jfl 0.7.2 → 0.8.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/commands/context-hub.d.ts.map +1 -1
- package/dist/commands/context-hub.js +26 -0
- package/dist/commands/context-hub.js.map +1 -1
- package/dist/commands/migrate-tenet.d.ts +25 -0
- package/dist/commands/migrate-tenet.d.ts.map +1 -0
- package/dist/commands/migrate-tenet.js +252 -0
- package/dist/commands/migrate-tenet.js.map +1 -0
- package/dist/commands/peter.d.ts.map +1 -1
- package/dist/commands/peter.js +47 -5
- package/dist/commands/peter.js.map +1 -1
- package/dist/commands/pi.d.ts +1 -0
- package/dist/commands/pi.d.ts.map +1 -1
- package/dist/commands/pi.js +5 -1
- package/dist/commands/pi.js.map +1 -1
- package/dist/commands/pivot.d.ts +28 -0
- package/dist/commands/pivot.d.ts.map +1 -0
- package/dist/commands/pivot.js +219 -0
- package/dist/commands/pivot.js.map +1 -0
- package/dist/commands/services-create.js +348 -0
- package/dist/commands/services-create.js.map +1 -1
- package/dist/dashboard-static/assets/index-BVrmW-ZI.js +154 -0
- package/dist/dashboard-static/assets/{index-CW8oWAdr.css → index-DtruPD44.css} +1 -1
- package/dist/dashboard-static/index.html +2 -2
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-generator.d.ts.map +1 -1
- package/dist/lib/agent-generator.js +15 -0
- package/dist/lib/agent-generator.js.map +1 -1
- package/dist/lib/counterfactual-engine.d.ts +136 -0
- package/dist/lib/counterfactual-engine.d.ts.map +1 -0
- package/dist/lib/counterfactual-engine.js +417 -0
- package/dist/lib/counterfactual-engine.js.map +1 -0
- package/dist/lib/dynamics-model.d.ts +107 -0
- package/dist/lib/dynamics-model.d.ts.map +1 -0
- package/dist/lib/dynamics-model.js +363 -0
- package/dist/lib/dynamics-model.js.map +1 -0
- package/dist/lib/eval-snapshot.d.ts.map +1 -1
- package/dist/lib/eval-snapshot.js +15 -4
- package/dist/lib/eval-snapshot.js.map +1 -1
- package/dist/lib/invariant-monitor.d.ts +50 -0
- package/dist/lib/invariant-monitor.d.ts.map +1 -0
- package/dist/lib/invariant-monitor.js +400 -0
- package/dist/lib/invariant-monitor.js.map +1 -0
- package/dist/lib/meta-orchestrator.d.ts +40 -3
- package/dist/lib/meta-orchestrator.d.ts.map +1 -1
- package/dist/lib/meta-orchestrator.js +181 -2
- package/dist/lib/meta-orchestrator.js.map +1 -1
- package/dist/lib/openclaw-sdk.d.ts +8 -0
- package/dist/lib/openclaw-sdk.d.ts.map +1 -1
- package/dist/lib/openclaw-sdk.js +11 -0
- package/dist/lib/openclaw-sdk.js.map +1 -1
- package/dist/lib/peter-parker-bridge.d.ts +37 -1
- package/dist/lib/peter-parker-bridge.d.ts.map +1 -1
- package/dist/lib/peter-parker-bridge.js +201 -1
- package/dist/lib/peter-parker-bridge.js.map +1 -1
- package/dist/lib/service-detector.d.ts +1 -1
- package/dist/lib/service-detector.d.ts.map +1 -1
- package/dist/lib/service-detector.js +26 -6
- package/dist/lib/service-detector.js.map +1 -1
- package/dist/lib/service-gtm.d.ts +1 -1
- package/dist/lib/service-gtm.d.ts.map +1 -1
- package/dist/lib/state-capture.d.ts +36 -0
- package/dist/lib/state-capture.d.ts.map +1 -0
- package/dist/lib/state-capture.js +541 -0
- package/dist/lib/state-capture.js.map +1 -0
- package/dist/lib/stratus-client.d.ts +78 -2
- package/dist/lib/stratus-client.d.ts.map +1 -1
- package/dist/lib/stratus-client.js +432 -1
- package/dist/lib/stratus-client.js.map +1 -1
- package/dist/lib/world-model-store.d.ts +172 -0
- package/dist/lib/world-model-store.d.ts.map +1 -0
- package/dist/lib/world-model-store.js +487 -0
- package/dist/lib/world-model-store.js.map +1 -0
- package/dist/types/world-model.d.ts +478 -0
- package/dist/types/world-model.d.ts.map +1 -0
- package/dist/types/world-model.js +80 -0
- package/dist/types/world-model.js.map +1 -0
- package/dist/utils/jfl-config.d.ts +5 -0
- package/dist/utils/jfl-config.d.ts.map +1 -1
- package/dist/utils/jfl-config.js +13 -1
- package/dist/utils/jfl-config.js.map +1 -1
- package/package.json +1 -1
- package/packages/pi/extensions/hud-tool.ts +2 -24
- package/packages/pi/extensions/index.ts +48 -25
- package/packages/pi/extensions/onboarding-v1.ts +455 -0
- package/packages/pi/extensions/onboarding-v2.ts +516 -0
- package/packages/pi/extensions/onboarding-v3.ts +675 -0
- package/packages/pi/extensions/pivot-tool.ts +59 -0
- package/packages/pi/extensions/session.ts +58 -0
- package/packages/pi/extensions/types.ts +2 -0
- package/packages/pi/skills/pivot/SKILL.md +91 -0
- package/template/.claude/settings.json +9 -0
- package/dist/dashboard-static/assets/index-Ck8f9dcM.js +0 -121
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding V2 — CINEMATIC (Sator Square)
|
|
3
|
+
*
|
|
4
|
+
* Full-screen animated onboarding sequence for the Tenet rebrand.
|
|
5
|
+
* Progressive reveal: Sator Square -> subsystem awakening -> mission briefing.
|
|
6
|
+
* Gold (#FFD700) on dark, theatrical pacing, mystery and gravitas.
|
|
7
|
+
*
|
|
8
|
+
* @purpose Cinematic onboarding overlay — Sator Square, subsystem init, mission brief
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs"
|
|
12
|
+
import { join } from "path"
|
|
13
|
+
import { execSync } from "child_process"
|
|
14
|
+
import type { PiContext, PiTheme, JflConfig } from "./types.js"
|
|
15
|
+
|
|
16
|
+
const GOLD = "#FFD700"
|
|
17
|
+
const GOLD_DIM = "#B8960F"
|
|
18
|
+
const DARK_GOLD = "#7A6400"
|
|
19
|
+
const WARM_WHITE = "#F5E6D3"
|
|
20
|
+
const CHARCOAL = "#1a1a1a"
|
|
21
|
+
const EMBER = "#CC5500"
|
|
22
|
+
|
|
23
|
+
const SATOR_SQUARE = [
|
|
24
|
+
["S", "A", "T", "O", "R"],
|
|
25
|
+
["A", "R", "E", "P", "O"],
|
|
26
|
+
["T", "E", "N", "E", "T"],
|
|
27
|
+
["O", "P", "E", "R", "A"],
|
|
28
|
+
["R", "O", "T", "A", "S"],
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
const TENET_POSITIONS = [
|
|
32
|
+
[2, 0], [2, 1], [2, 2], [2, 3], [2, 4],
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
interface SubsystemState {
|
|
36
|
+
name: string
|
|
37
|
+
icon: string
|
|
38
|
+
status: "waiting" | "connecting" | "ready" | "error"
|
|
39
|
+
detail: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface MissionData {
|
|
43
|
+
lastSessionSummary: string[]
|
|
44
|
+
pendingDecisions: number
|
|
45
|
+
currentPhase: string
|
|
46
|
+
projectName: string
|
|
47
|
+
projectType: string
|
|
48
|
+
branch: string
|
|
49
|
+
journalCount: number
|
|
50
|
+
activeAgents: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function gatherMissionData(root: string, config: JflConfig): MissionData {
|
|
54
|
+
const projectName = config.name ?? root.split("/").pop() ?? "TENET"
|
|
55
|
+
const projectType = config.type ?? "gtm"
|
|
56
|
+
|
|
57
|
+
let currentPhase = ""
|
|
58
|
+
const roadmapPath = join(root, "knowledge", "ROADMAP.md")
|
|
59
|
+
if (existsSync(roadmapPath)) {
|
|
60
|
+
try {
|
|
61
|
+
const content = readFileSync(roadmapPath, "utf-8")
|
|
62
|
+
const phaseMatch = content.match(/## (?:Phase|Current Phase)[:\s]*([^\n]+)/i)
|
|
63
|
+
if (phaseMatch) currentPhase = phaseMatch[1].trim()
|
|
64
|
+
} catch {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const lastSessionSummary: string[] = []
|
|
68
|
+
let journalCount = 0
|
|
69
|
+
const journalDir = join(root, ".jfl", "journal")
|
|
70
|
+
if (existsSync(journalDir)) {
|
|
71
|
+
try {
|
|
72
|
+
const files = readdirSync(journalDir).filter(f => f.endsWith(".jsonl")).sort()
|
|
73
|
+
for (const f of files) {
|
|
74
|
+
try {
|
|
75
|
+
const lines = readFileSync(join(journalDir, f), "utf-8").trim().split("\n").filter(Boolean)
|
|
76
|
+
journalCount += lines.length
|
|
77
|
+
for (const l of lines.slice(-5)) {
|
|
78
|
+
try {
|
|
79
|
+
const e = JSON.parse(l)
|
|
80
|
+
lastSessionSummary.push(e.title ?? e.summary ?? "")
|
|
81
|
+
} catch {}
|
|
82
|
+
}
|
|
83
|
+
} catch {}
|
|
84
|
+
}
|
|
85
|
+
} catch {}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let pendingDecisions = 0
|
|
89
|
+
const tasksPath = join(root, "knowledge", "TASKS.md")
|
|
90
|
+
if (existsSync(tasksPath)) {
|
|
91
|
+
try {
|
|
92
|
+
const content = readFileSync(tasksPath, "utf-8")
|
|
93
|
+
const pending = content.match(/\[ \]/g)
|
|
94
|
+
pendingDecisions = pending?.length ?? 0
|
|
95
|
+
} catch {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let branch = "main"
|
|
99
|
+
try {
|
|
100
|
+
branch = execSync("git branch --show-current", { cwd: root, stdio: ["pipe", "pipe", "ignore"] })
|
|
101
|
+
.toString().trim() || "main"
|
|
102
|
+
} catch {}
|
|
103
|
+
|
|
104
|
+
let activeAgents = 0
|
|
105
|
+
if (existsSync(journalDir)) {
|
|
106
|
+
const now = Date.now()
|
|
107
|
+
try {
|
|
108
|
+
for (const f of readdirSync(journalDir)) {
|
|
109
|
+
if (!f.startsWith("session-") || !f.endsWith(".jsonl")) continue
|
|
110
|
+
try {
|
|
111
|
+
const stat = statSync(join(journalDir, f))
|
|
112
|
+
if (now - stat.mtimeMs < 300000) activeAgents++
|
|
113
|
+
} catch {}
|
|
114
|
+
}
|
|
115
|
+
} catch {}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
lastSessionSummary: lastSessionSummary.slice(-4),
|
|
120
|
+
pendingDecisions,
|
|
121
|
+
currentPhase,
|
|
122
|
+
projectName,
|
|
123
|
+
projectType,
|
|
124
|
+
branch,
|
|
125
|
+
journalCount,
|
|
126
|
+
activeAgents,
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function probeSubsystem(
|
|
131
|
+
name: string,
|
|
132
|
+
probeFn: () => Promise<string>
|
|
133
|
+
): Promise<{ ok: boolean; detail: string }> {
|
|
134
|
+
try {
|
|
135
|
+
const detail = await probeFn()
|
|
136
|
+
return { ok: true, detail }
|
|
137
|
+
} catch {
|
|
138
|
+
return { ok: false, detail: "offline" }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function probeContextHub(root: string): Promise<string> {
|
|
143
|
+
const portFile = join(root, ".jfl", "context-hub.port")
|
|
144
|
+
if (!existsSync(portFile)) return "not found"
|
|
145
|
+
const port = readFileSync(portFile, "utf-8").trim()
|
|
146
|
+
const resp = await fetch(`http://localhost:${port}/api/health`, {
|
|
147
|
+
signal: AbortSignal.timeout(2000),
|
|
148
|
+
})
|
|
149
|
+
if (!resp.ok) throw new Error("unhealthy")
|
|
150
|
+
const data = await resp.json() as { status?: string; itemCount?: number }
|
|
151
|
+
const count = data.itemCount ?? 0
|
|
152
|
+
return count > 0 ? `${count} items` : "ready"
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function probeMemory(root: string): Promise<string> {
|
|
156
|
+
const dbPath = join(root, ".jfl", "memory.db")
|
|
157
|
+
if (!existsSync(dbPath)) return "no database"
|
|
158
|
+
const journalDir = join(root, ".jfl", "journal")
|
|
159
|
+
if (!existsSync(journalDir)) return "0 memories"
|
|
160
|
+
let count = 0
|
|
161
|
+
for (const f of readdirSync(journalDir).filter(f => f.endsWith(".jsonl"))) {
|
|
162
|
+
try {
|
|
163
|
+
count += readFileSync(join(journalDir, f), "utf-8").trim().split("\n").filter(Boolean).length
|
|
164
|
+
} catch {}
|
|
165
|
+
}
|
|
166
|
+
return `${count} memories`
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function probeSubway(root: string): Promise<string> {
|
|
170
|
+
const envPath = join(root, ".env")
|
|
171
|
+
if (!existsSync(envPath)) return "no key"
|
|
172
|
+
try {
|
|
173
|
+
const env = readFileSync(envPath, "utf-8")
|
|
174
|
+
if (env.includes("STRATUS_API_KEY") || env.includes("stratus_sk")) {
|
|
175
|
+
return "relay online"
|
|
176
|
+
}
|
|
177
|
+
} catch {}
|
|
178
|
+
return "no relay"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function setupOnboarding(ctx: PiContext, config: JflConfig): Promise<void> {
|
|
182
|
+
const root = ctx.session.projectRoot
|
|
183
|
+
|
|
184
|
+
if (!ctx.ui.hasUI) {
|
|
185
|
+
ctx.log("Onboarding v2: no TUI, skipping cinematic", "debug")
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const mission = gatherMissionData(root, config)
|
|
190
|
+
|
|
191
|
+
const subsystemProbes = await Promise.allSettled([
|
|
192
|
+
probeSubsystem("Context Hub", () => probeContextHub(root)),
|
|
193
|
+
probeSubsystem("Memory", () => probeMemory(root)),
|
|
194
|
+
probeSubsystem("Subway", () => probeSubway(root)),
|
|
195
|
+
])
|
|
196
|
+
|
|
197
|
+
const subsystems: SubsystemState[] = [
|
|
198
|
+
{
|
|
199
|
+
name: "Context Hub",
|
|
200
|
+
icon: "◆",
|
|
201
|
+
status: "waiting",
|
|
202
|
+
detail: "",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "Memory",
|
|
206
|
+
icon: "◆",
|
|
207
|
+
status: "waiting",
|
|
208
|
+
detail: "",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "Subway",
|
|
212
|
+
icon: "◆",
|
|
213
|
+
status: "waiting",
|
|
214
|
+
detail: "",
|
|
215
|
+
},
|
|
216
|
+
]
|
|
217
|
+
|
|
218
|
+
const probeResults = subsystemProbes.map(p =>
|
|
219
|
+
p.status === "fulfilled" ? p.value : { ok: false, detail: "probe failed" }
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
await ctx.ui.custom<void>((tui: any, theme: PiTheme, _kb: any, done: (r: void) => void) => {
|
|
223
|
+
let phase: "square" | "subsystems" | "briefing" | "ready" = "square"
|
|
224
|
+
let squareProgress = 0
|
|
225
|
+
let tenetGlow = false
|
|
226
|
+
let subsystemIndex = -1
|
|
227
|
+
let briefingRevealed = 0
|
|
228
|
+
let readyFade = 0
|
|
229
|
+
let frameTimer: ReturnType<typeof setTimeout> | null = null
|
|
230
|
+
let disposed = false
|
|
231
|
+
|
|
232
|
+
const TOTAL_CELLS = 25
|
|
233
|
+
const SQUARE_FRAME_MS = 50
|
|
234
|
+
const SUBSYSTEM_FRAME_MS = 300
|
|
235
|
+
const BRIEFING_FRAME_MS = 200
|
|
236
|
+
const READY_DELAY_MS = 600
|
|
237
|
+
const TENET_GLOW_DELAY_MS = 400
|
|
238
|
+
|
|
239
|
+
function scheduleNext(fn: () => void, ms: number) {
|
|
240
|
+
if (disposed) return
|
|
241
|
+
frameTimer = setTimeout(() => {
|
|
242
|
+
if (disposed) return
|
|
243
|
+
fn()
|
|
244
|
+
tui.requestRender()
|
|
245
|
+
}, ms)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function advanceSquare() {
|
|
249
|
+
if (squareProgress < TOTAL_CELLS) {
|
|
250
|
+
squareProgress++
|
|
251
|
+
scheduleNext(advanceSquare, SQUARE_FRAME_MS)
|
|
252
|
+
} else if (!tenetGlow) {
|
|
253
|
+
scheduleNext(() => {
|
|
254
|
+
tenetGlow = true
|
|
255
|
+
scheduleNext(startSubsystems, TENET_GLOW_DELAY_MS)
|
|
256
|
+
}, TENET_GLOW_DELAY_MS)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function startSubsystems() {
|
|
261
|
+
phase = "subsystems"
|
|
262
|
+
subsystemIndex = 0
|
|
263
|
+
advanceSubsystem()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function advanceSubsystem() {
|
|
267
|
+
if (subsystemIndex < subsystems.length) {
|
|
268
|
+
const result = probeResults[subsystemIndex]
|
|
269
|
+
subsystems[subsystemIndex].status = result.ok ? "ready" : "error"
|
|
270
|
+
subsystems[subsystemIndex].detail = result.detail
|
|
271
|
+
subsystemIndex++
|
|
272
|
+
if (subsystemIndex < subsystems.length) {
|
|
273
|
+
scheduleNext(advanceSubsystem, SUBSYSTEM_FRAME_MS)
|
|
274
|
+
} else {
|
|
275
|
+
scheduleNext(startBriefing, SUBSYSTEM_FRAME_MS)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function startBriefing() {
|
|
281
|
+
phase = "briefing"
|
|
282
|
+
briefingRevealed = 0
|
|
283
|
+
advanceBriefing()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function advanceBriefing() {
|
|
287
|
+
const totalLines = getBriefingLines().length
|
|
288
|
+
if (briefingRevealed < totalLines) {
|
|
289
|
+
briefingRevealed++
|
|
290
|
+
scheduleNext(advanceBriefing, BRIEFING_FRAME_MS)
|
|
291
|
+
} else {
|
|
292
|
+
scheduleNext(startReady, READY_DELAY_MS)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function getBriefingLines(): string[] {
|
|
297
|
+
const lines: string[] = []
|
|
298
|
+
if (mission.lastSessionSummary.length > 0) {
|
|
299
|
+
for (const s of mission.lastSessionSummary.slice(-3)) {
|
|
300
|
+
if (s.trim()) lines.push(s.trim())
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (mission.pendingDecisions > 0) {
|
|
304
|
+
lines.push(`${mission.pendingDecisions} tasks pending`)
|
|
305
|
+
}
|
|
306
|
+
if (mission.currentPhase) {
|
|
307
|
+
lines.push(mission.currentPhase)
|
|
308
|
+
}
|
|
309
|
+
if (lines.length === 0) {
|
|
310
|
+
lines.push("First session. No prior context.")
|
|
311
|
+
}
|
|
312
|
+
return lines
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function startReady() {
|
|
316
|
+
phase = "ready"
|
|
317
|
+
readyFade = 0
|
|
318
|
+
scheduleNext(() => {
|
|
319
|
+
readyFade = 1
|
|
320
|
+
scheduleNext(() => {
|
|
321
|
+
if (!disposed) done(undefined)
|
|
322
|
+
}, 500)
|
|
323
|
+
}, 300)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
scheduleNext(advanceSquare, 300)
|
|
327
|
+
|
|
328
|
+
function gold(t: PiTheme, text: string): string {
|
|
329
|
+
return t.fg(GOLD, text)
|
|
330
|
+
}
|
|
331
|
+
function dimGold(t: PiTheme, text: string): string {
|
|
332
|
+
return t.fg(GOLD_DIM, text)
|
|
333
|
+
}
|
|
334
|
+
function darkGold(t: PiTheme, text: string): string {
|
|
335
|
+
return t.fg(DARK_GOLD, text)
|
|
336
|
+
}
|
|
337
|
+
function warm(t: PiTheme, text: string): string {
|
|
338
|
+
return t.fg(WARM_WHITE, text)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function renderSquare(theme: PiTheme, width: number): string[] {
|
|
342
|
+
const lines: string[] = []
|
|
343
|
+
const squareWidth = 5 * 4 - 1
|
|
344
|
+
const leftPad = Math.max(0, Math.floor((width - squareWidth) / 2))
|
|
345
|
+
const pad = " ".repeat(leftPad)
|
|
346
|
+
|
|
347
|
+
for (let row = 0; row < 5; row++) {
|
|
348
|
+
let line = ""
|
|
349
|
+
for (let col = 0; col < 5; col++) {
|
|
350
|
+
const cellIndex = row * 5 + col
|
|
351
|
+
const char = SATOR_SQUARE[row][col]
|
|
352
|
+
const isTenet = TENET_POSITIONS.some(([r, c]) => r === row && c === col)
|
|
353
|
+
|
|
354
|
+
if (cellIndex < squareProgress) {
|
|
355
|
+
if (tenetGlow && isTenet) {
|
|
356
|
+
line += theme.bold(gold(theme, char))
|
|
357
|
+
} else if (tenetGlow) {
|
|
358
|
+
line += darkGold(theme, char)
|
|
359
|
+
} else {
|
|
360
|
+
line += dimGold(theme, char)
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
line += darkGold(theme, ".")
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (col < 4) line += " "
|
|
367
|
+
}
|
|
368
|
+
lines.push(pad + line)
|
|
369
|
+
if (row < 4) lines.push("")
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return lines
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function renderSubsystems(theme: PiTheme, width: number): string[] {
|
|
376
|
+
const lines: string[] = []
|
|
377
|
+
const innerWidth = Math.min(width - 8, 50)
|
|
378
|
+
const leftPad = Math.max(0, Math.floor((width - innerWidth) / 2))
|
|
379
|
+
const pad = " ".repeat(leftPad)
|
|
380
|
+
|
|
381
|
+
for (let i = 0; i < subsystems.length; i++) {
|
|
382
|
+
const sys = subsystems[i]
|
|
383
|
+
let icon: string
|
|
384
|
+
let nameStr: string
|
|
385
|
+
let detailStr: string
|
|
386
|
+
|
|
387
|
+
if (i > subsystemIndex - 1 && i !== subsystemIndex) {
|
|
388
|
+
icon = darkGold(theme, "○")
|
|
389
|
+
nameStr = darkGold(theme, sys.name)
|
|
390
|
+
detailStr = darkGold(theme, "waiting...")
|
|
391
|
+
} else if (sys.status === "ready") {
|
|
392
|
+
icon = gold(theme, "●")
|
|
393
|
+
nameStr = warm(theme, sys.name)
|
|
394
|
+
detailStr = gold(theme, sys.detail)
|
|
395
|
+
} else if (sys.status === "error") {
|
|
396
|
+
icon = theme.fg(EMBER, "●")
|
|
397
|
+
nameStr = theme.fg(EMBER, sys.name)
|
|
398
|
+
detailStr = theme.fg(EMBER, sys.detail)
|
|
399
|
+
} else {
|
|
400
|
+
icon = dimGold(theme, "◌")
|
|
401
|
+
nameStr = dimGold(theme, sys.name)
|
|
402
|
+
detailStr = dimGold(theme, "connecting...")
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const namePadded = (sys.name + " ".repeat(Math.max(0, 16 - sys.name.length)))
|
|
406
|
+
if (sys.status === "ready") {
|
|
407
|
+
lines.push(pad + `${icon} ${warm(theme, namePadded)} ${detailStr}`)
|
|
408
|
+
} else if (sys.status === "error") {
|
|
409
|
+
lines.push(pad + `${icon} ${theme.fg(EMBER, namePadded)} ${detailStr}`)
|
|
410
|
+
} else if (i > subsystemIndex - 1) {
|
|
411
|
+
lines.push(pad + `${icon} ${darkGold(theme, namePadded)} ${detailStr}`)
|
|
412
|
+
} else {
|
|
413
|
+
lines.push(pad + `${icon} ${dimGold(theme, namePadded)} ${detailStr}`)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return lines
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function renderBriefing(theme: PiTheme, width: number): string[] {
|
|
421
|
+
const lines: string[] = []
|
|
422
|
+
const innerWidth = Math.min(width - 8, 56)
|
|
423
|
+
const leftPad = Math.max(0, Math.floor((width - innerWidth) / 2))
|
|
424
|
+
const pad = " ".repeat(leftPad)
|
|
425
|
+
|
|
426
|
+
const divider = darkGold(theme, "───") + dimGold(theme, " Last Session ") + darkGold(theme, "─".repeat(Math.max(0, innerWidth - 17)))
|
|
427
|
+
lines.push(pad + divider)
|
|
428
|
+
lines.push("")
|
|
429
|
+
|
|
430
|
+
const briefingLines = getBriefingLines()
|
|
431
|
+
for (let i = 0; i < briefingLines.length; i++) {
|
|
432
|
+
if (i < briefingRevealed) {
|
|
433
|
+
lines.push(pad + " " + warm(theme, briefingLines[i]))
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return lines
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function renderReady(theme: PiTheme, width: number): string[] {
|
|
441
|
+
const lines: string[] = []
|
|
442
|
+
const leftPad = Math.max(0, Math.floor((width - 20) / 2))
|
|
443
|
+
const pad = " ".repeat(leftPad)
|
|
444
|
+
|
|
445
|
+
if (readyFade >= 1) {
|
|
446
|
+
lines.push(pad + theme.bold(gold(theme, mission.projectName)))
|
|
447
|
+
lines.push(pad + dimGold(theme, `${mission.projectType} · ${mission.branch}`))
|
|
448
|
+
} else {
|
|
449
|
+
lines.push(pad + darkGold(theme, mission.projectName))
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return lines
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
handleInput(key: string) {
|
|
457
|
+
if (key === "\x1b" || key === "q" || key === " " || key === "\r") {
|
|
458
|
+
if (frameTimer) clearTimeout(frameTimer)
|
|
459
|
+
disposed = true
|
|
460
|
+
done(undefined)
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
render(width: number): string[] {
|
|
465
|
+
const lines: string[] = []
|
|
466
|
+
const w = Math.min(width, 80)
|
|
467
|
+
|
|
468
|
+
lines.push("")
|
|
469
|
+
lines.push("")
|
|
470
|
+
|
|
471
|
+
if (phase === "square" || phase === "subsystems" || phase === "briefing" || phase === "ready") {
|
|
472
|
+
const squareLines = renderSquare(theme, w)
|
|
473
|
+
lines.push(...squareLines)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
lines.push("")
|
|
477
|
+
|
|
478
|
+
if (phase === "subsystems" || phase === "briefing" || phase === "ready") {
|
|
479
|
+
const sysLines = renderSubsystems(theme, w)
|
|
480
|
+
lines.push(...sysLines)
|
|
481
|
+
lines.push("")
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (phase === "briefing" || phase === "ready") {
|
|
485
|
+
const briefLines = renderBriefing(theme, w)
|
|
486
|
+
lines.push(...briefLines)
|
|
487
|
+
lines.push("")
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (phase === "ready") {
|
|
491
|
+
const readyLines = renderReady(theme, w)
|
|
492
|
+
lines.push(...readyLines)
|
|
493
|
+
lines.push("")
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (phase !== "ready") {
|
|
497
|
+
const hint = darkGold(theme, "Esc to skip")
|
|
498
|
+
const hintPad = " ".repeat(Math.max(0, Math.floor((w - 11) / 2)))
|
|
499
|
+
lines.push("")
|
|
500
|
+
lines.push(hintPad + hint)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
lines.push("")
|
|
504
|
+
|
|
505
|
+
return lines
|
|
506
|
+
},
|
|
507
|
+
|
|
508
|
+
invalidate() {},
|
|
509
|
+
}
|
|
510
|
+
}, {
|
|
511
|
+
overlay: true,
|
|
512
|
+
overlayOptions: {
|
|
513
|
+
dismissOnEscape: false,
|
|
514
|
+
},
|
|
515
|
+
})
|
|
516
|
+
}
|