@magnitudedev/pi-extension 0.0.1-alpha.4 → 0.0.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
@@ -1,95 +1,63 @@
1
1
  # Magnitude for Pi
2
2
 
3
- This Pi package adds Magnitude model management commands and live local-inference progress to Pi's
4
- built-in working row above the editor.
3
+ Run Pi on local models. Free, private, and offline.
5
4
 
6
- Requires Pi 0.83.0 or newer. Install from your terminal:
5
+ Magnitude runs the best local models for your hardware, plugged into the agent you already use.
6
+ It profiles your machine, recommends the models that fit, then downloads, tunes, and runs them.
7
7
 
8
- ```sh
9
- pi install npm:@magnitudedev/pi-extension
10
- ```
8
+ ## Get started
11
9
 
12
- On the next fresh interactive Pi launch, Magnitude asks **“Set up local models with Magnitude now?”**
13
- Yes opens Magnitude's actual graphical setup in the same terminal, including its model rankings,
14
- radar, downloads, and loading progress. It connects Pi automatically at the final step, then returns
15
- to the same Pi conversation with the selected model active. No agent prompt or cloud credentials
16
- are needed. If Magnitude is missing, accepting setup installs the CLI with
17
- `npm install --global @magnitudedev/cli` before opening onboarding. Pi stays visible with its native
18
- spinner: “Installing Magnitude…”. Installer output is suppressed; failures
19
- show a concise diagnostic. Escape cancels preparation, and `/magnitude-setup` retries.
20
- An existing CLI is preserved. The extension invokes `magnitude setup --host pi`; `--host` is hidden from help.
21
- If an older CLI does not recognize that option, update it explicitly and retry; it is never silently
22
- replaced.
23
-
24
- No or Escape leaves “You can set up local models anytime with `/magnitude-setup`.” in the conversation.
25
- The offer is remembered per Pi profile across restarts, reloads, and package updates. Existing
26
- Magnitude model configurations, non-interactive modes, conversations, and startup prompts are left
27
- alone. Run `/magnitude-setup` whenever you want to start onboarding yourself.
28
-
29
- The package includes the Magnitude usage skill; an already-loaded skill of that name takes
30
- precedence without a collision warning. `--no-skills` disables this fallback too. No separate CLI
31
- installation is needed. npm must be available with a writable global prefix, as for a normal npm
32
- CLI installation. Merely loading the extension does not install software.
33
-
34
- If you already have Magnitude and a local model, connect directly:
10
+ Install the extension:
35
11
 
36
12
  ```sh
37
- magnitude connections add pi
13
+ pi install npm:@magnitudedev/pi-extension
38
14
  ```
39
15
 
40
- Restart Pi or run `/reload` after connecting. `PI_CODING_AGENT_DIR`, if set, is honored.
41
- Installing the package directly does not configure provider models,
42
- install the Magnitude CLI, or start its service; the connection command
43
- does all connection configuration and can be run safely after a standalone package install.
16
+ Open Pi and accept the setup offer. Magnitude walks you through the best models for your machine,
17
+ downloads your selection, and connects it to Pi. You don't need Magnitude installed beforehand.
44
18
 
45
- The extension bundles Magnitude's private SDK. Model status, loading, and stopping use the existing
46
- RPC endpoint at port 10100. When necessary the SDK runs `magnitude service start`; it does not use
47
- CLI output as a model API. If `/stop-model` encounters a protocol mismatch, the
48
- extension runs `magnitude connections sync pi` once to install the exact plugin version selected by
49
- the installed CLI, then reloads Pi. Retry your model command after reload; it is never replayed
50
- automatically. Failed sync reports the error without reloading or looping. Manually owned incompatible
51
- packages are not replaced. Autocomplete and inference callbacks never trigger an update. During
52
- requests to the `magnitude` provider, it opts into Magnitude progress events and uses Pi's working
53
- row for model loading, prefill, and active-work timing. When Pi successfully completes a run, a one-line summary
54
- immediately above the editor shows the model's display name, total Pi agent-run time, first-token
55
- latency, and token-weighted generation throughput. Pi extensions execute with your user permissions.
19
+ You can also start setup anytime with `/magnitude-setup`.
56
20
 
