@gaosh3n/pi-package-manager 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/index.ts +76 -655
- package/internal/controller.ts +298 -0
- package/internal/model.ts +45 -0
- package/internal/records.ts +81 -0
- package/internal/reports.ts +396 -0
- package/internal/runtime.ts +69 -0
- package/package.json +2 -1
package/index.ts
CHANGED
|
@@ -1,175 +1,59 @@
|
|
|
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
|
+
createInstallResultReport,
|
|
29
|
+
createReportEntryRenderer,
|
|
30
|
+
createStatusReport,
|
|
31
|
+
formatStatusLines,
|
|
32
|
+
formatUtcTimestamp,
|
|
33
|
+
} from "./internal/reports.ts"
|
|
34
|
+
import {
|
|
35
|
+
defaultPackageManagerDeps,
|
|
36
|
+
type PackageManagerDeps,
|
|
37
|
+
} from "./internal/runtime.ts"
|
|
58
38
|
|
|
59
|
-
export default function (
|
|
60
|
-
|
|
39
|
+
export default function initPackageManager(
|
|
40
|
+
pi: ExtensionAPI,
|
|
41
|
+
deps: PackageManagerDeps = defaultPackageManagerDeps,
|
|
42
|
+
): void {
|
|
43
|
+
const controller = createPackageManagerController(pi, deps)
|
|
61
44
|
|
|
62
45
|
pi.registerEntryRenderer<PackageManagerReport>(
|
|
63
46
|
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
|
-
},
|
|
47
|
+
createReportEntryRenderer(),
|
|
154
48
|
)
|
|
155
49
|
|
|
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
|
-
})
|
|
50
|
+
pi.on("session_start", controller.onSessionStart)
|
|
167
51
|
|
|
168
52
|
pi.registerCommand("package-manager", {
|
|
169
53
|
description:
|
|
170
|
-
"Manage Pi
|
|
54
|
+
"Manage Pi packages (usage: /package-manager [status|update|install])",
|
|
171
55
|
getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {
|
|
172
|
-
const items = ["status", "update"].map((value) => ({
|
|
56
|
+
const items = ["status", "update", "install"].map((value) => ({
|
|
173
57
|
value,
|
|
174
58
|
label: value,
|
|
175
59
|
}))
|
|
@@ -184,512 +68,49 @@ export default function (pi: ExtensionAPI) {
|
|
|
184
68
|
const subcommand = tokens[0] ?? "status"
|
|
185
69
|
|
|
186
70
|
if (subcommand === "status") {
|
|
187
|
-
await
|
|
71
|
+
await controller.handleStatus(ctx)
|
|
188
72
|
return
|
|
189
73
|
}
|
|
190
74
|
|
|
191
75
|
if (subcommand === "update") {
|
|
192
|
-
|
|
76
|
+
await controller.handleUpdate(ctx, {
|
|
193
77
|
startupTriggered: tokens.includes("--startup"),
|
|
194
78
|
})
|
|
79
|
+
return
|
|
80
|
+
}
|
|
195
81
|
|
|
196
|
-
|
|
82
|
+
if (subcommand === "install") {
|
|
83
|
+
await controller.handleInstall(ctx)
|
|
197
84
|
return
|
|
198
85
|
}
|
|
199
86
|
|
|
200
|
-
ctx.ui.notify("Usage: /package-manager [status|update]", "warning")
|
|
87
|
+
ctx.ui.notify("Usage: /package-manager [status|update|install]", "warning")
|
|
201
88
|
},
|
|
202
89
|
})
|
|
203
90
|
}
|
|
204
91
|
|
|
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
|
-
|
|
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
|
-
})
|
|
92
|
+
export {
|
|
93
|
+
AUTO_UPDATE_RECORD_ENTRY_TYPE,
|
|
94
|
+
PACKAGE_MANAGER_TITLE,
|
|
95
|
+
REPORT_ENTRY_TYPE,
|
|
96
|
+
createAutomaticUpdateWidgetLines,
|
|
97
|
+
createAutoUpdateRecord,
|
|
98
|
+
createAutoUpdateResultReport,
|
|
99
|
+
createInstallResultReport,
|
|
100
|
+
createStatusReport,
|
|
101
|
+
formatStatusLines,
|
|
102
|
+
formatUtcTimestamp,
|
|
103
|
+
getLastAutoUpdateRecord,
|
|
104
|
+
isAutoUpdateRecord,
|
|
105
|
+
isAutoUpdateRecordEntry,
|
|
106
|
+
shouldAutoUpdateOnSessionStart,
|
|
107
|
+
}
|
|
108
|
+
export type {
|
|
109
|
+
AutoUpdateOutcome,
|
|
110
|
+
AutoUpdateRecord,
|
|
111
|
+
PackageManagerDeps,
|
|
112
|
+
PackageManagerReport,
|
|
113
|
+
PackageStatusSnapshot,
|
|
114
|
+
ReportTone,
|
|
115
|
+
WidgetState,
|
|
695
116
|
}
|