pi-onlyne 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dbydd
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,42 @@
1
+ # pi-onlyne
2
+
3
+ Pi extension for using Onlyne as a workspace-local messaging bridge.
4
+
5
+ ## What it does
6
+
7
+ - Watches an existing Onlyne workspace.
8
+ - Starts Onlyne for the current workspace when requested.
9
+ - Subscribes to inbound channel events without polling.
10
+ - Lets the agent reply, send, or broadcast messages through tools.
11
+ - Keeps config in the project, not in global home state.
12
+
13
+ ## Requirements
14
+
15
+ - `onlyne` available on `PATH`, or set `ONLYNE_BIN`.
16
+ - A workspace with `.onlyne/` already initialized.
17
+
18
+ ## Commands
19
+
20
+ ```text
21
+ /onlyne status
22
+ /onlyne watch on
23
+ /onlyne watch off
24
+ /onlyne config auto-start
25
+ ```
26
+
27
+ ## Agent tools
28
+
29
+ ```text
30
+ onlyne_reply({ text })
31
+ onlyne_send({ channelId, conversationId, text, rawText? })
32
+ onlyne_broadcast({ targets, text, rawText? })
33
+ onlyne_mark_no_reply({ reason })
34
+ ```
35
+
36
+ Messages default to Markdown. Set `rawText: true` only when the text must be sent literally.
37
+
38
+ ## Config
39
+
40
+ Project-local config lives at `.pi/onlyne.json`. Defaults are safe: watch is manual, inbound messages auto-handle once watch is on, and outbound reply fallback is guarded.
41
+
42
+ See `SPEC.md` for behavior details.
package/SPEC.md ADDED
@@ -0,0 +1,46 @@
1
+ # pi-onlyne SPEC
2
+
3
+ ## Scope
4
+
5
+ Pi extension for Onlyne. Onlyne remains a workspace-local IM broker; this extension owns Pi session lifecycle, watch behavior, message injection, send tools, and a small config surface.
6
+
7
+ ## v1 Decisions
8
+
9
+ - Watch is configurable; default manual.
10
+ - `watch on` first connects to existing `.onlyne/run/onlyne.sock`; if unavailable, spawns `onlyne --workspace <root> run`.
11
+ - Extension-owned daemon is killed on `watch off`, `session_shutdown`, or process signal.
12
+ - Inbound events come from Onlyne `subscribe_events`; no polling.
13
+ - Inbound mode is rule-based: `auto-handle`, `queue-only`, or `muted`.
14
+ - Outbound defaults to `guarded-explicit`: prefer tool reply, fallback to final text, else send configured error text.
15
+ - Send tools default to Markdown and may pass `raw_text: true` to Onlyne for literal text.
16
+ - Broadcast sends concurrently with per-target retry and per-target results.
17
+
18
+ ## Config
19
+
20
+ Stored in project `.pi/onlyne.json`:
21
+
22
+ ```json
23
+ {
24
+ "watch": { "autoStart": false },
25
+ "inbound": { "defaultMode": "auto-handle", "rules": [] },
26
+ "outbound": {
27
+ "defaultReplyMode": "guarded-explicit",
28
+ "guardedExplicit": { "reminders": 2, "noOutputFallbackText": "Onlyne/Pi error: no valid reply was produced." },
29
+ "retry": { "attempts": 2, "concurrency": 8 }
30
+ }
31
+ }
32
+ ```
33
+
34
+ ## Tools
35
+
36
+ - `onlyne_reply({ text })`
37
+ - `onlyne_send({ channelId, conversationId, text, rawText? })`
38
+ - `onlyne_broadcast({ targets, text, rawText? })`
39
+ - `onlyne_mark_no_reply({ reason? })`
40
+
41
+ ## Deferred
42
+
43
+ - Attachments.
44
+ - Auth QR/secret editing TUI.
45
+ - Schedules.
46
+ - Target groups.
@@ -0,0 +1,32 @@
1
+ export type InboundMode = "auto-handle" | "queue-only" | "muted";
2
+ export type ReplyMode = "guarded-explicit" | "explicit-only" | "implicit-final";
3
+ export interface OnlyneRule {
4
+ channel: string;
5
+ conversation?: string;
6
+ mode: InboundMode;
7
+ }
8
+ export interface OnlyneConfig {
9
+ watch: {
10
+ autoStart: boolean;
11
+ };
12
+ inbound: {
13
+ defaultMode: InboundMode;
14
+ rules: OnlyneRule[];
15
+ };
16
+ outbound: {
17
+ defaultReplyMode: ReplyMode;
18
+ guardedExplicit: {
19
+ reminders: number;
20
+ noOutputFallbackText: string;
21
+ };
22
+ retry: {
23
+ attempts: number;
24
+ concurrency: number;
25
+ };
26
+ };
27
+ }
28
+ export declare const defaultConfig: OnlyneConfig;
29
+ export declare function configPath(cwd: string): string;
30
+ export declare function loadConfig(cwd: string): OnlyneConfig;
31
+ export declare function saveConfig(cwd: string, config: OnlyneConfig): void;
32
+ export declare function inboundModeFor(config: OnlyneConfig, channel: string, conversation?: string): InboundMode;
package/dist/config.js ADDED
@@ -0,0 +1,19 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ export const defaultConfig = {
4
+ watch: { autoStart: false },
5
+ inbound: { defaultMode: "auto-handle", rules: [] },
6
+ outbound: { defaultReplyMode: "guarded-explicit", guardedExplicit: { reminders: 2, noOutputFallbackText: "Onlyne/Pi error: no valid reply was produced." }, retry: { attempts: 2, concurrency: 8 } },
7
+ };
8
+ export function configPath(cwd) { return join(cwd, ".pi", "onlyne.json"); }
9
+ export function loadConfig(cwd) {
10
+ const path = configPath(cwd);
11
+ if (!existsSync(path))
12
+ return structuredClone(defaultConfig);
13
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
14
+ return { ...structuredClone(defaultConfig), ...parsed, watch: { ...defaultConfig.watch, ...parsed.watch }, inbound: { ...defaultConfig.inbound, ...parsed.inbound, rules: parsed.inbound?.rules ?? [] }, outbound: { ...defaultConfig.outbound, ...parsed.outbound, guardedExplicit: { ...defaultConfig.outbound.guardedExplicit, ...parsed.outbound?.guardedExplicit }, retry: { ...defaultConfig.outbound.retry, ...parsed.outbound?.retry } } };
15
+ }
16
+ export function saveConfig(cwd, config) { const path = configPath(cwd); mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`); }
17
+ export function inboundModeFor(config, channel, conversation) {
18
+ return config.inbound.rules.find((r) => r.channel === channel && r.conversation === conversation)?.mode ?? config.inbound.rules.find((r) => r.channel === channel && !r.conversation)?.mode ?? config.inbound.defaultMode;
19
+ }
@@ -0,0 +1,2 @@
1
+ import { type ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ export default function onlyne(pi: ExtensionAPI): void;
package/dist/index.js ADDED
@@ -0,0 +1,78 @@
1
+ import { defineTool } from "@earendil-works/pi-coding-agent";
2
+ import { Type } from "typebox";
3
+ import { broadcast, connectOrSpawn, sendWithRetry, stopProcess, subscribe } from "./onlyne.js";
4
+ import { inboundModeFor, loadConfig, saveConfig } from "./config.js";
5
+ import { findWorkspace } from "./workspace.js";
6
+ const state = { cwd: process.cwd(), workspace: null, watching: false, owner: "stopped" };
7
+ const textResult = (text, details) => ({ content: [{ type: "text", text }], details });
8
+ const currentConfig = () => loadConfig(state.cwd);
9
+ function inboundText(data) { const msg = data?.data?.data ?? data?.data ?? data; const channelId = msg.channel_id ?? msg.channelId; const conversationId = msg.conversation_id ?? msg.conversationId; const text = msg.text ?? msg.content ?? msg.body; return channelId && conversationId && typeof text === "string" ? { channelId, conversationId, text } : null; }
10
+ async function startWatch(pi) { state.workspace = findWorkspace(state.cwd); if (!state.workspace)
11
+ throw new Error("current workspace has no .onlyne configuration"); const conn = await connectOrSpawn(state.workspace); state.owner = conn.owner; state.child = conn.process; state.socket = subscribe(state.workspace.socketPath, (line) => { if (!line?.event || line.type !== "inbound_message")
12
+ return; const inbound = inboundText(line); if (!inbound)
13
+ return; const mode = inboundModeFor(currentConfig(), inbound.channelId, inbound.conversationId); if (mode === "muted")
14
+ return; state.currentInbound = { ...inbound, replied: false, noReply: false, reminders: 0 }; if (mode === "auto-handle")
15
+ pi.sendUserMessage(`Onlyne inbound message from ${inbound.channelId}/${inbound.conversationId}:\n\n${inbound.text}\n\nReply with onlyne_reply, or call onlyne_mark_no_reply if no reply is needed.`, { deliverAs: "followUp" }); }); state.watching = true; return `watching ${state.workspace.root} (${state.owner})`; }
16
+ function stopWatch() { state.socket?.destroy(); state.socket = undefined; if (state.owner === "extension")
17
+ stopProcess(state.child); state.child = undefined; state.watching = false; state.owner = "stopped"; return "watch stopped"; }
18
+ async function reply(text) { if (!state.workspace)
19
+ throw new Error("onlyne workspace not found"); const inbound = state.currentInbound; if (!inbound)
20
+ throw new Error("no active inbound message"); const res = await sendWithRetry(state.workspace.socketPath, { channelId: inbound.channelId, conversationId: inbound.conversationId }, text, currentConfig().outbound.retry.attempts); if (res.ok)
21
+ inbound.replied = true; return res; }
22
+ export default function onlyne(pi) {
23
+ pi.on("session_start", async (_event, ctx) => { state.cwd = ctx.cwd; state.workspace = findWorkspace(ctx.cwd); ctx.ui.setStatus("onlyne", state.workspace ? "onlyne: ready" : "onlyne: no .onlyne"); if (currentConfig().watch.autoStart && state.workspace) {
24
+ try {
25
+ ctx.ui.notify(await startWatch(pi), "info");
26
+ }
27
+ catch (e) {
28
+ ctx.ui.notify(String(e), "warning");
29
+ }
30
+ } });
31
+ pi.on("session_shutdown", async () => { stopWatch(); });
32
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"])
33
+ process.once(sig, () => stopWatch());
34
+ pi.on("message_end", async (event) => { const text = typeof event.content === "string" ? event.content.trim() : ""; if (text && !text.startsWith("{") && !text.startsWith("[onlyne-internal]"))
35
+ state.lastValidOutput = text; });
36
+ pi.on("turn_end", async () => {
37
+ const inbound = state.currentInbound;
38
+ if (!inbound || inbound.replied || inbound.noReply || !state.workspace)
39
+ return;
40
+ const cfg = currentConfig();
41
+ if (cfg.outbound.defaultReplyMode === "explicit-only")
42
+ return;
43
+ if (cfg.outbound.defaultReplyMode === "guarded-explicit" && inbound.reminders < cfg.outbound.guardedExplicit.reminders) {
44
+ if (inbound.reminders === 0)
45
+ inbound.fallbackText = state.lastValidOutput;
46
+ inbound.reminders++;
47
+ pi.sendUserMessage(`Onlyne reminder ${inbound.reminders}/${cfg.outbound.guardedExplicit.reminders}: reply to ${inbound.channelId}/${inbound.conversationId} with onlyne_reply, or call onlyne_mark_no_reply.`, { deliverAs: "followUp" });
48
+ return;
49
+ }
50
+ await reply(inbound.fallbackText || state.lastValidOutput || cfg.outbound.guardedExplicit.noOutputFallbackText);
51
+ });
52
+ pi.registerCommand("onlyne", { description: "Onlyne watch/status/config commands", handler: async (argLine, ctx) => { const [cmd, sub] = argLine.trim().split(/\s+/); try {
53
+ if (cmd === "watch" && sub === "on")
54
+ ctx.ui.notify(await startWatch(pi), "info");
55
+ else if (cmd === "watch" && sub === "off")
56
+ ctx.ui.notify(stopWatch(), "info");
57
+ else if (cmd === "status")
58
+ ctx.ui.notify(`onlyne ${state.watching ? "watching" : "stopped"}; owner=${state.owner}; workspace=${state.workspace?.root ?? "none"}`, "info");
59
+ else if (cmd === "config" && sub === "auto-start") {
60
+ const cfg = currentConfig();
61
+ cfg.watch.autoStart = !cfg.watch.autoStart;
62
+ saveConfig(state.cwd, cfg);
63
+ ctx.ui.notify(`autoStart=${cfg.watch.autoStart}`, "info");
64
+ }
65
+ else
66
+ ctx.ui.notify("usage: /onlyne status | watch on|off | config auto-start", "info");
67
+ }
68
+ catch (e) {
69
+ ctx.ui.notify(e instanceof Error ? e.message : String(e), "error");
70
+ } } });
71
+ pi.registerTool(defineTool({ name: "onlyne_reply", label: "Onlyne reply", description: "Reply with plain text to the current Onlyne inbound message.", parameters: Type.Object({ text: Type.String() }), executionMode: "parallel", async execute(_id, params) { return textResult(JSON.stringify(await reply(params.text))); } }));
72
+ pi.registerTool(defineTool({ name: "onlyne_send", label: "Onlyne send", description: "Send Markdown to one Onlyne channel conversation. Set rawText=true only for literal plain text.", parameters: Type.Object({ channelId: Type.String(), conversationId: Type.String(), text: Type.String(), rawText: Type.Optional(Type.Boolean()) }), executionMode: "parallel", async execute(_id, params) { if (!state.workspace)
73
+ throw new Error("onlyne workspace not found"); const res = await sendWithRetry(state.workspace.socketPath, params, params.text, currentConfig().outbound.retry.attempts, params.rawText ?? false); return textResult(JSON.stringify(res), res); } }));
74
+ pi.registerTool(defineTool({ name: "onlyne_broadcast", label: "Onlyne broadcast", description: "Send Markdown to many Onlyne channel conversations concurrently. Set rawText=true only for literal plain text.", parameters: Type.Object({ targets: Type.Array(Type.Object({ channelId: Type.String(), conversationId: Type.String() })), text: Type.String(), rawText: Type.Optional(Type.Boolean()) }), executionMode: "parallel", async execute(_id, params) { if (!state.workspace)
75
+ throw new Error("onlyne workspace not found"); const cfg = currentConfig(); const results = await broadcast(state.workspace.socketPath, params.targets, params.text, cfg.outbound.retry.attempts, cfg.outbound.retry.concurrency, params.rawText ?? false); return textResult(JSON.stringify({ ok: results.every((r) => r.ok), results }), results); } }));
76
+ pi.registerTool(defineTool({ name: "onlyne_mark_no_reply", label: "Onlyne no reply", description: "Mark the current Onlyne inbound message as intentionally not replied.", parameters: Type.Object({ reason: Type.Optional(Type.String()) }), executionMode: "parallel", async execute(_id, params) { if (state.currentInbound)
77
+ state.currentInbound.noReply = true; return textResult("marked no reply", params); } }));
78
+ }
@@ -0,0 +1,32 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ import { type Socket } from "node:net";
3
+ import type { Workspace } from "./workspace.js";
4
+ export interface OnlyneRequest {
5
+ id: string;
6
+ op: string;
7
+ channel_id?: string;
8
+ conversation_id?: string;
9
+ text?: string;
10
+ format?: "plain" | "markdown";
11
+ raw_text?: boolean;
12
+ limit?: number;
13
+ }
14
+ export interface SendTarget {
15
+ channelId: string;
16
+ conversationId: string;
17
+ }
18
+ export interface SendResult extends SendTarget {
19
+ ok: boolean;
20
+ error?: string;
21
+ }
22
+ export declare function request(socketPath: string, req: OnlyneRequest): Promise<any>;
23
+ export declare function subscribe(socketPath: string, onLine: (line: any) => void): Socket;
24
+ export declare function spawnDaemon(ws: Workspace, onlyneBin?: string): ChildProcess;
25
+ export declare function waitForSocket(socketPath: string, timeoutMs?: number): Promise<void>;
26
+ export declare function connectOrSpawn(ws: Workspace): Promise<{
27
+ owner: "external" | "extension";
28
+ process?: ChildProcess;
29
+ }>;
30
+ export declare function stopProcess(child?: ChildProcess): void;
31
+ export declare function sendWithRetry(socketPath: string, target: SendTarget, text: string, attempts: number, rawText?: boolean): Promise<SendResult>;
32
+ export declare function broadcast(socketPath: string, targets: SendTarget[], text: string, attempts: number, concurrency: number, rawText?: boolean): Promise<SendResult[]>;
package/dist/onlyne.js ADDED
@@ -0,0 +1,109 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createConnection } from "node:net";
3
+ export function request(socketPath, req) {
4
+ return new Promise((resolve, reject) => {
5
+ const socket = createConnection(socketPath);
6
+ let data = "";
7
+ socket.setEncoding("utf8");
8
+ socket.on("error", reject);
9
+ socket.on("connect", () => socket.write(`${JSON.stringify(req)}\n`));
10
+ socket.on("data", (chunk) => { data += chunk; const idx = data.indexOf("\n"); if (idx >= 0) {
11
+ socket.end();
12
+ try {
13
+ resolve(JSON.parse(data.slice(0, idx)));
14
+ }
15
+ catch (e) {
16
+ reject(e);
17
+ }
18
+ } });
19
+ });
20
+ }
21
+ export function subscribe(socketPath, onLine) {
22
+ const socket = createConnection(socketPath);
23
+ let buf = "";
24
+ socket.setEncoding("utf8");
25
+ socket.on("connect", () => socket.write('{"id":"sub","op":"subscribe_events"}\n'));
26
+ socket.on("data", (chunk) => { buf += chunk; for (;;) {
27
+ const idx = buf.indexOf("\n");
28
+ if (idx < 0)
29
+ break;
30
+ const raw = buf.slice(0, idx);
31
+ buf = buf.slice(idx + 1);
32
+ if (!raw.trim())
33
+ continue;
34
+ try {
35
+ onLine(JSON.parse(raw));
36
+ }
37
+ catch { /* ignore */ }
38
+ } });
39
+ return socket;
40
+ }
41
+ export function spawnDaemon(ws, onlyneBin = process.env.ONLYNE_BIN ?? "onlyne") {
42
+ const script = `
43
+ set -eu
44
+ parent="$1"
45
+ shift
46
+ "$@" &
47
+ child=$!
48
+ cleanup() { kill "$child" 2>/dev/null || true; wait "$child" 2>/dev/null || true; }
49
+ trap cleanup INT TERM HUP EXIT
50
+ while kill -0 "$parent" 2>/dev/null && kill -0 "$child" 2>/dev/null; do sleep 1; done
51
+ cleanup
52
+ `;
53
+ return spawn("sh", ["-c", script, "onlyne-supervisor", String(process.pid), onlyneBin, "--workspace", ws.root, "run"], { cwd: ws.root, stdio: "ignore" });
54
+ }
55
+ export async function waitForSocket(socketPath, timeoutMs = 5000) {
56
+ const deadline = Date.now() + timeoutMs;
57
+ let last;
58
+ while (Date.now() < deadline) {
59
+ try {
60
+ await request(socketPath, { id: "ping", op: "ping" });
61
+ return;
62
+ }
63
+ catch (e) {
64
+ last = e;
65
+ }
66
+ await new Promise((r) => setTimeout(r, 100));
67
+ }
68
+ throw last instanceof Error ? last : new Error("onlyne socket not ready");
69
+ }
70
+ export async function connectOrSpawn(ws) {
71
+ try {
72
+ await request(ws.socketPath, { id: "ping", op: "ping" });
73
+ return { owner: "external" };
74
+ }
75
+ catch { /* spawn */ }
76
+ const process = spawnDaemon(ws);
77
+ await waitForSocket(ws.socketPath);
78
+ return { owner: "extension", process };
79
+ }
80
+ export function stopProcess(child) { if (!child || child.killed)
81
+ return; child.kill("SIGTERM"); setTimeout(() => { if (!child.killed)
82
+ child.kill("SIGKILL"); }, 1500).unref(); }
83
+ export async function sendWithRetry(socketPath, target, text, attempts, rawText = false) {
84
+ let error = "unknown error";
85
+ for (let i = 0; i < Math.max(1, attempts); i++) {
86
+ try {
87
+ const res = await request(socketPath, { id: `send-${Date.now()}-${i}`, op: "send_message", channel_id: target.channelId, conversation_id: target.conversationId, text, raw_text: rawText });
88
+ if (res.ok)
89
+ return { ...target, ok: true };
90
+ error = res.error?.message ?? JSON.stringify(res.error ?? res);
91
+ }
92
+ catch (e) {
93
+ error = e instanceof Error ? e.message : String(e);
94
+ }
95
+ }
96
+ return { ...target, ok: false, error };
97
+ }
98
+ export async function broadcast(socketPath, targets, text, attempts, concurrency, rawText = false) {
99
+ const out = [];
100
+ let next = 0;
101
+ async function worker() { for (;;) {
102
+ const i = next++;
103
+ if (i >= targets.length)
104
+ return;
105
+ out[i] = await sendWithRetry(socketPath, targets[i], text, attempts, rawText);
106
+ } }
107
+ await Promise.all(Array.from({ length: Math.max(1, Math.min(concurrency, targets.length)) }, worker));
108
+ return out;
109
+ }
@@ -0,0 +1,6 @@
1
+ export interface Workspace {
2
+ root: string;
3
+ onlyneDir: string;
4
+ socketPath: string;
5
+ }
6
+ export declare function findWorkspace(start: string): Workspace | null;
@@ -0,0 +1,14 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ export function findWorkspace(start) {
4
+ let dir = resolve(start);
5
+ for (;;) {
6
+ const onlyneDir = join(dir, ".onlyne");
7
+ if (existsSync(onlyneDir))
8
+ return { root: dir, onlyneDir, socketPath: join(onlyneDir, "run", "onlyne.sock") };
9
+ const parent = dirname(dir);
10
+ if (parent === dir)
11
+ return null;
12
+ dir = parent;
13
+ }
14
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "pi-onlyne",
3
+ "version": "0.2.2",
4
+ "description": "Pi extension tools for sending messages through Onlyne.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": "./dist/index.js",
9
+ "files": [
10
+ "dist",
11
+ "README.md",
12
+ "SPEC.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.json",
17
+ "check": "npm run build && node --test test/*.test.mjs",
18
+ "prepack": "npm run build",
19
+ "prepublishOnly": "npm run check"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/dbydd/pi-onlyne.git"
24
+ },
25
+ "keywords": [
26
+ "pi",
27
+ "pi-package",
28
+ "onlyne",
29
+ "messaging",
30
+ "extension"
31
+ ],
32
+ "author": "dbydd <1992003927@qq.com>",
33
+ "license": "MIT",
34
+ "publishConfig": {
35
+ "access": "public",
36
+ "registry": "https://registry.npmjs.org/"
37
+ },
38
+ "dependencies": {
39
+ "@earendil-works/pi-ai": "^0.79.10",
40
+ "@earendil-works/pi-coding-agent": "^0.79.10",
41
+ "typebox": "^1.2.16"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^22.15.21",
45
+ "typescript": "^5.8.3"
46
+ }
47
+ }