57
- Commands:
21
+ Requires Pi 0.83.0 or newer. Magnitude supports macOS and Linux, and Windows through WSL.
22
+ An internet connection is needed for installation and model downloads; after that, you can work offline.
58
23
 
59
- - `/magnitude-setup` — open Magnitude's graphical model setup inside Pi
60
- - `/stop-model` — stop the active model
24
+ ## Why Magnitude?
61
25
 
62
- Installed Magnitude models appear in Pi's built-in `/model` selector. To discover, compare, install,
63
- or remove models from the Magnitude catalog, ask the agent; the connection installs Magnitude's
64
- agent skill and the agent uses the `magnitude catalog` and `magnitude connections` commands.
26
+ - **Free to run:** no token costs, API keys, or rate limits
27
+ - **Private and offline:** local model requests stay on your machine
28
+ - **Recommends what fits:** the best models for your hardware, with estimated tok/s
29
+ - **Tuned end to end:** inference settings chosen for your machine
30
+ - **Models on demand:** loaded when Pi needs them, unloaded when idle or memory gets tight
31
+ - **Live performance stats:** see model loading, prompt processing, and time spent working
65
32
 
66
- Set `MAGNITUDE_CLI` to an alternate Magnitude executable path when developing or testing the package.
67
- `magnitude connections add pi` selects an exact compatible package and verifies its bundled contents.
68
- The SDK is a build-time workspace dependency, not a separately published or installed package.
33
+ After each completed run, a summary shows the model, time worked, time to first token, and tokens per
34
+ second. It stays in your chat history so you can refer back to it.
69
35
 
70
- From a Magnitude source checkout, run the complete local connection and TUI flow with:
36
+ ## Using your models
71
37
 
72
- ```sh
73
- bun run dev:pi
38
+ Choose an installed Magnitude model from Pi's `/model` selector. To discover or install more models,
39
+ ask your agent:
40
+
41
+ ```text
42
+ Use Magnitude to recommend the best local models for my hardware and help me install one.
74
43
  ```
75
44
 
76
- This builds and installs the package from `integrations/pi` into an isolated Pi configuration,
77
- with temporary connection receipts and agent skill. Pi runs in the caller's working directory.
78
- It uses the normal connection service,
79
- builds and runs the checkout's inference runtime,
80
- and keeps the current source CLI available until Pi exits. Exiting Pi stops the development runtime
81
- and restores the service state that existed before launch. The launcher inherits the current
82
- environment; it does not start or configure tracing.
45
+ Use `/magnitude-setup` to run guided setup again, or `/stop-model` to unload the active model and
46
+ free its memory.
47
+
48
+ ### Already using Magnitude?
83
49
 
84
- To test the first-run setup offer with no preconfigured Magnitude provider:
50
+ Connect your installed models to Pi:
85
51
 
86
52
  ```sh
