pi-widget-host 0.2.0 → 0.3.3
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/CHANGELOG.md +29 -0
- package/README.md +23 -6
- package/extensions/index.ts +3 -1
- package/lib/demo-provider.ts +15 -8
- package/lib/registry.ts +11 -89
- package/lib/types.ts +2 -12
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- Add Buy Me a Coffee sponsor button to README and native GitHub funding link via `.github/FUNDING.yml`.
|
|
6
|
+
|
|
3
7
|
All notable changes to this project will be documented in this file.
|
|
4
8
|
|
|
5
9
|
This project follows semantic versioning.
|
|
6
10
|
|
|
11
|
+
## [0.3.2] - 2026-06-26
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- npm Trusted Publishing publish retry after registry configuration fix (no functional changes from 0.3.1).
|
|
16
|
+
|
|
17
|
+
## [0.3.1] - 2026-06-25
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- README install and development guidance now matches the current Pi OSS template baseline, including project-local install, `pi -e npm:pi-widget-host`, and `npm run pack:check` (`npm pack --dry-run`) validation in the public docs flow.
|
|
22
|
+
- `Package contents` now lists the actual shipped paths instead of unused template resource directories.
|
|
23
|
+
|
|
24
|
+
## [0.3.0] - 2026-06-24
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- Package-readiness hardening for the first publishable Host-only MVP release boundary.
|
|
29
|
+
- Explicit acceptance coverage for `matchday` event boost, known host tags, and silent empty slot behavior.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Minor semver bump marking the completed Host-only MVP: event boost, known tags, registry protocol, and demo-provider dogfooding path.
|
|
34
|
+
|
|
7
35
|
## [0.2.0] - 2026-06-15
|
|
8
36
|
|
|
9
37
|
### Added
|
|
@@ -18,3 +46,4 @@ This project follows semantic versioning.
|
|
|
18
46
|
### Changed
|
|
19
47
|
|
|
20
48
|
- Replaced template placeholders and removed template-only skill, prompt, and theme resources.
|
|
49
|
+
|
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
[](https://pi.dev/packages)
|
|
9
9
|
[](docs/release.md)
|
|
10
|
+
<a href="https://buymeacoffee.com/ekawano114m"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" width="217" height="60"></a>
|
|
10
11
|
|
|
11
12
|
> Shared-slot host for managing one prompt-top Pi widget across multiple providers.
|
|
12
13
|
|
|
@@ -32,19 +33,31 @@ v1 is a **Host-only MVP**:
|
|
|
32
33
|
|
|
33
34
|
## Install
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
Install the published npm package with Pi:
|
|
36
37
|
|
|
37
38
|
```bash
|
|
38
39
|
pi install npm:pi-widget-host
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
Install into the current project instead of your user Pi settings:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pi install npm:pi-widget-host -l
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or install from GitHub:
|
|
42
49
|
|
|
43
50
|
```bash
|
|
44
51
|
pi install git:github.com/eiei114/pi-widget-host
|
|
45
52
|
```
|
|
46
53
|
|
|
47
|
-
|
|
54
|
+
Try it without permanently installing:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pi -e npm:pi-widget-host
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Local development from this repository:
|
|
48
61
|
|
|
49
62
|
```bash
|
|
50
63
|
pi -e .
|
|
@@ -97,17 +110,21 @@ The built-in demo provider exists to prove the host loop first:
|
|
|
97
110
|
|
|
98
111
|
| Path | Purpose |
|
|
99
112
|
|---|---|
|
|
100
|
-
| `extensions
|
|
101
|
-
| `lib/` | config store, registry protocol, policy evaluation, demo provider |
|
|
102
|
-
| `docs
|
|
113
|
+
| `extensions/index.ts` | Pi extension entrypoint and `/widget-host:*` command registration |
|
|
114
|
+
| `lib/` | config store, registry protocol, policy evaluation, and demo provider |
|
|
115
|
+
| `docs/protocol.md` | registry protocol reference for future provider packages |
|
|
116
|
+
| `docs/release.md` | Trusted Publishing release notes |
|
|
103
117
|
|
|
104
118
|
## Development
|
|
105
119
|
|
|
106
120
|
```bash
|
|
107
121
|
npm install
|
|
108
122
|
npm run ci
|
|
123
|
+
npm run pack:check
|
|
109
124
|
```
|
|
110
125
|
|
|
126
|
+
`npm run ci` runs typecheck, tests, and pack validation. `npm run pack:check` runs `npm pack --dry-run` to confirm the shipped tarball contents.
|
|
127
|
+
|
|
111
128
|
## Release
|
|
112
129
|
|
|
113
130
|
This package uses npm Trusted Publishing.
|
package/extensions/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { readHostConfig, updateHostConfig, writeHostConfig } from "../lib/config.ts";
|
|
3
3
|
import { stopDemoProviderHeartbeat, syncBuiltInDemoProvider } from "../lib/demo-provider.ts";
|
|
4
4
|
import { DEFAULT_PRESET_ID, describePreset, evaluateProviderEntries, getPreset, getRemainingTtlMs, PRESET_OPTIONS } from "../lib/policy.ts";
|
|
5
|
-
import { getWidgetHostRegistry, listProviderEntries, removeProviderEntry } from "../lib/registry.ts";
|
|
5
|
+
import { clearHostPresent, getWidgetHostRegistry, listProviderEntries, markHostPresent, removeProviderEntry } from "../lib/registry.ts";
|
|
6
6
|
import { DEMO_PROVIDER_ID, WIDGET_ID, type HostConfig, type PolicyEvaluationResult, type ProviderState } from "../lib/types.ts";
|
|
7
7
|
|
|
8
8
|
type NotifyLevel = "info" | "warning" | "error";
|
|
@@ -103,6 +103,7 @@ export default function widgetHostExtension(pi: ExtensionAPI) {
|
|
|
103
103
|
pi.on("session_start", async (_event, ctx) => {
|
|
104
104
|
if (!ctx.hasUI) return;
|
|
105
105
|
bindUi(ctx as { hasUI: boolean; ui: HostUiBridge });
|
|
106
|
+
markHostPresent();
|
|
106
107
|
ensureRegistryWatcher();
|
|
107
108
|
await refreshHostWidget(ctx as { hasUI: boolean; ui: HostUiBridge });
|
|
108
109
|
});
|
|
@@ -113,6 +114,7 @@ export default function widgetHostExtension(pi: ExtensionAPI) {
|
|
|
113
114
|
registryDispose = undefined;
|
|
114
115
|
stopDemoProviderHeartbeat();
|
|
115
116
|
removeProviderEntry(DEMO_PROVIDER_ID);
|
|
117
|
+
clearHostPresent();
|
|
116
118
|
uiBridge = undefined;
|
|
117
119
|
});
|
|
118
120
|
|
package/lib/demo-provider.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
import { createProviderRuntime, type ProviderRuntime } from "pi-widget-core/provider";
|
|
1
2
|
import { readHostConfig } from "./config.ts";
|
|
2
3
|
import { detectTimeBlock } from "./policy.ts";
|
|
3
|
-
import { publishProviderEntry, removeProviderEntry } from "./registry.ts";
|
|
4
4
|
import { DEMO_PROVIDER_ID, type HostConfig, type ProviderEntry } from "./types.ts";
|
|
5
5
|
|
|
6
6
|
const HEARTBEAT_MS = 30_000;
|
|
7
7
|
const ENTRY_TTL_MS = 90_000;
|
|
8
8
|
|
|
9
9
|
let heartbeat: NodeJS.Timeout | undefined;
|
|
10
|
+
let runtime: ProviderRuntime | undefined;
|
|
11
|
+
|
|
12
|
+
function ensureRuntime(): ProviderRuntime {
|
|
13
|
+
runtime ??= createProviderRuntime({ providerId: DEMO_PROVIDER_ID });
|
|
14
|
+
return runtime;
|
|
15
|
+
}
|
|
10
16
|
|
|
11
17
|
function baseTagsForBlock(block: ReturnType<typeof detectTimeBlock>): string[] {
|
|
12
18
|
switch (block) {
|
|
@@ -52,11 +58,10 @@ export function buildDemoProviderEntry(config: HostConfig, now = new Date()): Pr
|
|
|
52
58
|
async function heartbeatTick(): Promise<void> {
|
|
53
59
|
const config = await readHostConfig();
|
|
54
60
|
if (!config.demoProviderEnabled) {
|
|
55
|
-
removeProviderEntry(DEMO_PROVIDER_ID);
|
|
56
61
|
stopDemoProviderHeartbeat();
|
|
57
62
|
return;
|
|
58
63
|
}
|
|
59
|
-
|
|
64
|
+
ensureRuntime().update(buildDemoProviderEntry(config));
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
function ensureHeartbeat(): void {
|
|
@@ -68,17 +73,19 @@ function ensureHeartbeat(): void {
|
|
|
68
73
|
|
|
69
74
|
export async function syncBuiltInDemoProvider(config: HostConfig): Promise<void> {
|
|
70
75
|
if (!config.demoProviderEnabled) {
|
|
71
|
-
removeProviderEntry(DEMO_PROVIDER_ID);
|
|
72
76
|
stopDemoProviderHeartbeat();
|
|
73
77
|
return;
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
ensureRuntime().update(buildDemoProviderEntry(config));
|
|
77
81
|
ensureHeartbeat();
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
export function stopDemoProviderHeartbeat(): void {
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
if (heartbeat) {
|
|
86
|
+
clearInterval(heartbeat);
|
|
87
|
+
heartbeat = undefined;
|
|
88
|
+
}
|
|
89
|
+
runtime?.stop();
|
|
90
|
+
runtime = undefined;
|
|
84
91
|
}
|
package/lib/registry.ts
CHANGED
|
@@ -1,89 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function normalizeEntry(entry: ProviderEntry): ProviderEntry {
|
|
15
|
-
return {
|
|
16
|
-
providerId: entry.providerId.trim(),
|
|
17
|
-
available: entry.available === true,
|
|
18
|
-
lines: Array.isArray(entry.lines) ? entry.lines.map((line) => String(line)) : [],
|
|
19
|
-
updatedAt: new Date(entry.updatedAt).toISOString(),
|
|
20
|
-
priority: typeof entry.priority === "number" ? entry.priority : 0,
|
|
21
|
-
tags: Array.isArray(entry.tags) ? [...new Set(entry.tags.map((tag) => String(tag).trim()).filter(Boolean))] : [],
|
|
22
|
-
mode: typeof entry.mode === "string" && entry.mode.trim().length > 0 ? entry.mode.trim() : undefined,
|
|
23
|
-
ttlMs: typeof entry.ttlMs === "number" && entry.ttlMs > 0 ? entry.ttlMs : undefined,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function sameEntry(left: ProviderEntry | undefined, right: ProviderEntry): boolean {
|
|
28
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function createRegistry(): WidgetHostRegistry {
|
|
32
|
-
const entries = new Map<string, ProviderEntry>();
|
|
33
|
-
const listeners = new Set<Listener>();
|
|
34
|
-
const notify = () => {
|
|
35
|
-
for (const listener of listeners) {
|
|
36
|
-
listener();
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
version: 1,
|
|
42
|
-
set(entry) {
|
|
43
|
-
const normalized = normalizeEntry(entry);
|
|
44
|
-
const previous = entries.get(normalized.providerId);
|
|
45
|
-
if (sameEntry(previous, normalized)) return;
|
|
46
|
-
entries.set(normalized.providerId, normalized);
|
|
47
|
-
notify();
|
|
48
|
-
},
|
|
49
|
-
remove(providerId) {
|
|
50
|
-
if (!entries.delete(providerId)) return;
|
|
51
|
-
notify();
|
|
52
|
-
},
|
|
53
|
-
list() {
|
|
54
|
-
return [...entries.values()].map((entry) => ({ ...entry, lines: [...entry.lines], tags: [...(entry.tags ?? [])] }));
|
|
55
|
-
},
|
|
56
|
-
subscribe(listener) {
|
|
57
|
-
listeners.add(listener);
|
|
58
|
-
return () => {
|
|
59
|
-
listeners.delete(listener);
|
|
60
|
-
};
|
|
61
|
-
},
|
|
62
|
-
clear() {
|
|
63
|
-
if (entries.size === 0) return;
|
|
64
|
-
entries.clear();
|
|
65
|
-
notify();
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function getWidgetHostRegistry(): WidgetHostRegistry {
|
|
71
|
-
const root = globalThis as typeof globalThis & Record<symbol, WidgetHostRegistry | undefined>;
|
|
72
|
-
const existing = root[REGISTRY_SYMBOL];
|
|
73
|
-
if (existing) return existing;
|
|
74
|
-
const registry = createRegistry();
|
|
75
|
-
root[REGISTRY_SYMBOL] = registry;
|
|
76
|
-
return registry;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function publishProviderEntry(entry: ProviderEntry): void {
|
|
80
|
-
getWidgetHostRegistry().set(entry);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function removeProviderEntry(providerId: string): void {
|
|
84
|
-
getWidgetHostRegistry().remove(providerId);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function listProviderEntries(): ProviderEntry[] {
|
|
88
|
-
return getWidgetHostRegistry().list();
|
|
89
|
-
}
|
|
1
|
+
export {
|
|
2
|
+
clearHostPresent,
|
|
3
|
+
getWidgetHostRegistry,
|
|
4
|
+
listProviderEntries,
|
|
5
|
+
markHostPresent,
|
|
6
|
+
publishProviderEntry,
|
|
7
|
+
removeProviderEntry,
|
|
8
|
+
subscribeToProviderEntries,
|
|
9
|
+
type ProviderEntry,
|
|
10
|
+
type WidgetHostRegistry,
|
|
11
|
+
} from "pi-widget-core/host";
|
package/lib/types.ts
CHANGED
|
@@ -1,25 +1,15 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { REGISTRY_SYMBOL, type ProviderEntry } from "pi-widget-core/protocol";
|
|
3
4
|
|
|
4
5
|
export const PACKAGE_NAME = "pi-widget-host";
|
|
5
6
|
export const WIDGET_ID = PACKAGE_NAME;
|
|
6
7
|
export const DEMO_PROVIDER_ID = `${PACKAGE_NAME}.demo`;
|
|
7
|
-
export
|
|
8
|
+
export { REGISTRY_SYMBOL, type ProviderEntry };
|
|
8
9
|
|
|
9
10
|
export type HostTimeBlock = "morning" | "day" | "evening" | "night";
|
|
10
11
|
export type KnownHostTag = "music" | "sports" | "playing-now" | "matchday" | "idle";
|
|
11
12
|
|
|
12
|
-
export interface ProviderEntry {
|
|
13
|
-
providerId: string;
|
|
14
|
-
available: boolean;
|
|
15
|
-
lines: string[];
|
|
16
|
-
updatedAt: string;
|
|
17
|
-
priority?: number;
|
|
18
|
-
tags?: string[];
|
|
19
|
-
mode?: string;
|
|
20
|
-
ttlMs?: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
13
|
export interface BlockPolicy {
|
|
24
14
|
allowedProviderIds?: string[];
|
|
25
15
|
preferredTags?: KnownHostTag[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-widget-host",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Host package for managing one shared Pi widget slot across multiple providers with preset policies and a built-in demo provider.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,6 +45,9 @@
|
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"pi-widget-core": "^0.1.0"
|
|
50
|
+
},
|
|
48
51
|
"peerDependencies": {
|
|
49
52
|
"@earendil-works/pi-coding-agent": "*"
|
|
50
53
|
},
|
|
@@ -55,7 +58,7 @@
|
|
|
55
58
|
},
|
|
56
59
|
"devDependencies": {
|
|
57
60
|
"@earendil-works/pi-coding-agent": "latest",
|
|
58
|
-
"@types/node": "^
|
|
61
|
+
"@types/node": "^26.0.0",
|
|
59
62
|
"tsx": "^4.20.6",
|
|
60
63
|
"typescript": "^6.0.3"
|
|
61
64
|
}
|