@gaosh3n/pi-package-manager 0.1.0 → 0.1.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/index.ts +67 -653
- package/internal/controller.ts +223 -0
- package/internal/model.ts +41 -0
- package/internal/records.ts +81 -0
- package/internal/reports.ts +351 -0
- package/internal/runtime.ts +54 -0
- package/package.json +2 -1
package/index.ts
CHANGED
|
@@ -1,169 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
getAgentDir,
|
|
4
|
-
keyText,
|
|
5
|
-
SettingsManager,
|
|
6
|
-
type CustomEntry,
|
|
7
|
-
type ExecResult,
|
|
8
|
-
type ExtensionAPI,
|
|
9
|
-
type ExtensionCommandContext,
|
|
10
|
-
type ExtensionContext,
|
|
11
|
-
type SessionEntry,
|
|
12
|
-
type SessionStartEvent,
|
|
13
|
-
} from "@earendil-works/pi-coding-agent"
|
|
14
|
-
import { type AutocompleteItem, Box, Text } from "@earendil-works/pi-tui"
|
|
15
|
-
|
|
16
|
-
export const AUTO_UPDATE_RECORD_ENTRY_TYPE = "package-manager-auto-update-record"
|
|
17
|
-
export const REPORT_ENTRY_TYPE = "package-manager-report"
|
|
18
|
-
export const PACKAGE_MANAGER_TITLE = "Pi Package Manager"
|
|
19
|
-
|
|
20
|
-
const UPDATE_COMMAND = ["update", "--extensions"] as const
|
|
21
|
-
const PACKAGE_MANAGER_WIDGET_KEY = "pi-package-manager"
|
|
22
|
-
const RELOAD_COUNTDOWN_SECONDS = 5
|
|
23
|
-
const OUTPUT_PREVIEW_LINE_COUNT = 8
|
|
24
|
-
|
|
25
|
-
export type AutoUpdateOutcome = "succeeded" | "failed" | "skipped"
|
|
26
|
-
export type ReportTone = "info" | "success" | "warning" | "error"
|
|
27
|
-
|
|
28
|
-
type WidgetState =
|
|
29
|
-
| { mode: "status-checking" }
|
|
30
|
-
| { mode: "checking" }
|
|
31
|
-
| { mode: "installing"; packages: number }
|
|
32
|
-
| { mode: "countdown"; secondsRemaining: number }
|
|
33
|
-
|
|
34
|
-
export interface AutoUpdateRecord {
|
|
35
|
-
startedAtUtc: string
|
|
36
|
-
endedAtUtc: string
|
|
37
|
-
outcome: AutoUpdateOutcome
|
|
38
|
-
packagesUpdated: number
|
|
39
|
-
reason?: string
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface PackageManagerReport {
|
|
43
|
-
title: string
|
|
44
|
-
headline?: string
|
|
45
|
-
tone: ReportTone
|
|
46
|
-
lines: string[]
|
|
47
|
-
lineTone?: "default" | "dim"
|
|
48
|
-
output?: string
|
|
49
|
-
outputLabel?: string
|
|
50
|
-
outputTone?: "default" | "dim"
|
|
51
|
-
hideOutputWhenCollapsed?: boolean
|
|
52
|
-
}
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"
|
|
2
|
+
import { type AutocompleteItem } from "@earendil-works/pi-tui"
|
|
53
3
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
4
|
+
import {
|
|
5
|
+
createPackageManagerController,
|
|
6
|
+
shouldAutoUpdateOnSessionStart,
|
|
7
|
+
} from "./internal/controller.ts"
|
|
8
|
+
import {
|
|
9
|
+
createAutoUpdateRecord,
|
|
10
|
+
getLastAutoUpdateRecord,
|
|
11
|
+
isAutoUpdateRecord,
|
|
12
|
+
isAutoUpdateRecordEntry,
|
|
13
|
+
} from "./internal/records.ts"
|
|
14
|
+
import {
|
|
15
|
+
AUTO_UPDATE_RECORD_ENTRY_TYPE,
|
|
16
|
+
PACKAGE_MANAGER_TITLE,
|
|
17
|
+
REPORT_ENTRY_TYPE,
|
|
18
|
+
type AutoUpdateOutcome,
|
|
19
|
+
type AutoUpdateRecord,
|
|
20
|
+
type PackageManagerReport,
|
|
21
|
+
type PackageStatusSnapshot,
|
|
22
|
+
type ReportTone,
|
|
23
|
+
type WidgetState,
|
|
24
|
+
} from "./internal/model.ts"
|
|
25
|
+
import {
|
|
26
|
+
createAutomaticUpdateWidgetLines,
|
|
27
|
+
createAutoUpdateResultReport,
|
|
28
|
+
createReportEntryRenderer,
|
|
29
|
+
createStatusReport,
|
|
30
|
+
formatStatusLines,
|
|
31
|
+
formatUtcTimestamp,
|
|
32
|
+
} from "./internal/reports.ts"
|
|
33
|
+
import {
|
|
34
|
+
defaultPackageManagerDeps,
|
|
35
|
+
type PackageManagerDeps,
|
|
36
|
+
} from "./internal/runtime.ts"
|
|
58
37
|
|
|
59
|
-
export default function (
|
|
60
|
-
|
|
38
|
+
export default function initPackageManager(
|
|
39
|
+
pi: ExtensionAPI,
|
|
40
|
+
deps: PackageManagerDeps = defaultPackageManagerDeps,
|
|
41
|
+
): void {
|
|
42
|
+
const controller = createPackageManagerController(pi, deps)
|
|
61
43
|
|
|
62
44
|
pi.registerEntryRenderer<PackageManagerReport>(
|
|
63
45
|
REPORT_ENTRY_TYPE,
|
|
64
|
-
(
|
|
65
|
-
const report = entry.data
|
|
66
|
-
|
|
67
|
-
if (!report) {
|
|
68
|
-
return undefined
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const toneColor =
|
|
72
|
-
report.tone === "error"
|
|
73
|
-
? "error"
|
|
74
|
-
: report.tone === "warning"
|
|
75
|
-
? "warning"
|
|
76
|
-
: report.tone === "success"
|
|
77
|
-
? "success"
|
|
78
|
-
: "accent"
|
|
79
|
-
const lineTextTone = report.lineTone === "dim" ? "dim" : "customMessageText"
|
|
80
|
-
const outputTextTone = report.outputTone === "dim" ? "dim" : lineTextTone
|
|
81
|
-
|
|
82
|
-
const box = new Box(1, 1, (text: string) =>
|
|
83
|
-
theme.bg("customMessageBg", text),
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
box.addChild(
|
|
87
|
-
new Text(
|
|
88
|
-
`${theme.fg(toneColor, "●")} ${theme.bold(theme.fg("customMessageLabel", report.title))}`,
|
|
89
|
-
0,
|
|
90
|
-
0,
|
|
91
|
-
),
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
if (report.headline) {
|
|
95
|
-
box.addChild(new Text(theme.fg("text", report.headline), 0, 0))
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (report.lines.length > 0) {
|
|
99
|
-
box.addChild(
|
|
100
|
-
new Text(
|
|
101
|
-
report.lines
|
|
102
|
-
.map((line) => theme.fg(lineTextTone, line))
|
|
103
|
-
.join("\n"),
|
|
104
|
-
0,
|
|
105
|
-
0,
|
|
106
|
-
),
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (report.output?.trim()) {
|
|
111
|
-
if (!expanded && report.hideOutputWhenCollapsed) {
|
|
112
|
-
box.addChild(
|
|
113
|
-
new Text(
|
|
114
|
-
formatExpandHint(theme, "to expand to see update output."),
|
|
115
|
-
0,
|
|
116
|
-
0,
|
|
117
|
-
),
|
|
118
|
-
)
|
|
119
|
-
} else {
|
|
120
|
-
const { text, truncated } = formatReportOutput(
|
|
121
|
-
report.output,
|
|
122
|
-
expanded,
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
box.addChild(
|
|
126
|
-
new Text(
|
|
127
|
-
theme.fg(
|
|
128
|
-
outputTextTone,
|
|
129
|
-
report.outputLabel ?? "Update output:",
|
|
130
|
-
),
|
|
131
|
-
0,
|
|
132
|
-
0,
|
|
133
|
-
),
|
|
134
|
-
)
|
|
135
|
-
box.addChild(new Text(theme.fg(outputTextTone, text), 0, 0))
|
|
136
|
-
|
|
137
|
-
if (truncated) {
|
|
138
|
-
box.addChild(
|
|
139
|
-
new Text(
|
|
140
|
-
formatExpandHint(
|
|
141
|
-
theme,
|
|
142
|
-
"to expand to view the full update output.",
|
|
143
|
-
),
|
|
144
|
-
0,
|
|
145
|
-
0,
|
|
146
|
-
),
|
|
147
|
-
)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return box
|
|
153
|
-
},
|
|
46
|
+
createReportEntryRenderer(),
|
|
154
47
|
)
|
|
155
48
|
|
|
156
|
-
pi.on("session_start",
|
|
157
|
-
lastAutoUpdateRecord = getLastAutoUpdateRecord(ctx.sessionManager.getEntries())
|
|
158
|
-
|
|
159
|
-
if (!shouldAutoUpdateOnSessionStart(event)) {
|
|
160
|
-
return
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
pi.sendUserMessage("/package-manager update --startup", {
|
|
164
|
-
expandPromptTemplates: true,
|
|
165
|
-
})
|
|
166
|
-
})
|
|
49
|
+
pi.on("session_start", controller.onSessionStart)
|
|
167
50
|
|
|
168
51
|
pi.registerCommand("package-manager", {
|
|
169
52
|
description:
|
|
@@ -184,16 +67,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
184
67
|
const subcommand = tokens[0] ?? "status"
|
|
185
68
|
|
|
186
69
|
if (subcommand === "status") {
|
|
187
|
-
await
|
|
70
|
+
await controller.handleStatus(ctx)
|
|
188
71
|
return
|
|
189
72
|
}
|
|
190
73
|
|
|
191
74
|
if (subcommand === "update") {
|
|
192
|
-
|
|
75
|
+
await controller.handleUpdate(ctx, {
|
|
193
76
|
startupTriggered: tokens.includes("--startup"),
|
|
194
77
|
})
|
|
195
|
-
|
|
196
|
-
lastAutoUpdateRecord = result.autoUpdateRecord
|
|
197
78
|
return
|
|
198
79
|
}
|
|
199
80
|
|
|
@@ -202,494 +83,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
202
83
|
})
|
|
203
84
|
}
|
|
204
85
|
|
|
205
|
-
export
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return isoUtc.replace("T", " ").replace(/\.\d{3}Z$/, " UTC+00")
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export function formatStatusLines(snapshot: PackageStatusSnapshot): string[] {
|
|
232
|
-
if (!snapshot.lastAutoUpdate) {
|
|
233
|
-
return ["Latest package update: none recorded."]
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const lines = [
|
|
237
|
-
`Latest update start: ${formatUtcTimestamp(snapshot.lastAutoUpdate.startedAtUtc)}`,
|
|
238
|
-
`Latest update end: ${formatUtcTimestamp(snapshot.lastAutoUpdate.endedAtUtc)}`,
|
|
239
|
-
`Latest update result: ${snapshot.lastAutoUpdate.outcome}`,
|
|
240
|
-
`Latest update packages updated: ${snapshot.lastAutoUpdate.packagesUpdated}`,
|
|
241
|
-
]
|
|
242
|
-
|
|
243
|
-
if (snapshot.lastAutoUpdate.reason) {
|
|
244
|
-
lines.push(`Latest update detail: ${snapshot.lastAutoUpdate.reason}`)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return lines
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export function createStatusReport(
|
|
251
|
-
snapshot: PackageStatusSnapshot,
|
|
252
|
-
): PackageManagerReport {
|
|
253
|
-
return {
|
|
254
|
-
title: PACKAGE_MANAGER_TITLE,
|
|
255
|
-
headline:
|
|
256
|
-
snapshot.availableUpdates.length === 0
|
|
257
|
-
? "No package updates are available."
|
|
258
|
-
: `${snapshot.availableUpdates.length} package update${snapshot.availableUpdates.length === 1 ? " is" : "s are"} available.`,
|
|
259
|
-
tone: snapshot.availableUpdates.length > 0 ? "warning" : "info",
|
|
260
|
-
lines: formatStatusLines(snapshot),
|
|
261
|
-
lineTone: "dim",
|
|
262
|
-
output:
|
|
263
|
-
snapshot.availableUpdates.length > 0
|
|
264
|
-
? snapshot.availableUpdates.map((name) => `- ${name}`).join("\n")
|
|
265
|
-
: undefined,
|
|
266
|
-
outputLabel:
|
|
267
|
-
snapshot.availableUpdates.length > 0 ? "Available updates:" : undefined,
|
|
268
|
-
outputTone: "dim",
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export function createAutoUpdateResultReport(input: {
|
|
273
|
-
record: AutoUpdateRecord
|
|
274
|
-
output?: string
|
|
275
|
-
reloadAfterSeconds?: number
|
|
276
|
-
}): PackageManagerReport {
|
|
277
|
-
const lines = [
|
|
278
|
-
`Start: ${formatUtcTimestamp(input.record.startedAtUtc)}`,
|
|
279
|
-
`End: ${formatUtcTimestamp(input.record.endedAtUtc)}`,
|
|
280
|
-
`Result: ${input.record.outcome}`,
|
|
281
|
-
`Packages updated: ${input.record.packagesUpdated}`,
|
|
282
|
-
]
|
|
283
|
-
|
|
284
|
-
if (input.reloadAfterSeconds) {
|
|
285
|
-
lines.push(
|
|
286
|
-
`Reloading in ${input.reloadAfterSeconds} seconds to activate updated package resources.`,
|
|
287
|
-
)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return {
|
|
291
|
-
title: PACKAGE_MANAGER_TITLE,
|
|
292
|
-
headline: formatAutomaticUpdateHeadline(input.record.outcome),
|
|
293
|
-
tone:
|
|
294
|
-
input.record.outcome === "failed"
|
|
295
|
-
? "error"
|
|
296
|
-
: input.record.outcome === "succeeded"
|
|
297
|
-
? "success"
|
|
298
|
-
: "info",
|
|
299
|
-
lines,
|
|
300
|
-
lineTone: "dim",
|
|
301
|
-
output: input.output ?? input.record.reason,
|
|
302
|
-
outputTone: "dim",
|
|
303
|
-
hideOutputWhenCollapsed: true,
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
export function createAutomaticUpdateWidgetLines(state: WidgetState): string[] {
|
|
308
|
-
if (state.mode === "status-checking") {
|
|
309
|
-
return ["Pi package status in progress.", "Checking for package updates..."]
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (state.mode === "checking") {
|
|
313
|
-
return [
|
|
314
|
-
formatAutomaticUpdateHeadline("in-progress"),
|
|
315
|
-
"Checking for package updates...",
|
|
316
|
-
]
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (state.mode === "installing") {
|
|
320
|
-
return [
|
|
321
|
-
formatAutomaticUpdateHeadline("in-progress"),
|
|
322
|
-
`Installing ${state.packages} package update${state.packages === 1 ? "" : "s"}...`,
|
|
323
|
-
]
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return [
|
|
327
|
-
formatAutomaticUpdateHeadline("succeeded"),
|
|
328
|
-
`Reloading in ${state.secondsRemaining} second${state.secondsRemaining === 1 ? "" : "s"} to activate updated package resources.`,
|
|
329
|
-
]
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function formatAutomaticUpdateHeadline(
|
|
333
|
-
state: AutoUpdateOutcome | "in-progress",
|
|
334
|
-
): string {
|
|
335
|
-
const suffix =
|
|
336
|
-
state === "in-progress"
|
|
337
|
-
? "in progress"
|
|
338
|
-
: state === "succeeded"
|
|
339
|
-
? "completed"
|
|
340
|
-
: state
|
|
341
|
-
|
|
342
|
-
return `Pi package(s) update ${suffix}.`
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export function getLastAutoUpdateRecord(
|
|
346
|
-
entries: readonly SessionEntry[],
|
|
347
|
-
): AutoUpdateRecord | undefined {
|
|
348
|
-
for (let index = entries.length - 1; index >= 0; index--) {
|
|
349
|
-
const entry = entries[index]
|
|
350
|
-
|
|
351
|
-
if (!isAutoUpdateRecordEntry(entry)) {
|
|
352
|
-
continue
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return entry.data
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return undefined
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
async function handleStatusCommand(
|
|
362
|
-
pi: ExtensionAPI,
|
|
363
|
-
ctx: ExtensionCommandContext,
|
|
364
|
-
lastAutoUpdateRecord: AutoUpdateRecord | undefined,
|
|
365
|
-
): Promise<void> {
|
|
366
|
-
setPackageManagerWidget(ctx, { mode: "status-checking" })
|
|
367
|
-
|
|
368
|
-
try {
|
|
369
|
-
const availableUpdates = await checkForAvailableUpdates(ctx)
|
|
370
|
-
pi.appendEntry(
|
|
371
|
-
REPORT_ENTRY_TYPE,
|
|
372
|
-
createStatusReport({
|
|
373
|
-
availableUpdates,
|
|
374
|
-
lastAutoUpdate: lastAutoUpdateRecord,
|
|
375
|
-
}),
|
|
376
|
-
)
|
|
377
|
-
} catch (error) {
|
|
378
|
-
pi.appendEntry(REPORT_ENTRY_TYPE, {
|
|
379
|
-
title: PACKAGE_MANAGER_TITLE,
|
|
380
|
-
headline: "Package status check failed.",
|
|
381
|
-
tone: "error",
|
|
382
|
-
lines: formatStatusLines({
|
|
383
|
-
availableUpdates: [],
|
|
384
|
-
lastAutoUpdate: lastAutoUpdateRecord,
|
|
385
|
-
}),
|
|
386
|
-
lineTone: "dim",
|
|
387
|
-
output: `Failed to check package updates: ${getErrorMessage(error)}`,
|
|
388
|
-
outputLabel: "Error detail:",
|
|
389
|
-
outputTone: "dim",
|
|
390
|
-
})
|
|
391
|
-
} finally {
|
|
392
|
-
clearPackageManagerWidget(ctx)
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
async function runUpdate(
|
|
397
|
-
pi: ExtensionAPI,
|
|
398
|
-
ctx: ExtensionCommandContext,
|
|
399
|
-
options: { startupTriggered: boolean },
|
|
400
|
-
): Promise<{ autoUpdateRecord: AutoUpdateRecord }> {
|
|
401
|
-
const startedAtUtc = new Date().toISOString()
|
|
402
|
-
let shouldClearWidget = true
|
|
403
|
-
|
|
404
|
-
setPackageManagerWidget(ctx, { mode: "checking" })
|
|
405
|
-
|
|
406
|
-
try {
|
|
407
|
-
if (process.env.PI_OFFLINE) {
|
|
408
|
-
const record = createAutoUpdateRecord({
|
|
409
|
-
startedAtUtc,
|
|
410
|
-
endedAtUtc: new Date().toISOString(),
|
|
411
|
-
outcome: "skipped",
|
|
412
|
-
packagesUpdated: 0,
|
|
413
|
-
reason: "PI_OFFLINE is set.",
|
|
414
|
-
})
|
|
415
|
-
|
|
416
|
-
pi.appendEntry(AUTO_UPDATE_RECORD_ENTRY_TYPE, record)
|
|
417
|
-
pi.appendEntry(REPORT_ENTRY_TYPE, createAutoUpdateResultReport({ record }))
|
|
418
|
-
return { autoUpdateRecord: record }
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
const availableUpdates = await checkForAvailableUpdates(ctx)
|
|
422
|
-
|
|
423
|
-
if (availableUpdates.length === 0) {
|
|
424
|
-
const record = createAutoUpdateRecord({
|
|
425
|
-
startedAtUtc,
|
|
426
|
-
endedAtUtc: new Date().toISOString(),
|
|
427
|
-
outcome: "skipped",
|
|
428
|
-
packagesUpdated: 0,
|
|
429
|
-
reason: "No package updates are available.",
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
pi.appendEntry(AUTO_UPDATE_RECORD_ENTRY_TYPE, record)
|
|
433
|
-
pi.appendEntry(REPORT_ENTRY_TYPE, createAutoUpdateResultReport({ record }))
|
|
434
|
-
return { autoUpdateRecord: record }
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
setPackageManagerWidget(ctx, {
|
|
438
|
-
mode: "installing",
|
|
439
|
-
packages: availableUpdates.length,
|
|
440
|
-
})
|
|
441
|
-
|
|
442
|
-
const result = await pi.exec("pi", [...UPDATE_COMMAND], {
|
|
443
|
-
cwd: ctx.cwd,
|
|
444
|
-
signal: ctx.signal,
|
|
445
|
-
})
|
|
446
|
-
const output = getExecDisplayOutput(result)
|
|
447
|
-
|
|
448
|
-
if (result.code === 0) {
|
|
449
|
-
const record = createAutoUpdateRecord({
|
|
450
|
-
startedAtUtc,
|
|
451
|
-
endedAtUtc: new Date().toISOString(),
|
|
452
|
-
outcome: "succeeded",
|
|
453
|
-
packagesUpdated: availableUpdates.length,
|
|
454
|
-
})
|
|
455
|
-
|
|
456
|
-
pi.appendEntry(AUTO_UPDATE_RECORD_ENTRY_TYPE, record)
|
|
457
|
-
pi.appendEntry(
|
|
458
|
-
REPORT_ENTRY_TYPE,
|
|
459
|
-
createAutoUpdateResultReport({
|
|
460
|
-
record,
|
|
461
|
-
output,
|
|
462
|
-
reloadAfterSeconds: RELOAD_COUNTDOWN_SECONDS,
|
|
463
|
-
}),
|
|
464
|
-
)
|
|
465
|
-
await runReloadCountdown(ctx)
|
|
466
|
-
clearPackageManagerWidget(ctx)
|
|
467
|
-
shouldClearWidget = false
|
|
468
|
-
await ctx.reload()
|
|
469
|
-
return { autoUpdateRecord: record }
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
const record = createAutoUpdateRecord({
|
|
473
|
-
startedAtUtc,
|
|
474
|
-
endedAtUtc: new Date().toISOString(),
|
|
475
|
-
outcome: "failed",
|
|
476
|
-
packagesUpdated: 0,
|
|
477
|
-
reason: getExecFailureDetail(result),
|
|
478
|
-
})
|
|
479
|
-
|
|
480
|
-
pi.appendEntry(AUTO_UPDATE_RECORD_ENTRY_TYPE, record)
|
|
481
|
-
pi.appendEntry(
|
|
482
|
-
REPORT_ENTRY_TYPE,
|
|
483
|
-
createAutoUpdateResultReport({ record, output }),
|
|
484
|
-
)
|
|
485
|
-
|
|
486
|
-
if (options.startupTriggered && ctx.hasUI) {
|
|
487
|
-
ctx.ui.notify(
|
|
488
|
-
"Pi Package Manager automatic startup update failed. See transcript for details.",
|
|
489
|
-
"error",
|
|
490
|
-
)
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
return { autoUpdateRecord: record }
|
|
494
|
-
} catch (error) {
|
|
495
|
-
const record = createAutoUpdateRecord({
|
|
496
|
-
startedAtUtc,
|
|
497
|
-
endedAtUtc: new Date().toISOString(),
|
|
498
|
-
outcome: "failed",
|
|
499
|
-
packagesUpdated: 0,
|
|
500
|
-
reason: getErrorMessage(error),
|
|
501
|
-
})
|
|
502
|
-
|
|
503
|
-
pi.appendEntry(AUTO_UPDATE_RECORD_ENTRY_TYPE, record)
|
|
504
|
-
pi.appendEntry(REPORT_ENTRY_TYPE, createAutoUpdateResultReport({ record }))
|
|
505
|
-
|
|
506
|
-
if (options.startupTriggered && ctx.hasUI) {
|
|
507
|
-
ctx.ui.notify(
|
|
508
|
-
"Pi Package Manager automatic startup update failed. See transcript for details.",
|
|
509
|
-
"error",
|
|
510
|
-
)
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
return { autoUpdateRecord: record }
|
|
514
|
-
} finally {
|
|
515
|
-
if (shouldClearWidget) {
|
|
516
|
-
clearPackageManagerWidget(ctx)
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
async function runReloadCountdown(
|
|
522
|
-
ctx: ExtensionContext,
|
|
523
|
-
seconds = RELOAD_COUNTDOWN_SECONDS,
|
|
524
|
-
): Promise<void> {
|
|
525
|
-
for (let remaining = seconds; remaining >= 1; remaining--) {
|
|
526
|
-
setPackageManagerWidget(ctx, {
|
|
527
|
-
mode: "countdown",
|
|
528
|
-
secondsRemaining: remaining,
|
|
529
|
-
})
|
|
530
|
-
|
|
531
|
-
await delay(1000)
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
async function checkForAvailableUpdates(ctx: ExtensionContext): Promise<string[]> {
|
|
536
|
-
const agentDir = getAgentDir()
|
|
537
|
-
const settingsManager = SettingsManager.create(ctx.cwd, agentDir, {
|
|
538
|
-
projectTrusted: ctx.isProjectTrusted(),
|
|
539
|
-
})
|
|
540
|
-
const packageManager = new DefaultPackageManager({
|
|
541
|
-
cwd: ctx.cwd,
|
|
542
|
-
agentDir,
|
|
543
|
-
settingsManager,
|
|
544
|
-
})
|
|
545
|
-
const updates = await packageManager.checkForAvailableUpdates()
|
|
546
|
-
|
|
547
|
-
return updates
|
|
548
|
-
.map((update) => update.displayName)
|
|
549
|
-
.sort((left, right) => left.localeCompare(right))
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function setPackageManagerWidget(ctx: ExtensionContext, state: WidgetState): void {
|
|
553
|
-
if (ctx.mode !== "tui") {
|
|
554
|
-
return
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
ctx.ui.setWidget(
|
|
558
|
-
PACKAGE_MANAGER_WIDGET_KEY,
|
|
559
|
-
(_tui, theme) => {
|
|
560
|
-
const background =
|
|
561
|
-
state.mode === "countdown"
|
|
562
|
-
? (text: string) => theme.bg("toolSuccessBg", text)
|
|
563
|
-
: (text: string) => theme.bg("toolPendingBg", text)
|
|
564
|
-
const box = new Box(1, 1, background)
|
|
565
|
-
const titleColor = state.mode === "countdown" ? "success" : "accent"
|
|
566
|
-
const bodyLines = createAutomaticUpdateWidgetLines(state)
|
|
567
|
-
|
|
568
|
-
box.addChild(
|
|
569
|
-
new Text(
|
|
570
|
-
`${theme.fg(titleColor, "●")} ${theme.bold(theme.fg("customMessageLabel", PACKAGE_MANAGER_TITLE))}`,
|
|
571
|
-
0,
|
|
572
|
-
0,
|
|
573
|
-
),
|
|
574
|
-
)
|
|
575
|
-
box.addChild(
|
|
576
|
-
new Text(
|
|
577
|
-
bodyLines
|
|
578
|
-
.map((line, index) =>
|
|
579
|
-
theme.fg(
|
|
580
|
-
index === bodyLines.length - 1 ? "dim" : "text",
|
|
581
|
-
line,
|
|
582
|
-
),
|
|
583
|
-
)
|
|
584
|
-
.join("\n"),
|
|
585
|
-
0,
|
|
586
|
-
0,
|
|
587
|
-
),
|
|
588
|
-
)
|
|
589
|
-
|
|
590
|
-
return box
|
|
591
|
-
},
|
|
592
|
-
{ placement: "aboveEditor" },
|
|
593
|
-
)
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
function clearPackageManagerWidget(ctx: ExtensionContext): void {
|
|
597
|
-
if (ctx.mode !== "tui") {
|
|
598
|
-
return
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
ctx.ui.setWidget(PACKAGE_MANAGER_WIDGET_KEY, undefined)
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function formatExpandHint(
|
|
605
|
-
theme: { fg(token: string, text: string): string },
|
|
606
|
-
description: string,
|
|
607
|
-
): string {
|
|
608
|
-
const expandKey = keyText("app.tools.expand") || "Ctrl+O"
|
|
609
|
-
|
|
610
|
-
return `${theme.fg("dim", expandKey)}${theme.fg("muted", ` ${description}`)}`
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
function formatReportOutput(
|
|
614
|
-
output: string,
|
|
615
|
-
expanded: boolean,
|
|
616
|
-
): { text: string; truncated: boolean } {
|
|
617
|
-
const normalizedLines = output.trim().split(/\r?\n/)
|
|
618
|
-
|
|
619
|
-
if (expanded || normalizedLines.length <= OUTPUT_PREVIEW_LINE_COUNT) {
|
|
620
|
-
return {
|
|
621
|
-
text: normalizedLines.join("\n"),
|
|
622
|
-
truncated: false,
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
return {
|
|
627
|
-
text: normalizedLines.slice(0, OUTPUT_PREVIEW_LINE_COUNT).join("\n"),
|
|
628
|
-
truncated: true,
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
function getExecDisplayOutput(result: ExecResult): string | undefined {
|
|
633
|
-
const stdout = result.stdout.trim()
|
|
634
|
-
const stderr = result.stderr.trim()
|
|
635
|
-
const sections: string[] = []
|
|
636
|
-
|
|
637
|
-
if (stdout) {
|
|
638
|
-
sections.push(stdout)
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
if (stderr) {
|
|
642
|
-
sections.push(`[stderr]\n${stderr}`)
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
return sections.length > 0 ? sections.join("\n\n") : undefined
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
function isAutoUpdateRecordEntry(
|
|
649
|
-
entry: SessionEntry,
|
|
650
|
-
): entry is CustomEntry<AutoUpdateRecord> {
|
|
651
|
-
return (
|
|
652
|
-
entry.type === "custom" &&
|
|
653
|
-
entry.customType === AUTO_UPDATE_RECORD_ENTRY_TYPE &&
|
|
654
|
-
isAutoUpdateRecord(entry.data)
|
|
655
|
-
)
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
function isAutoUpdateRecord(value: unknown): value is AutoUpdateRecord {
|
|
659
|
-
if (!value || typeof value !== "object") {
|
|
660
|
-
return false
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
const candidate = value as Partial<AutoUpdateRecord>
|
|
664
|
-
return (
|
|
665
|
-
typeof candidate.startedAtUtc === "string" &&
|
|
666
|
-
typeof candidate.endedAtUtc === "string" &&
|
|
667
|
-
(candidate.outcome === "succeeded" ||
|
|
668
|
-
candidate.outcome === "failed" ||
|
|
669
|
-
candidate.outcome === "skipped") &&
|
|
670
|
-
typeof candidate.packagesUpdated === "number" &&
|
|
671
|
-
(candidate.reason === undefined || typeof candidate.reason === "string")
|
|
672
|
-
)
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
function getExecFailureDetail(result: ExecResult): string {
|
|
676
|
-
const stderr = result.stderr.trim()
|
|
677
|
-
const stdout = result.stdout.trim()
|
|
678
|
-
const detail = stderr || stdout || "Package update command failed."
|
|
679
|
-
|
|
680
|
-
return detail.length > 400 ? `${detail.slice(0, 397)}...` : detail
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
function getErrorMessage(error: unknown): string {
|
|
684
|
-
if (error instanceof Error) {
|
|
685
|
-
return error.message
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
return String(error)
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
function delay(milliseconds: number): Promise<void> {
|
|
692
|
-
return new Promise((resolve) => {
|
|
693
|
-
setTimeout(resolve, milliseconds)
|
|
694
|
-
})
|
|
86
|
+
export {
|
|
87
|
+
AUTO_UPDATE_RECORD_ENTRY_TYPE,
|
|
88
|
+
PACKAGE_MANAGER_TITLE,
|
|
89
|
+
REPORT_ENTRY_TYPE,
|
|
90
|
+
createAutomaticUpdateWidgetLines,
|
|
91
|
+
createAutoUpdateRecord,
|
|
92
|
+
createAutoUpdateResultReport,
|
|
93
|
+
createStatusReport,
|
|
94
|
+
formatStatusLines,
|
|
95
|
+
formatUtcTimestamp,
|
|
96
|
+
getLastAutoUpdateRecord,
|
|
97
|
+
isAutoUpdateRecord,
|
|
98
|
+
isAutoUpdateRecordEntry,
|
|
99
|
+
shouldAutoUpdateOnSessionStart,
|
|
100
|
+
}
|
|
101
|
+
export type {
|
|
102
|
+
AutoUpdateOutcome,
|
|
103
|
+
AutoUpdateRecord,
|
|
104
|
+
PackageManagerDeps,
|
|
105
|
+
PackageManagerReport,
|
|
106
|
+
PackageStatusSnapshot,
|
|
107
|
+
ReportTone,
|
|
108
|
+
WidgetState,
|
|
695
109
|
}
|