87
- bun run dev:pi --setup
53
+ magnitude connections add pi
88
54
  ```
89
55
 
90
- This installs only the checkout's package into the temporary Pi profile. Accept the setup offer,
91
- choose a model, and return to Pi to chat. Both development modes keep connection files and skills
92
- inside the temporary profile, and do not change login-startup registration. They share the machine's
93
- real model storage and inference service: downloads consume disk space and selected models use
94
- memory. The launcher restores the prior managed service after Pi exits. Ordinary installed setup
95
- does enable login startup.
56
+ Then restart Pi or run `/reload`.
57
+
58
+ ## Learn more
59
+
60
+ - [Documentation](https://docs.magnitude.dev)
61
+ - [GitHub](https://github.com/magnitudedev/magnitude)
62
+ - [Discord](https://discord.gg/EHt48pPWdC)
63
+ - [Report an issue](https://github.com/magnitudedev/magnitude/issues)
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@magnitudedev/pi-extension",
3
- "version": "0.0.1-alpha.4",
3
+ "version": "0.0.2",
4
4
  "rpcVersion": 1,
5
- "contentFingerprint": "d8a654678e1244761397d9bb6f30765c9d2a9c7813fdea7b024f4076dab494ef",
5
+ "contentFingerprint": "eb4eac2538369de2dabb2c65f7ac94bd518c5de6bfc26d12af33cad167a6748a",
6
6
  "files": {
7
- "README.md": "26fa7e5b56c9b171946b7efec036703e258ab51607d1e20f00c45d6e96133a29",
8
- "dist/magnitude.js": "b21c625dd7e2c0f00c6992845dad09cd753b4e85cf5b258d334ba4a4c6d065c3",
7
+ "README.md": "c6a121075d1ccacc4db063dd8ff1a336a04f77a7f444acba12e1628df32fa3f5",
8
+ "dist/magnitude.js": "d741bd337e8ba28984303fcfa059e106afd331adce214c276f4478b3978b00d7",
9
9
  "dist/skills/magnitude/SKILL.md": "37de67f0c7b831415f6298c8785b7ae97d5cfe454dc778651d96114840d2ace2"
10
10
  }
11
11
  }
package/dist/magnitude.js CHANGED
@@ -6238,9 +6238,16 @@ var makeObservingFetch = (fetchImplementation, begin, scope) => {
6238
6238
  };
6239
6239
 
6240
6240
  // extensions/progress.ts
6241
- import { Text } from "@earendil-works/pi-tui";
6242
6241
  import { Data as Data3, Effect as Effect15, Fiber as Fiber3, Option as Option17, Queue, Schema as Schema55 } from "effect";
6243
- var MAGNITUDE_SUMMARY_WIDGET_KEY = "magnitude-inference-summary";
6242
+ var MAGNITUDE_SUMMARY_ENTRY_TYPE = "magnitude-inference-summary";
6243
+ var Milliseconds = Schema55.Number.pipe(Schema55.finite(), Schema55.nonNegative());
6244
+ var ProgressSummary = Schema55.Struct({
6245
+ modelName: Schema55.String,
6246
+ elapsedMs: Milliseconds,
6247
+ ttftMs: Milliseconds,
6248
+ generatedTokens: Schema55.Number.pipe(Schema55.int(), Schema55.nonNegative()),
6249
+ decodeMs: Milliseconds
6250
+ });
6244
6251
  var RequestId = Schema55.Number.pipe(Schema55.int(), Schema55.brand("ProgressRequestId"));
6245
6252
 
6246
6253
  class Observing extends Data3.TaggedClass("Observing") {
@@ -6276,27 +6283,28 @@ var runMachine = defineFSM({ Idle: Idle2, Working, Settled, Disposed }, {
6276
6283
  });
6277
6284
  var seconds = (ms) => `${(Math.max(0, ms) / 1000).toFixed(1)}s`;
6278
6285
  var count = (n) => n < 1000 ? String(Math.round(n)) : `${(n / (n < 1e6 ? 1000 : 1e6)).toFixed(1).replace(/\.0$/, "")}${n < 1e6 ? "k" : "m"}`;
6279
- var duration = (ms) => {
6286
+ var formatElapsed = (ms) => {
6280
6287
  const s = Math.floor(Math.max(0, ms) / 1000);
6281
- return s === 0 ? "<1 second" : s < 60 ? `${s} second${s === 1 ? "" : "s"}` : s % 60 === 0 ? `${s / 60} minute${s === 60 ? "" : "s"}` : `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
6288
+ return s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${s % 60}s`;
6282
6289
  };
6290
+ var formatSummary = (summary) => `● ${summary.modelName} worked for ${summary.elapsedMs < 1000 ? "<1s" : formatElapsed(summary.elapsedMs)}` + ` · ${seconds(summary.ttftMs)} TTFT` + (summary.decodeMs > 0 && summary.generatedTokens > 0 ? ` · ${(summary.generatedTokens * 1000 / summary.decodeMs).toFixed(1)} tok/s` : "");
6283
6291
  var formatLiveProgress = ({ progress, modelName, startedAt }, now) => {
6284
6292
  switch (progress.phase) {
6285
6293
  case "queued":
6286
6294
  case "preparing":
6287
6295
  return;
6288
6296
  case "model_loading":
6289
- return `Loading ${modelName} into memory · ${Math.round(Math.max(0, Math.min(1, progress.fraction)) * 100)}% · ${seconds(now - startedAt)}`;
6297
+ return `Loading ${modelName} into memory · ${Math.round(Math.max(0, Math.min(1, progress.fraction)) * 100)}% · ${formatElapsed(now - startedAt)}`;
6290
6298
  case "generating":
6291
- return `Working · ${seconds(now - startedAt)}`;
6299
+ return `Working · ${formatElapsed(now - startedAt)}`;
6292
6300
  case "prefill": {
6293
6301
  const cached = Math.min(progress.cached_tokens, progress.total_tokens);
6294
6302
  const completed = Math.min(Math.max(progress.completed_tokens, cached), progress.total_tokens);
6295
- return `Prefilling prompt · ${count(completed - cached)} / ${count(progress.total_tokens - cached)} tokens · ${count(cached)} cached · ${seconds(now - startedAt)}`;
6303
+ return `Prefilling prompt · ${count(completed - cached)} / ${count(progress.total_tokens - cached)} tokens · ${count(cached)} cached · ${formatElapsed(now - startedAt)}`;
6296
6304
  }
6297
6305
  }
6298
6306
  };
6299
- var makeProgressTracker = (ui, now = () => performance.now()) => Effect15.gen(function* () {
6307
+ var makeProgressTracker = (ui, appendSummary, now = () => performance.now()) => Effect15.gen(function* () {
6300
6308
  let state = new Idle2;
6301
6309
  let nextId = 0;
6302
6310
  let active;
@@ -6304,7 +6312,6 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect15.gen(fu
6304
6312
  let nextResponseId = 0;
6305
6313
  const wake = yield* Queue.sliding(1);
6306
6314
  const present = (f) => Effect15.sync(f).pipe(Effect15.catchAllCause(() => Effect15.void));
6307
- const clearSummary = present(() => ui.setWidget(MAGNITUDE_SUMMARY_WIDGET_KEY, undefined));
6308
6315
  const resetRow = present(() => ui.setWorkingMessage());
6309
6316
  const render2 = present(() => {
6310
6317
  if (active)
@@ -6320,14 +6327,17 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect15.gen(fu
6320
6327
  const settled = state;
6321
6328
  state = runMachine.transition(settled, "Idle", {});
6322
6329
  const timings = [...settled.run.completed.entries()].filter(([id]) => settled.run.accepted.has(id)).sort(([a], [b]) => a - b).map(([, value]) => value);
6323
- if ([...settled.run.responses.values()].some((outcome) => !Option17.getOrElse(outcome, () => false)) || timings.length === 0) {
6324
- yield* clearSummary;
6330
+ if ([...settled.run.responses.values()].some((outcome) => !Option17.getOrElse(outcome, () => false)) || timings.length === 0)
6325
6331
  return;
6326
- }
6327
6332
  const tokens = timings.reduce((sum, t) => sum + t.predicted_n, 0);
6328
6333
  const decodeMs = timings.reduce((sum, t) => sum + t.predicted_ms, 0);
6329
- const summary = `● ${settled.run.modelName} worked for ${duration(settled.settledAt - settled.run.startedAt)}` + ` · ${seconds(timings[0].time_to_first_token_ms)} TTFT` + (decodeMs > 0 && tokens > 0 ? ` · ${(tokens * 1000 / decodeMs).toFixed(1)} tok/s` : "");
6330
- yield* present(() => ui.setWidget(MAGNITUDE_SUMMARY_WIDGET_KEY, (_tui, theme) => new Text(theme.fg("muted", summary), 0, 0)));
6334
+ yield* present(() => appendSummary(ProgressSummary.make({
6335
+ modelName: settled.run.modelName,
6336
+ elapsedMs: Math.max(0, settled.settledAt - settled.run.startedAt),
6337
+ ttftMs: timings[0].time_to_first_token_ms,
6338
+ generatedTokens: tokens,
6339
+ decodeMs
6340
+ })));
6331
6341
  });
6332
6342
  const clear = Effect15.gen(function* () {
6333
6343
  if (state._tag === "Disposed")
@@ -6337,7 +6347,6 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect15.gen(fu
6337
6347
  active = undefined;
6338
6348
  latest = undefined;
6339
6349
  yield* resetRow;
6340
- yield* clearSummary;
6341
6350
  });
6342
6351
  const startRun = (modelName) => Effect15.gen(function* () {
6343
6352
  if (state._tag === "Disposed")
@@ -6347,7 +6356,6 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect15.gen(fu
6347
6356
  if (state._tag !== "Idle")
6348
6357
  return;
6349
6358
  state = runMachine.transition(state, "Working", { run: { startedAt: now(), modelName, requests: new Map, completed: new Map, responses: new Map, accepted: new Set } });
6350
- yield* clearSummary;
6351
6359
  });
6352
6360
  const timer = yield* Effect15.forever(Effect15.gen(function* () {
6353
6361
  yield* Queue.take(wake);
@@ -6395,7 +6403,6 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect15.gen(fu
6395
6403
  if (!successful && latest !== undefined && requests.includes(latest)) {
6396
6404
  active = undefined;
6397
6405
  yield* resetRow;
6398
- yield* clearSummary;
6399
6406
  }
6400
6407
  yield* finalize;
6401
6408
  });
@@ -6459,8 +6466,13 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect15.gen(fu
6459
6466
  });
6460
6467
 
6461
6468
  // extensions/magnitude.ts
6462
- import { Effect as Effect16, Exit as Exit5, Scope as Scope7 } from "effect";
6469
+ import { Text } from "@earendil-works/pi-tui";
6470
+ import { Effect as Effect16, Exit as Exit5, Option as Option18, Schema as Schema56, Scope as Scope7 } from "effect";
6463
6471
  function magnitudeExtension(pi) {
6472
+ pi.registerEntryRenderer(MAGNITUDE_SUMMARY_ENTRY_TYPE, (entry, _options, theme) => {
6473
+ const summary = Schema56.decodeUnknownOption(ProgressSummary)(entry.data);
6474
+ return Option18.isSome(summary) ? new Text(theme.fg("muted", formatSummary(summary.value)), 0, 0) : undefined;
6475
+ });
6464
6476
  const completions = openAICompletionsApi();
6465
6477
  let tracker;
6466
6478
  let scope;
@@ -6487,7 +6499,7 @@ function magnitudeExtension(pi) {
6487
6499
  if (scope)
6488
6500
  await Effect16.runPromise(Scope7.close(scope, Exit5.void));
6489
6501
  scope = Effect16.runSync(Scope7.make());
6490
- tracker = await Effect16.runPromise(makeProgressTracker(ctx.ui).pipe(Scope7.extend(scope)));
6502
+ tracker = await Effect16.runPromise(makeProgressTracker(ctx.ui, (summary) => pi.appendEntry(MAGNITUDE_SUMMARY_ENTRY_TYPE, summary)).pipe(Scope7.extend(scope)));
6491
6503
  });
6492
6504
  pi.on("model_select", (event) => {
6493
6505
  if (event.model.provider !== "magnitude")
package/package.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "@magnitudedev/pi-extension",
3
- "version": "0.0.1-alpha.4",
4
- "description": "Magnitude model controls and inference progress for Pi",
3
+ "version": "0.0.2",
4
+ "description": "Run local AI models in Pi with Magnitude",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/magnitudedev/magnitude.git",
10
+ "directory": "integrations/pi"
11
+ },
7
12
  "publishConfig": {
8
13
  "access": "public"
9
14
  },
@@ -12,6 +17,7 @@
12
17
  "README.md"
13
18
  ],
14
19
  "pi": {
20
+ "image": "https://raw.githubusercontent.com/magnitudedev/magnitude/main/assets/readme/pi-preview.png",
15
21
  "extensions": [
16
22
  "./dist/magnitude.js"
17
23
  ]