@gaosh3n/pi-package-manager 0.1.1 → 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 CHANGED
@@ -8,6 +8,7 @@
8
8
  - a visible progress UI while Pi checks or updates packages
9
9
  - `/package-manager status` to see whether updates are available
10
10
  - `/package-manager update` to run the update flow on demand
11
+ - `/package-manager install` to install a Pi package from an entered source
11
12
  - a final result card in Pi so you can review the latest outcome
12
13
  - automatic Pi reload after a successful startup update
13
14
 
@@ -44,3 +45,13 @@ Run:
44
45
  ```
45
46
 
46
47
  Pi will run the update flow for you, show live progress, and record the final result in the transcript.
48
+
49
+ ### Install a package
50
+
51
+ Run:
52
+
53
+ ```text
54
+ /package-manager install
55
+ ```
56
+
57
+ Pi will prompt you for a package source such as `npm:@foo/bar` or `git:github.com/user/repo`, run the native `pi install ...` flow, and show a final result card. After a successful install, run `/reload` to activate the installed package resources.
package/index.ts CHANGED
@@ -25,6 +25,7 @@ import {
25
25
  import {
26
26
  createAutomaticUpdateWidgetLines,
27
27
  createAutoUpdateResultReport,
28
+ createInstallResultReport,
28
29
  createReportEntryRenderer,
29
30
  createStatusReport,
30
31
  formatStatusLines,
@@ -50,9 +51,9 @@ export default function initPackageManager(
50
51
 
51
52
  pi.registerCommand("package-manager", {
52
53
  description:
53
- "Manage Pi package updates (usage: /package-manager [status|update])",
54
+ "Manage Pi packages (usage: /package-manager [status|update|install])",
54
55
  getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {
55
- const items = ["status", "update"].map((value) => ({
56
+ const items = ["status", "update", "install"].map((value) => ({
56
57
  value,
57
58
  label: value,
58
59
  }))
@@ -78,7 +79,12 @@ export default function initPackageManager(
78
79
  return
79
80
  }
80
81
 
81
- ctx.ui.notify("Usage: /package-manager [status|update]", "warning")
82
+ if (subcommand === "install") {
83
+ await controller.handleInstall(ctx)
84
+ return
85
+ }
86
+
87
+ ctx.ui.notify("Usage: /package-manager [status|update|install]", "warning")
82
88
  },
83
89
  })
84
90
  }
@@ -90,6 +96,7 @@ export {
90
96
  createAutomaticUpdateWidgetLines,
91
97
  createAutoUpdateRecord,
92
98
  createAutoUpdateResultReport,
99
+ createInstallResultReport,
93
100
  createStatusReport,
94
101
  formatStatusLines,
95
102
  formatUtcTimestamp,
@@ -14,6 +14,7 @@ import {
14
14
  import {
15
15
  clearPackageManagerWidget,
16
16
  createAutoUpdateResultReport,
17
+ createInstallResultReport,
17
18
  createStatusErrorReport,
18
19
  createStatusReport,
19
20
  getExecDisplayOutput,
@@ -30,6 +31,7 @@ export function createPackageManagerController(
30
31
  onSessionStart,
31
32
  handleStatus,
32
33
  handleUpdate,
34
+ handleInstall,
33
35
  }
34
36
 
35
37
  async function onSessionStart(
@@ -165,6 +167,79 @@ export function createPackageManagerController(
165
167
  }
166
168
  }
167
169
 
170
+ async function handleInstall(ctx: ExtensionCommandContext): Promise<void> {
171
+ if (!ctx.hasUI) {
172
+ ctx.ui.notify(
173
+ "/package-manager install requires dialog-capable UI.",
174
+ "warning",
175
+ )
176
+ return
177
+ }
178
+
179
+ const source = (
180
+ await ctx.ui.input(
181
+ "Install Pi Package",
182
+ "npm:@scope/pkg or git:github.com/user/repo",
183
+ )
184
+ )?.trim()
185
+
186
+ if (source === undefined) {
187
+ return
188
+ }
189
+
190
+ if (!source) {
191
+ ctx.ui.notify("Package source is required.", "warning")
192
+ return
193
+ }
194
+
195
+ const startedAtUtc = deps.nowIso()
196
+ setPackageManagerWidget(ctx, { mode: "package-installing", source })
197
+
198
+ try {
199
+ const result = await deps.runNativeInstall(pi, ctx, source)
200
+ const output = getExecDisplayOutput(result)
201
+
202
+ if (result.code === 0) {
203
+ pi.appendEntry(
204
+ REPORT_ENTRY_TYPE,
205
+ createInstallResultReport({
206
+ startedAtUtc,
207
+ endedAtUtc: deps.nowIso(),
208
+ source,
209
+ outcome: "succeeded",
210
+ output,
211
+ }),
212
+ )
213
+ return
214
+ }
215
+
216
+ pi.appendEntry(
217
+ REPORT_ENTRY_TYPE,
218
+ createInstallResultReport({
219
+ startedAtUtc,
220
+ endedAtUtc: deps.nowIso(),
221
+ source,
222
+ outcome: "failed",
223
+ output,
224
+ reason: getExecFailureDetail(result),
225
+ }),
226
+ )
227
+ } catch (error) {
228
+ pi.appendEntry(
229
+ REPORT_ENTRY_TYPE,
230
+ createInstallResultReport({
231
+ startedAtUtc,
232
+ endedAtUtc: deps.nowIso(),
233
+ source,
234
+ outcome: "failed",
235
+ reason: getErrorMessage(error),
236
+ }),
237
+ )
238
+ } finally {
239
+ clearPackageManagerWidget(ctx)
240
+ }
241
+ }
242
+
168
243
  function appendSkippedResult(startedAtUtc: string, reason: string): void {
169
244
  const record = createAutoUpdateRecord({
170
245
  startedAtUtc,
package/internal/model.ts CHANGED
@@ -5,8 +5,10 @@ export const PACKAGE_MANAGER_WIDGET_KEY = "pi-package-manager"
5
5
  export const RELOAD_COUNTDOWN_SECONDS = 5
6
6
  export const OUTPUT_PREVIEW_LINE_COUNT = 8
7
7
  export const UPDATE_COMMAND = ["update", "--extensions"] as const
8
+ export const INSTALL_COMMAND = ["install"] as const
8
9
 
9
10
  export type AutoUpdateOutcome = "succeeded" | "failed" | "skipped"
11
+ export type InstallOutcome = "succeeded" | "failed"
10
12
  export type ReportTone = "info" | "success" | "warning" | "error"
11
13
 
12
14
  export type WidgetState =
@@ -14,6 +16,7 @@ export type WidgetState =
14
16
  | { mode: "checking" }
15
17
  | { mode: "installing"; packages: number }
16
18
  | { mode: "countdown"; secondsRemaining: number }
19
+ | { mode: "package-installing"; source: string }
17
20
 
18
21
  export interface AutoUpdateRecord {
19
22
  startedAtUtc: string
@@ -31,6 +34,7 @@ export interface PackageManagerReport {
31
34
  lineTone?: "default" | "dim"
32
35
  output?: string
33
36
  outputLabel?: string
37
+ outputDescription?: string
34
38
  outputTone?: "default" | "dim"
35
39
  hideOutputWhenCollapsed?: boolean
36
40
  }
@@ -11,6 +11,7 @@ import {
11
11
  PACKAGE_MANAGER_WIDGET_KEY,
12
12
  type AutoUpdateOutcome,
13
13
  type AutoUpdateRecord,
14
+ type InstallOutcome,
14
15
  type PackageManagerReport,
15
16
  type PackageStatusSnapshot,
16
17
  type WidgetState,
@@ -73,7 +74,10 @@ export function createReportEntryRenderer() {
73
74
  if (!expanded && report.hideOutputWhenCollapsed) {
74
75
  box.addChild(
75
76
  new Text(
76
- formatExpandHint(theme, "to expand to see update output."),
77
+ formatExpandHint(
78
+ theme,
79
+ `to expand to see ${report.outputDescription ?? "output"}.`,
80
+ ),
77
81
  0,
78
82
  0,
79
83
  ),
@@ -83,10 +87,7 @@ export function createReportEntryRenderer() {
83
87
 
84
88
  box.addChild(
85
89
  new Text(
86
- theme.fg(
87
- outputTextTone,
88
- report.outputLabel ?? "Update output:",
89
- ),
90
+ theme.fg(outputTextTone, report.outputLabel ?? "Output:"),
90
91
  0,
91
92
  0,
92
93
  ),
@@ -98,7 +99,7 @@ export function createReportEntryRenderer() {
98
99
  new Text(
99
100
  formatExpandHint(
100
101
  theme,
101
- "to expand to view the full update output.",
102
+ `to expand to view the full ${report.outputDescription ?? "output"}.`,
102
103
  ),
103
104
  0,
104
105
  0,
@@ -203,6 +204,43 @@ export function createAutoUpdateResultReport(input: {
203
204
  lines,
204
205
  lineTone: "dim",
205
206
  output: input.output ?? input.record.reason,
207
+ outputLabel: "Update output:",
208
+ outputDescription: "update output",
209
+ outputTone: "dim",
210
+ hideOutputWhenCollapsed: true,
211
+ }
212
+ }
213
+
214
+ export function createInstallResultReport(input: {
215
+ startedAtUtc: string
216
+ endedAtUtc: string
217
+ source: string
218
+ outcome: InstallOutcome
219
+ output?: string
220
+ reason?: string
221
+ }): PackageManagerReport {
222
+ return {
223
+ title: PACKAGE_MANAGER_TITLE,
224
+ headline:
225
+ input.outcome === "succeeded"
226
+ ? "Pi package install completed."
227
+ : "Pi package install failed.",
228
+ tone: input.outcome === "succeeded" ? "success" : "error",
229
+ lines: [
230
+ `Start: ${formatUtcTimestamp(input.startedAtUtc)}`,
231
+ `End: ${formatUtcTimestamp(input.endedAtUtc)}`,
232
+ `Result: ${input.outcome}`,
233
+ `Package source: ${input.source}`,
234
+ ...(input.outcome === "succeeded"
235
+ ? ["Run /reload to activate installed package resources."]
236
+ : []),
237
+ ],
238
+ lineTone: "dim",
239
+ output: input.output ?? input.reason,
240
+ outputLabel:
241
+ input.outcome === "succeeded" ? "Install output:" : "Error detail:",
242
+ outputDescription:
243
+ input.outcome === "succeeded" ? "install output" : "error detail",
206
244
  outputTone: "dim",
207
245
  hideOutputWhenCollapsed: true,
208
246
  }
@@ -227,6 +265,13 @@ export function createAutomaticUpdateWidgetLines(state: WidgetState): string[] {
227
265
  ]
228
266
  }
229
267
 
268
+ if (state.mode === "package-installing") {
269
+ return [
270
+ "Pi package install in progress.",
271
+ `Installing package from ${state.source}...`,
272
+ ]
273
+ }
274
+
230
275
  return [
231
276
  formatAutomaticUpdateHeadline("succeeded"),
232
277
  `Reloading in ${state.secondsRemaining} second${state.secondsRemaining === 1 ? "" : "s"} to activate updated package resources.`,
@@ -8,7 +8,7 @@ import {
8
8
  type ExtensionContext,
9
9
  } from "@earendil-works/pi-coding-agent"
10
10
 
11
- import { UPDATE_COMMAND } from "./model.ts"
11
+ import { INSTALL_COMMAND, UPDATE_COMMAND } from "./model.ts"
12
12
 
13
13
  export interface PackageManagerDeps {
14
14
  nowIso(): string
@@ -19,6 +19,11 @@ export interface PackageManagerDeps {
19
19
  pi: Pick<ExtensionAPI, "exec">,
20
20
  ctx: ExtensionCommandContext,
21
21
  ): Promise<ExecResult>
22
+ runNativeInstall(
23
+ pi: Pick<ExtensionAPI, "exec">,
24
+ ctx: ExtensionCommandContext,
25
+ source: string,
26
+ ): Promise<ExecResult>
22
27
  }
23
28
 
24
29
  export const defaultPackageManagerDeps: PackageManagerDeps = {
@@ -51,4 +56,14 @@ export const defaultPackageManagerDeps: PackageManagerDeps = {
51
56
  signal: ctx.signal,
52
57
  })
53
58
  },
59
+ runNativeInstall(
60
+ pi: Pick<ExtensionAPI, "exec">,
61
+ ctx: ExtensionCommandContext,
62
+ source: string,
63
+ ) {
64
+ return pi.exec("pi", [...INSTALL_COMMAND, source], {
65
+ cwd: ctx.cwd,
66
+ signal: ctx.signal,
67
+ })
68
+ },
54
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaosh3n/pi-package-manager",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Package Manager for Pi",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.23.0",