@aliou/pi-neuralwatt 0.0.1 → 0.1.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/README.md ADDED
@@ -0,0 +1,150 @@
1
+ ![banner](assets/banner.png)
2
+
3
+ # Pi Neuralwatt Extension
4
+
5
+ A Pi extension that adds [Neuralwatt](https://portal.neuralwatt.com/auth/register?ref=NW-ALIOU-Q7MF) as a model provider, giving you access to open-source models through an OpenAI-compatible API with energy transparency.
6
+
7
+ ## Installation
8
+
9
+ ### Get API Key
10
+
11
+ Sign up at [neuralwatt.com](https://portal.neuralwatt.com/auth/register?ref=NW-ALIOU-Q7MF) to get an API key.
12
+
13
+ ### Configure Credentials
14
+
15
+ The extension uses Pi's credential storage. Add your API key to `~/.pi/agent/auth.json` (recommended):
16
+
17
+ ```json
18
+ {
19
+ "neuralwatt": { "type": "api_key", "key": "your-api-key-here" }
20
+ }
21
+ ```
22
+
23
+ Or set environment variable:
24
+
25
+ ```bash
26
+ export NEURALWATT_API_KEY="your-api-key-here"
27
+ ```
28
+
29
+ ### Install Extension
30
+
31
+ ```bash
32
+ # From npm
33
+ pi install npm:@aliou/pi-neuralwatt
34
+
35
+ # From git
36
+ pi install git:github.com/aliou/pi-neuralwatt
37
+
38
+ # Local development
39
+ pi -e ./src/extensions/provider/index.ts
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ Once installed, select `neuralwatt` as your provider and choose from available models:
45
+
46
+ ```
47
+ /model neuralwatt meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8
48
+ ```
49
+
50
+ ### Quota Command
51
+
52
+ Check your API usage at a glance:
53
+
54
+ ```
55
+ /neuralwatt:quota
56
+ ```
57
+
58
+ The quota command shows three tabs:
59
+ - **Subscription** (when subscribed) — plan details, energy quota with progress bar, billing period
60
+ - **Credits** — credit balance with progress bar, accounting method
61
+ - **Usage & Key** — monthly usage (cost, requests, tokens, energy), API key info, key allowance, rate limits
62
+
63
+ ### Quota Warnings
64
+
65
+ When enabled, the extension notifies you when credits or energy are running low. When you have an active subscription, only energy warnings fire (credits are on-demand top-up only). Warnings use escalation on severity transitions and have a cooldown for `warning` level.
66
+
67
+ ### Sub-bar Integration
68
+
69
+ When a Neuralwatt model is active, the footer status bar shows live quota usage (credits and energy). The status updates after each response and on session start.
70
+
71
+ ## Settings
72
+
73
+ Configure features via `/neuralwatt:settings` or `pi config`:
74
+
75
+ ```
76
+ /neuralwatt:settings
77
+ ```
78
+
79
+ Available settings:
80
+ - **Quota command** (`quotaCommand`) — Show/hide `/neuralwatt:quota`
81
+ - **Quota warnings** (`quotaWarnings`) — Enable/disable low quota notifications
82
+ - **Sub-bar integration** (`subBarIntegration`) — Show/hide usage in status bar
83
+
84
+ The provider itself cannot be disabled — it is always loaded.
85
+
86
+ ## Disabling Features
87
+
88
+ Each feature is a separate Pi extension. You can disable individual features using `pi config`:
89
+
90
+ ```
91
+ pi config extensions.disabled add @aliou/pi-neuralwatt/quota-warnings
92
+ ```
93
+
94
+ This prevents the quota-warnings extension from loading while keeping the rest active. Replace `quota-warnings` with `command-quotas` or `sub-bar-integration` to disable other features.
95
+
96
+ ## Adding or Updating Models
97
+
98
+ Models are hardcoded in `src/extensions/provider/models.ts` and validated against the live API. To update:
99
+
100
+ 1. Run `pnpm test` — it fetches `/v1/models` and compares against hardcoded definitions
101
+ 2. Fix any discrepancies (missing models, changed context windows)
102
+ 3. Re-run `pnpm test` to confirm
103
+
104
+ ## Development
105
+
106
+ ### Setup
107
+
108
+ ```bash
109
+ git clone https://github.com/aliou/pi-neuralwatt.git
110
+ cd pi-neuralwatt
111
+
112
+ # Install dependencies (sets up pre-commit hooks)
113
+ pnpm install && pnpm prepare
114
+ ```
115
+
116
+ Pre-commit hooks run on every commit:
117
+ - TypeScript type checking
118
+ - Biome linting
119
+ - Biome formatting with auto-fix
120
+
121
+ ### Commands
122
+
123
+ ```bash
124
+ # Type check
125
+ pnpm run typecheck
126
+
127
+ # Lint
128
+ pnpm run lint
129
+
130
+ # Format
131
+ pnpm run format
132
+
133
+ # Test
134
+ pnpm run test
135
+ ```
136
+
137
+ ## Release
138
+
139
+ This repository uses [Changesets](https://github.com/changesets/changesets) for versioning.
140
+
141
+ ## Requirements
142
+
143
+ - Pi coding agent v0.67.68+
144
+ - Neuralwatt API key (configured in `~/.pi/agent/auth.json` or via `NEURALWATT_API_KEY`)
145
+
146
+ ## Links
147
+
148
+ - [Neuralwatt](https://portal.neuralwatt.com/auth/register?ref=NW-ALIOU-Q7MF)
149
+ - [Neuralwatt API Docs](https://neuralwatt.com/docs)
150
+ - [Pi Documentation](https://buildwithpi.ai/)
package/package.json CHANGED
@@ -1,15 +1,77 @@
1
1
  {
2
2
  "name": "@aliou/pi-neuralwatt",
3
- "version": "0.0.1",
4
- "description": "Pi extension providing a Neuralwatt inference API provider",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "type": "module",
5
6
  "private": false,
7
+ "keywords": [
8
+ "pi-package",
9
+ "pi-extension",
10
+ "pi"
11
+ ],
6
12
  "repository": {
7
13
  "type": "git",
8
14
  "url": "https://github.com/aliou/pi-neuralwatt"
9
15
  },
10
- "license": "MIT",
11
- "main": "index.js",
16
+ "pi": {
17
+ "extensions": [
18
+ "./src/extensions/provider/index.ts",
19
+ "./src/extensions/command-quotas/index.ts",
20
+ "./src/extensions/quota-warnings/index.ts",
21
+ "./src/extensions/sub-bar-integration/index.ts"
22
+ ]
23
+ },
12
24
  "publishConfig": {
13
25
  "access": "public"
26
+ },
27
+ "files": [
28
+ "src",
29
+ "schema.json",
30
+ "README.md"
31
+ ],
32
+ "dependencies": {
33
+ "@aliou/pi-utils-settings": "^0.9.0",
34
+ "@aliou/pi-utils-ui": "^0.1.0"
35
+ },
36
+ "peerDependencies": {
37
+ "@mariozechner/pi-coding-agent": ">=0.67.68",
38
+ "@mariozechner/pi-tui": ">=0.67.68",
39
+ "@sinclair/typebox": ">=0.34.0"
40
+ },
41
+ "devDependencies": {
42
+ "@aliou/biome-plugins": "^0.8.1",
43
+ "@biomejs/biome": "^2.4.12",
44
+ "@changesets/cli": "^2.27.11",
45
+ "@mariozechner/pi-coding-agent": "0.67.68",
46
+ "@mariozechner/pi-tui": "0.67.68",
47
+ "@types/node": "^25.0.10",
48
+ "husky": "^9.1.7",
49
+ "ts-json-schema-generator": "^2.4.0",
50
+ "typescript": "^5.9.3",
51
+ "vitest": "^4.0.18"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "@mariozechner/pi-coding-agent": {
55
+ "optional": true
56
+ },
57
+ "@mariozechner/pi-tui": {
58
+ "optional": true
59
+ },
60
+ "@sinclair/typebox": {
61
+ "optional": true
62
+ }
63
+ },
64
+ "scripts": {
65
+ "typecheck": "tsc --noEmit",
66
+ "lint": "biome check",
67
+ "format": "biome check --write",
68
+ "test": "vitest run",
69
+ "test:watch": "vitest",
70
+ "gen:schema": "ts-json-schema-generator --path src/config.ts --type NeuralwattConfig --no-type-check -o schema.json",
71
+ "check:schema": "ts-json-schema-generator --path src/config.ts --type NeuralwattConfig --no-type-check -o /tmp/schema-check.json && diff -q schema.json /tmp/schema-check.json",
72
+ "check:lockfile": "pnpm install --frozen-lockfile --ignore-scripts",
73
+ "changeset": "changeset",
74
+ "version": "changeset version",
75
+ "release": "pnpm changeset publish"
14
76
  }
15
- }
77
+ }
package/schema.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "$ref": "#/definitions/NeuralwattConfig",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "definitions": {
5
+ "NeuralwattConfig": {
6
+ "additionalProperties": false,
7
+ "properties": {
8
+ "quotaCommand": {
9
+ "description": "Show the quota command (/neuralwatt:quota).",
10
+ "type": "boolean"
11
+ },
12
+ "quotaWarnings": {
13
+ "description": "Show quota warnings when credits or energy are low.",
14
+ "type": "boolean"
15
+ },
16
+ "subBarIntegration": {
17
+ "description": "Show usage in the sub-bar / status bar.",
18
+ "type": "boolean"
19
+ }
20
+ },
21
+ "type": "object"
22
+ }
23
+ }
24
+ }
package/src/config.ts ADDED
@@ -0,0 +1,154 @@
1
+ import {
2
+ ConfigLoader,
3
+ registerSettingsCommand,
4
+ type SettingsSection,
5
+ } from "@aliou/pi-utils-settings";
6
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
7
+ import type { SettingItem } from "@mariozechner/pi-tui";
8
+
9
+ export type NeuralwattFeatureId =
10
+ | "quotaCommand"
11
+ | "quotaWarnings"
12
+ | "subBarIntegration";
13
+
14
+ export const NEURALWATT_EXTENSIONS_REQUEST_EVENT =
15
+ "neuralwatt:extensions:request" as const;
16
+
17
+ export const NEURALWATT_EXTENSIONS_REGISTER_EVENT =
18
+ "neuralwatt:extensions:register" as const;
19
+
20
+ export interface NeuralwattExtensionsRegisterPayload {
21
+ feature: NeuralwattFeatureId;
22
+ }
23
+
24
+ export interface NeuralwattConfig {
25
+ /** Show the quota command (/neuralwatt:quota). */
26
+ quotaCommand?: boolean;
27
+ /** Show quota warnings when credits or energy are low. */
28
+ quotaWarnings?: boolean;
29
+ /** Show usage in the sub-bar / status bar. */
30
+ subBarIntegration?: boolean;
31
+ }
32
+
33
+ export interface ResolvedNeuralwattConfig {
34
+ quotaCommand: boolean;
35
+ quotaWarnings: boolean;
36
+ subBarIntegration: boolean;
37
+ }
38
+
39
+ const DEFAULTS: ResolvedNeuralwattConfig = {
40
+ quotaCommand: true,
41
+ quotaWarnings: true,
42
+ subBarIntegration: true,
43
+ };
44
+
45
+ export const configLoader = new ConfigLoader<
46
+ NeuralwattConfig,
47
+ ResolvedNeuralwattConfig
48
+ >("neuralwatt", DEFAULTS);
49
+
50
+ export const NEURALWATT_CONFIG_UPDATED_EVENT =
51
+ "neuralwatt:config:updated" as const;
52
+
53
+ export interface NeuralwattConfigUpdatedPayload {
54
+ config: ResolvedNeuralwattConfig;
55
+ }
56
+
57
+ export function emitConfigUpdated(pi: ExtensionAPI): void {
58
+ pi.events.emit(NEURALWATT_CONFIG_UPDATED_EVENT, {
59
+ config: configLoader.getConfig(),
60
+ });
61
+ }
62
+
63
+ export interface RegisterNeuralwattSettingsOptions {
64
+ getLoadedFeatures: () => Set<NeuralwattFeatureId>;
65
+ }
66
+
67
+ function featureRow(
68
+ id: NeuralwattFeatureId,
69
+ label: string,
70
+ description: string,
71
+ configValue: boolean,
72
+ isLoaded: boolean,
73
+ ): SettingItem {
74
+ if (isLoaded) {
75
+ return {
76
+ id,
77
+ label,
78
+ description,
79
+ currentValue: configValue ? "enabled" : "disabled",
80
+ values: ["enabled", "disabled"],
81
+ };
82
+ }
83
+ return {
84
+ id,
85
+ label,
86
+ description: `${description} (Not loaded by Pi)`,
87
+ currentValue: "unavailable",
88
+ values: [],
89
+ };
90
+ }
91
+
92
+ export function registerNeuralwattSettings(
93
+ pi: ExtensionAPI,
94
+ options: RegisterNeuralwattSettingsOptions,
95
+ ): void {
96
+ const { getLoadedFeatures } = options;
97
+
98
+ registerSettingsCommand<NeuralwattConfig, ResolvedNeuralwattConfig>(pi, {
99
+ commandName: "neuralwatt:settings",
100
+ title: "Neuralwatt Settings",
101
+ configStore: configLoader,
102
+ buildSections: (tabConfig, resolved): SettingsSection[] => {
103
+ const loaded = getLoadedFeatures();
104
+ return [
105
+ {
106
+ label: "Features",
107
+ items: [
108
+ featureRow(
109
+ "quotaCommand",
110
+ "Quota command",
111
+ "Toggle the /neuralwatt:quota command, showing your API usage at a glance",
112
+ tabConfig?.quotaCommand ?? resolved.quotaCommand,
113
+ loaded.has("quotaCommand"),
114
+ ),
115
+ featureRow(
116
+ "quotaWarnings",
117
+ "Quota warnings",
118
+ "Toggle notifications when credits or energy are running low",
119
+ tabConfig?.quotaWarnings ?? resolved.quotaWarnings,
120
+ loaded.has("quotaWarnings"),
121
+ ),
122
+ featureRow(
123
+ "subBarIntegration",
124
+ "Sub-bar integration",
125
+ "Toggle integration with the status bar and sub-core",
126
+ tabConfig?.subBarIntegration ?? resolved.subBarIntegration,
127
+ loaded.has("subBarIntegration"),
128
+ ),
129
+ ],
130
+ },
131
+ ];
132
+ },
133
+ onSettingChange: (id, newValue, config) => {
134
+ if (!getLoadedFeatures().has(id as NeuralwattFeatureId)) {
135
+ return null;
136
+ }
137
+
138
+ const enabled = newValue === "enabled";
139
+ switch (id) {
140
+ case "quotaCommand":
141
+ return { ...config, quotaCommand: enabled };
142
+ case "quotaWarnings":
143
+ return { ...config, quotaWarnings: enabled };
144
+ case "subBarIntegration":
145
+ return { ...config, subBarIntegration: enabled };
146
+ default:
147
+ return null;
148
+ }
149
+ },
150
+ onSave: async () => {
151
+ emitConfigUpdated(pi);
152
+ },
153
+ });
154
+ }
@@ -0,0 +1,83 @@
1
+ import { join } from "node:path";
2
+ import { type ExtensionAPI, getAgentDir } from "@mariozechner/pi-coding-agent";
3
+ import { getNeuralwattApiKey } from "../../lib/env";
4
+ import { fetchQuotas } from "../../utils/quotas";
5
+ import { QuotasComponent } from "./components/quotas-display";
6
+
7
+ function missingAuthMessage(): string {
8
+ const authPath = join(getAgentDir(), "auth.json");
9
+ return `Neuralwatt quota requires an API key. Add credentials to ${authPath} or set the NEURALWATT_API_KEY environment variable.`;
10
+ }
11
+
12
+ export function registerQuotasCommand(pi: ExtensionAPI): void {
13
+ pi.registerCommand("neuralwatt:quota", {
14
+ description: "Display Neuralwatt API usage and quota",
15
+ handler: async (_args, ctx) => {
16
+ const apiKey = await getNeuralwattApiKey(ctx.modelRegistry.authStorage);
17
+ if (!apiKey) {
18
+ ctx.ui.notify(missingAuthMessage(), "warning");
19
+ return;
20
+ }
21
+ const key: string = apiKey;
22
+
23
+ const result = await ctx.ui.custom<null>((tui, theme, _kb, done) => {
24
+ const controller = new AbortController();
25
+ const component = new QuotasComponent(
26
+ theme,
27
+ tui,
28
+ () => {
29
+ controller.abort();
30
+ done(null);
31
+ },
32
+ () => {
33
+ component.setState({ type: "loading" });
34
+ tui.requestRender();
35
+ void loadQuotas();
36
+ },
37
+ );
38
+
39
+ async function loadQuotas(): Promise<void> {
40
+ const fetchResult = await fetchQuotas(key, controller.signal);
41
+ if (controller.signal.aborted) return;
42
+ if (fetchResult.success) {
43
+ component.setState({
44
+ type: "loaded",
45
+ quotas: fetchResult.data.quotas,
46
+ });
47
+ } else {
48
+ component.setState({
49
+ type: "error",
50
+ message: fetchResult.error.message,
51
+ });
52
+ }
53
+ tui.requestRender();
54
+ }
55
+
56
+ void loadQuotas();
57
+
58
+ return {
59
+ render: (width: number) => component.render(width),
60
+ invalidate: () => component.invalidate(),
61
+ handleInput: (data: string) => component.handleInput(data),
62
+ dispose: () => {
63
+ controller.abort();
64
+ component.destroy();
65
+ },
66
+ };
67
+ });
68
+
69
+ // Non-interactive fallback (RPC, print, JSON modes)
70
+ if (result === undefined) {
71
+ const fetchResult = await fetchQuotas(key);
72
+ if (!fetchResult.success) {
73
+ ctx.ui.notify(
74
+ JSON.stringify({ error: fetchResult.error.message }),
75
+ "error",
76
+ );
77
+ return;
78
+ }
79
+ ctx.ui.notify(JSON.stringify(fetchResult.data.quotas), "info");
80
+ }
81
+ },
82
+ });
83
+ }