pi-insomnia 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/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ This project follows the spirit of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and uses semantic versioning for releases.
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - TBD
10
+
11
+ ### Added
12
+
13
+ - Initial `pi-insomnia` package scaffold.
14
+ - Automatic macOS idle sleep inhibition while Pi agent runs are active.
15
+ - Cleanup on agent completion and session shutdown.
16
+ - Footer status indicator while sleep inhibition is active.
17
+ - Best-effort install/update telemetry ping to `mocito.dev`, gated by Pi telemetry/offline settings and disabled in CI.
@@ -0,0 +1,41 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment include:
12
+
13
+ - Demonstrating empathy and kindness toward other people
14
+ - Being respectful of differing opinions, viewpoints, and experiences
15
+ - Giving and gracefully accepting constructive feedback
16
+ - Accepting responsibility and apologizing to those affected by our mistakes
17
+ - Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ - The use of sexualized language or imagery, and sexual attention or advances
22
+ - Trolling, insulting or derogatory comments, and personal or political attacks
23
+ - Public or private harassment
24
+ - Publishing others' private information without explicit permission
25
+ - Other conduct which could reasonably be considered inappropriate in a professional setting
26
+
27
+ ## Enforcement Responsibilities
28
+
29
+ Project maintainers are responsible for clarifying and enforcing standards of acceptable behavior and may remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned with this Code of Conduct.
30
+
31
+ ## Scope
32
+
33
+ This Code of Conduct applies within all project spaces and also applies when an individual is officially representing the project in public spaces.
34
+
35
+ ## Enforcement
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the project maintainers through GitHub. All complaints will be reviewed and investigated promptly and fairly.
38
+
39
+ ## Attribution
40
+
41
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1.
@@ -0,0 +1,51 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in contributing to `pi-insomnia`.
4
+
5
+ ## Development setup
6
+
7
+ ```bash
8
+ npm install
9
+ npm run check
10
+ ```
11
+
12
+ This package is source-distributed: Pi loads the TypeScript extension files directly. There is no build step for runtime use.
13
+
14
+ ## Local testing
15
+
16
+ Install this checkout into a temporary Pi project:
17
+
18
+ ```bash
19
+ mkdir -p <test-project>
20
+ cd <test-project>
21
+ pi install -l /path/to/pi-mono/packages/pi-insomnia
22
+ pi
23
+ ```
24
+
25
+ For a one-off run without changing settings:
26
+
27
+ ```bash
28
+ pi -e /path/to/pi-mono/packages/pi-insomnia --print "list loaded extensions"
29
+ ```
30
+
31
+ ## Pull request checklist
32
+
33
+ Before opening a pull request:
34
+
35
+ - Run `npm run check`.
36
+ - Run `npm audit --omit=dev`.
37
+ - Run `npm run pack:dry-run` and confirm the package contents are intentional.
38
+ - Update `README.md` if user-facing behavior changes.
39
+ - Update `CHANGELOG.md` for notable changes.
40
+ - Keep examples and paths generic; do not commit machine-specific paths, API keys, tokens, or provider config containing secrets.
41
+
42
+ ## Coding guidelines
43
+
44
+ - Keep `extensions/index.ts` focused on Pi extension wiring and move reusable implementation details into `src/`.
45
+ - Avoid adding commands or tools unless they are necessary; the default package behavior should remain automatic when installed.
46
+ - Treat config keys and environment variables as public interface; changes to defaults or precedence are breaking changes.
47
+ - Keep macOS-specific power-management behavior isolated so future platform backends can be added without changing extension wiring.
48
+
49
+ ## Code of conduct
50
+
51
+ This project follows the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jose Mocito
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # pi-insomnia
2
+
3
+ A source-distributed [Pi](https://pi.dev) package that prevents macOS idle sleep while the Pi agent is working.
4
+
5
+ > [!WARNING]
6
+ > Pi packages can execute arbitrary code through extensions. Review package source before installing any third-party Pi package.
7
+
8
+ ## Features
9
+
10
+ - Automatically starts a macOS sleep assertion when a Pi agent run starts.
11
+ - Automatically releases the assertion when the agent run ends.
12
+ - Cleans up on Pi session shutdown, reloads, and quits.
13
+ - Uses the macOS built-in `/usr/bin/caffeinate` command; no third-party runtime dependency is required.
14
+ - Shows a small `☕ sleep inhibited` footer status while active in UI modes.
15
+ - No slash commands, tools, or manual activation needed.
16
+
17
+ ## Platform support
18
+
19
+ `pi-insomnia` currently targets macOS. On non-macOS platforms it silently no-ops.
20
+
21
+ On macOS, the extension launches:
22
+
23
+ ```bash
24
+ /usr/bin/caffeinate -i -w <pi-pid>
25
+ ```
26
+
27
+ `-i` prevents idle system sleep while the process is running. `-w <pi-pid>` also ties the assertion to the Pi process so the helper exits if Pi exits unexpectedly. The extension still explicitly terminates the helper when the agent becomes idle.
28
+
29
+ ## Installation
30
+
31
+ Install from npm:
32
+
33
+ ```bash
34
+ pi install npm:pi-insomnia
35
+ ```
36
+
37
+ Install project-locally with Pi's `-l` flag:
38
+
39
+ ```bash
40
+ pi install -l npm:pi-insomnia
41
+ ```
42
+
43
+ During local development from this monorepo:
44
+
45
+ ```bash
46
+ pi install /path/to/pi-mono/packages/pi-insomnia
47
+ ```
48
+
49
+ For a one-off test run without installing:
50
+
51
+ ```bash
52
+ pi -e /path/to/pi-mono/packages/pi-insomnia --print "explain what this package does"
53
+ ```
54
+
55
+ This is an npm-compatible TypeScript Pi package. There is no runtime build step.
56
+
57
+ ## Configuration
58
+
59
+ | Variable | Purpose |
60
+ |---|---|
61
+ | `PI_OFFLINE=1` | Disables install/update telemetry. |
62
+ | `PI_TELEMETRY=0` | Disables install/update telemetry. |
63
+
64
+ `pi-insomnia` has no behavior configuration. If the package is installed and Pi is running on macOS, sleep inhibition is automatic while the agent is working.
65
+
66
+ ## Notes
67
+
68
+ - The extension inhibits idle system sleep, not display sleep. It does not use `caffeinate -d`.
69
+ - The extension does not request elevated privileges.
70
+ - On startup, Pi Insomnia sends a best-effort install/update telemetry ping once per package version unless Pi telemetry is disabled, offline mode is enabled, or Pi runs in CI.
71
+
72
+ ## Development
73
+
74
+ ```bash
75
+ npm install
76
+ npm run check
77
+ npm run pack:dry-run
78
+ ```
package/SECURITY.md ADDED
@@ -0,0 +1,28 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ Security fixes are provided for the latest released version of `pi-insomnia`.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Please do not open a public issue for suspected security vulnerabilities.
10
+
11
+ Report privately by contacting the repository maintainer through GitHub. Include:
12
+
13
+ - a description of the issue;
14
+ - steps to reproduce;
15
+ - affected versions or commits, if known;
16
+ - any suggested mitigation.
17
+
18
+ The maintainer will acknowledge reports as soon as practical and coordinate disclosure once a fix or mitigation is available.
19
+
20
+ ## Security model
21
+
22
+ `pi-insomnia` is a Pi package. Pi extensions execute with the same permissions as the local user running Pi. Users should review installed Pi packages and only install packages from sources they trust.
23
+
24
+ Do not commit API keys, tokens, credentials, local settings, or machine-specific paths.
25
+
26
+ On macOS, the extension starts the system-provided `/usr/bin/caffeinate` helper while the Pi agent is working. It does not request elevated privileges, pass user prompts to the helper, or run shell commands. The helper is started directly with fixed arguments and is terminated when the agent becomes idle or the Pi session shuts down.
27
+
28
+ On startup, the extension sends a best-effort install/update telemetry ping to `mocito.dev` once per package version unless Pi telemetry is disabled, offline mode is enabled, or Pi runs in CI. The ping includes only the package name, version, and parsed platform/runtime/architecture from its User-Agent; it does not include prompts, file paths, config values, environment variables, or API keys.
@@ -0,0 +1,30 @@
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { reportInstallTelemetry } from "../src/install-telemetry.js";
3
+ import { MacSleepInhibitor } from "../src/sleep-inhibitor.js";
4
+
5
+ const STATUS_KEY = "pi-insomnia";
6
+
7
+ function setStatus(ctx: ExtensionContext, active: boolean): void {
8
+ if (!ctx.hasUI) return;
9
+ ctx.ui.setStatus(STATUS_KEY, active ? "☕ sleep inhibited" : undefined);
10
+ }
11
+
12
+ export default function piInsomnia(pi: ExtensionAPI) {
13
+ reportInstallTelemetry();
14
+
15
+ const inhibitor = new MacSleepInhibitor();
16
+
17
+ pi.on("agent_start", async (_event, ctx) => {
18
+ if (inhibitor.acquire()) setStatus(ctx, true);
19
+ });
20
+
21
+ pi.on("agent_end", async (_event, ctx) => {
22
+ inhibitor.release();
23
+ if (!inhibitor.isInhibiting) setStatus(ctx, false);
24
+ });
25
+
26
+ pi.on("session_shutdown", async (_event, ctx) => {
27
+ inhibitor.forceStop();
28
+ setStatus(ctx, false);
29
+ });
30
+ }
package/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { default } from "./extensions/index.js";
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "pi-insomnia",
3
+ "version": "0.1.0",
4
+ "description": "Prevent macOS idle sleep while Pi agents are working.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Jose Mocito",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/jvm/pi-mono.git",
11
+ "directory": "packages/pi-insomnia"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/jvm/pi-mono/issues"
15
+ },
16
+ "homepage": "https://github.com/jvm/pi-mono/tree/main/packages/pi-insomnia#readme",
17
+ "keywords": [
18
+ "pi-package",
19
+ "pi-extension",
20
+ "pi",
21
+ "macos",
22
+ "sleep",
23
+ "caffeinate",
24
+ "insomnia"
25
+ ],
26
+ "exports": {
27
+ ".": "./src/index.ts"
28
+ },
29
+ "pi": {
30
+ "extensions": [
31
+ "./index.ts"
32
+ ]
33
+ },
34
+ "files": [
35
+ "index.ts",
36
+ "extensions",
37
+ "src",
38
+ "README.md",
39
+ "LICENSE",
40
+ "CHANGELOG.md",
41
+ "SECURITY.md",
42
+ "CONTRIBUTING.md",
43
+ "CODE_OF_CONDUCT.md"
44
+ ],
45
+ "scripts": {
46
+ "check": "tsc --noEmit",
47
+ "typecheck": "tsc --noEmit",
48
+ "pack:dry-run": "npm pack --dry-run"
49
+ },
50
+ "peerDependencies": {
51
+ "@earendil-works/pi-coding-agent": "*"
52
+ },
53
+ "devDependencies": {
54
+ "@earendil-works/pi-coding-agent": "^0.78.0",
55
+ "@types/node": "^25.6.2",
56
+ "typescript": "^6.0.3"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ },
61
+ "engines": {
62
+ "node": ">=20.6.0"
63
+ }
64
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export const PACKAGE_NAME = "pi-insomnia";
2
+
3
+ export * from "./sleep-inhibitor.js";
@@ -0,0 +1,104 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
6
+
7
+ const PACKAGE_NAME = "pi-insomnia";
8
+ const INSTALL_TELEMETRY_URL = "https://mocito.dev/api/report-install";
9
+ const INSTALL_TELEMETRY_TIMEOUT_MS = 5000;
10
+ const CI_ENVIRONMENT_VARIABLES = [
11
+ "APPVEYOR",
12
+ "BITBUCKET_BUILD_NUMBER",
13
+ "BUILDKITE",
14
+ "CIRCLECI",
15
+ "CODESPACES",
16
+ "DRONE",
17
+ "GITHUB_ACTIONS",
18
+ "GITLAB_CI",
19
+ "JENKINS_URL",
20
+ "NETLIFY",
21
+ "TEAMCITY_VERSION",
22
+ "TF_BUILD",
23
+ "TRAVIS",
24
+ "VERCEL",
25
+ ];
26
+
27
+ interface InstallTelemetryState {
28
+ lastReportedVersion?: string;
29
+ }
30
+
31
+ interface PiSettingsDocument {
32
+ enableInstallTelemetry?: unknown;
33
+ }
34
+
35
+ function readJsonFile(path: string): unknown {
36
+ try {
37
+ return JSON.parse(readFileSync(path, "utf8")) as unknown;
38
+ } catch {
39
+ return {};
40
+ }
41
+ }
42
+
43
+ function isTruthyEnvFlag(value: string | undefined): boolean {
44
+ if (!value) return false;
45
+ return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
46
+ }
47
+
48
+ function isPresentEnvFlag(value: string | undefined): boolean {
49
+ if (!value) return false;
50
+ const normalized = value.toLowerCase();
51
+ return normalized !== "0" && normalized !== "false" && normalized !== "no";
52
+ }
53
+
54
+ function isCiEnvironment(): boolean {
55
+ if (isTruthyEnvFlag(process.env.CI)) return true;
56
+ return CI_ENVIRONMENT_VARIABLES.some((name) => isPresentEnvFlag(process.env[name]));
57
+ }
58
+
59
+ function isInstallTelemetryEnabled(): boolean {
60
+ if (isCiEnvironment()) return false;
61
+ if (isTruthyEnvFlag(process.env.PI_OFFLINE)) return false;
62
+ if (process.env.PI_TELEMETRY !== undefined) return isTruthyEnvFlag(process.env.PI_TELEMETRY);
63
+
64
+ const settings = readJsonFile(join(getAgentDir(), "settings.json")) as PiSettingsDocument;
65
+ return settings.enableInstallTelemetry !== false;
66
+ }
67
+
68
+ function getPackageVersion(): string {
69
+ const packageJson = readJsonFile(fileURLToPath(new URL("../package.json", import.meta.url))) as { version?: unknown };
70
+ return typeof packageJson.version === "string" && packageJson.version.length > 0 ? packageJson.version : "0.0.0";
71
+ }
72
+
73
+ function getInstallTelemetryUserAgent(version: string): string {
74
+ const runtimeVersions = process.versions as NodeJS.ProcessVersions & { bun?: string };
75
+ const runtime = runtimeVersions.bun ? `bun/${runtimeVersions.bun}` : `node/${process.version}`;
76
+ return `${PACKAGE_NAME}/${version} (${process.platform}; ${runtime}; ${process.arch})`;
77
+ }
78
+
79
+ async function reportInstallTelemetryAsync(): Promise<void> {
80
+ try {
81
+ if (!isInstallTelemetryEnabled()) return;
82
+
83
+ const version = getPackageVersion();
84
+ const extensionsDir = join(getAgentDir(), "extensions");
85
+ const statePath = join(extensionsDir, "pi-insomnia-install.json");
86
+ const state = readJsonFile(statePath) as InstallTelemetryState;
87
+ if (state.lastReportedVersion === version) return;
88
+
89
+ await mkdir(extensionsDir, { recursive: true });
90
+ await writeFile(statePath, `${JSON.stringify({ lastReportedVersion: version }, null, 2)}\n`, "utf8");
91
+
92
+ const params = new URLSearchParams({ tool: PACKAGE_NAME, version });
93
+ await fetch(`${INSTALL_TELEMETRY_URL}?${params.toString()}`, {
94
+ headers: { "User-Agent": getInstallTelemetryUserAgent(version) },
95
+ signal: AbortSignal.timeout(INSTALL_TELEMETRY_TIMEOUT_MS),
96
+ });
97
+ } catch {
98
+ // Best-effort telemetry: ignore settings, filesystem, and network failures.
99
+ }
100
+ }
101
+
102
+ export function reportInstallTelemetry(): void {
103
+ void reportInstallTelemetryAsync();
104
+ }
@@ -0,0 +1,84 @@
1
+ import { spawn, type ChildProcess } from "node:child_process";
2
+
3
+ export const DEFAULT_CAFFEINATE_PATH = "/usr/bin/caffeinate";
4
+
5
+ export interface MacSleepInhibitorOptions {
6
+ platform?: NodeJS.Platform;
7
+ pid?: number;
8
+ caffeinatePath?: string;
9
+ }
10
+
11
+ export class MacSleepInhibitor {
12
+ private readonly platform: NodeJS.Platform;
13
+ private readonly pid: number;
14
+ private readonly caffeinatePath: string;
15
+ private child: ChildProcess | undefined;
16
+ private activeRequests = 0;
17
+
18
+ constructor(options: MacSleepInhibitorOptions = {}) {
19
+ this.platform = options.platform ?? process.platform;
20
+ this.pid = options.pid ?? process.pid;
21
+ this.caffeinatePath = options.caffeinatePath ?? DEFAULT_CAFFEINATE_PATH;
22
+ }
23
+
24
+ get isSupported(): boolean {
25
+ return this.platform === "darwin";
26
+ }
27
+
28
+ get isActive(): boolean {
29
+ return this.activeRequests > 0;
30
+ }
31
+
32
+ get isInhibiting(): boolean {
33
+ return this.child !== undefined;
34
+ }
35
+
36
+ acquire(): boolean {
37
+ if (!this.isSupported) return false;
38
+
39
+ this.activeRequests += 1;
40
+ this.start();
41
+ return this.isInhibiting;
42
+ }
43
+
44
+ release(): void {
45
+ if (!this.isSupported) return;
46
+
47
+ if (this.activeRequests > 0) this.activeRequests -= 1;
48
+ if (this.activeRequests === 0) this.stop();
49
+ }
50
+
51
+ forceStop(): void {
52
+ this.activeRequests = 0;
53
+ this.stop();
54
+ }
55
+
56
+ private start(): void {
57
+ if (this.child) return;
58
+
59
+ try {
60
+ const child = spawn(this.caffeinatePath, ["-i", "-w", String(this.pid)], {
61
+ stdio: "ignore",
62
+ });
63
+
64
+ this.child = child;
65
+ child.once("error", () => {
66
+ if (this.child === child) this.child = undefined;
67
+ });
68
+ child.once("exit", () => {
69
+ if (this.child === child) this.child = undefined;
70
+ });
71
+ child.unref();
72
+ } catch {
73
+ this.child = undefined;
74
+ }
75
+ }
76
+
77
+ private stop(): void {
78
+ const child = this.child;
79
+ if (!child) return;
80
+
81
+ this.child = undefined;
82
+ if (!child.killed) child.kill("SIGTERM");
83
+ }
84
+ }