@streichsbaer/pi-mesh 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/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +796 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/live-socket.d.ts +17 -0
- package/dist/live-socket.js +116 -0
- package/dist/lock.d.ts +7 -0
- package/dist/lock.js +82 -0
- package/dist/mesh.d.ts +3 -0
- package/dist/mesh.js +22 -0
- package/dist/model-list.d.ts +48 -0
- package/dist/model-list.js +208 -0
- package/dist/model-selection.d.ts +47 -0
- package/dist/model-selection.js +171 -0
- package/dist/pi-runner.d.ts +39 -0
- package/dist/pi-runner.js +182 -0
- package/dist/pi-session-parser.d.ts +57 -0
- package/dist/pi-session-parser.js +459 -0
- package/dist/registry.d.ts +24 -0
- package/dist/registry.js +139 -0
- package/dist/types.d.ts +142 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +17 -0
- package/dist/utils.js +157 -0
- package/package.json +72 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
export type DeliveryMode = "auto" | "prompt" | "steer" | "follow-up";
|
|
2
|
+
export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
3
|
+
export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
4
|
+
export interface ModelSelection {
|
|
5
|
+
provider?: string;
|
|
6
|
+
model?: string;
|
|
7
|
+
thinkingLevel?: ThinkingLevel;
|
|
8
|
+
}
|
|
9
|
+
export interface SessionSummary {
|
|
10
|
+
path: string;
|
|
11
|
+
rawSessionId: string;
|
|
12
|
+
cwd: string;
|
|
13
|
+
repoName: string;
|
|
14
|
+
timestamp: string | null;
|
|
15
|
+
updatedAt: number;
|
|
16
|
+
sessionName: string;
|
|
17
|
+
dashboardSessionId: string;
|
|
18
|
+
workflowId: string;
|
|
19
|
+
providerId: string;
|
|
20
|
+
laneId: string;
|
|
21
|
+
firstUser: string;
|
|
22
|
+
lastUser: string;
|
|
23
|
+
lastAssistant: string;
|
|
24
|
+
}
|
|
25
|
+
export interface TranscriptHeader {
|
|
26
|
+
id: string;
|
|
27
|
+
version?: number;
|
|
28
|
+
cwd?: string;
|
|
29
|
+
timestamp?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface TranscriptEntry {
|
|
32
|
+
type: string;
|
|
33
|
+
id?: string;
|
|
34
|
+
parentId?: string | null;
|
|
35
|
+
timestamp?: string;
|
|
36
|
+
message?: AgentMessageLike;
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
export interface AgentMessageLike {
|
|
40
|
+
role?: string;
|
|
41
|
+
content?: string | ContentPartLike[];
|
|
42
|
+
toolName?: string;
|
|
43
|
+
toolCallId?: string | null;
|
|
44
|
+
isError?: boolean;
|
|
45
|
+
details?: unknown;
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
export type ContentPartLike = {
|
|
49
|
+
type: "text";
|
|
50
|
+
text?: string;
|
|
51
|
+
} | {
|
|
52
|
+
type: "thinking";
|
|
53
|
+
thinking?: string;
|
|
54
|
+
} | {
|
|
55
|
+
type: "toolCall";
|
|
56
|
+
id?: string;
|
|
57
|
+
name?: string;
|
|
58
|
+
arguments?: Record<string, unknown>;
|
|
59
|
+
} | {
|
|
60
|
+
type: string;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
};
|
|
63
|
+
export interface TranscriptEvent {
|
|
64
|
+
kind: "message" | "tool_call" | "tool_result";
|
|
65
|
+
role: string;
|
|
66
|
+
id: string | null;
|
|
67
|
+
parentId?: string | null;
|
|
68
|
+
parentMessageId?: string | null;
|
|
69
|
+
timestamp: string | null;
|
|
70
|
+
timestampMs: number | null;
|
|
71
|
+
text?: string;
|
|
72
|
+
thinkingParts?: number;
|
|
73
|
+
toolName?: string;
|
|
74
|
+
toolCallId?: string | null;
|
|
75
|
+
args?: Record<string, unknown>;
|
|
76
|
+
isError?: boolean;
|
|
77
|
+
details?: unknown;
|
|
78
|
+
}
|
|
79
|
+
export interface ToolInvocation {
|
|
80
|
+
toolCallId: string | null;
|
|
81
|
+
toolName: string;
|
|
82
|
+
startedAt: string | null;
|
|
83
|
+
startedAtMs: number | null;
|
|
84
|
+
endedAt?: string | null;
|
|
85
|
+
endedAtMs?: number | null;
|
|
86
|
+
args: Record<string, unknown>;
|
|
87
|
+
argsSummary: string;
|
|
88
|
+
callEvent: TranscriptEvent | null;
|
|
89
|
+
resultEvent: TranscriptEvent | null;
|
|
90
|
+
status: "pending" | "ok" | "error";
|
|
91
|
+
failed: boolean;
|
|
92
|
+
exitCode: number | null;
|
|
93
|
+
}
|
|
94
|
+
export interface Turn {
|
|
95
|
+
index: number;
|
|
96
|
+
startedAt: string | null;
|
|
97
|
+
startedAtMs: number | null;
|
|
98
|
+
endedAt: string | null;
|
|
99
|
+
endedAtMs: number | null;
|
|
100
|
+
events: TranscriptEvent[];
|
|
101
|
+
user: TranscriptEvent;
|
|
102
|
+
assistantMessages: TranscriptEvent[];
|
|
103
|
+
toolInvocations: ToolInvocation[];
|
|
104
|
+
failures: ToolInvocation[];
|
|
105
|
+
finalAssistant: TranscriptEvent | null;
|
|
106
|
+
}
|
|
107
|
+
export interface SessionData {
|
|
108
|
+
session: SessionSummary;
|
|
109
|
+
transcript: {
|
|
110
|
+
header: TranscriptHeader;
|
|
111
|
+
entries: TranscriptEntry[];
|
|
112
|
+
};
|
|
113
|
+
events: TranscriptEvent[];
|
|
114
|
+
toolInvocations: ToolInvocation[];
|
|
115
|
+
turns: Turn[];
|
|
116
|
+
}
|
|
117
|
+
export type ManagedSessionKind = "interactive" | "sleeping" | "attached";
|
|
118
|
+
export type ManagedSessionStatus = "offline" | "starting" | "running" | "idle" | "busy" | "error";
|
|
119
|
+
export interface ManagedSessionRecord {
|
|
120
|
+
meshId: string;
|
|
121
|
+
name?: string;
|
|
122
|
+
labels?: string[];
|
|
123
|
+
kind: ManagedSessionKind;
|
|
124
|
+
status: ManagedSessionStatus;
|
|
125
|
+
folder: string;
|
|
126
|
+
sessionFile: string;
|
|
127
|
+
rawSessionId?: string;
|
|
128
|
+
pid?: number;
|
|
129
|
+
socketPath?: string;
|
|
130
|
+
createdAt: string;
|
|
131
|
+
updatedAt: string;
|
|
132
|
+
lastError?: string;
|
|
133
|
+
pendingModelSelection?: ModelSelection;
|
|
134
|
+
}
|
|
135
|
+
export interface MeshPaths {
|
|
136
|
+
id: "local";
|
|
137
|
+
baseDir: string;
|
|
138
|
+
registryFile: string;
|
|
139
|
+
inboxDir: string;
|
|
140
|
+
locksDir: string;
|
|
141
|
+
socketDirFile: string;
|
|
142
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function homeDir(): string;
|
|
2
|
+
export declare function expandHome(value: string): string;
|
|
3
|
+
export declare function piAgentDir(): string;
|
|
4
|
+
export declare function exists(filePath: string): Promise<boolean>;
|
|
5
|
+
export declare function ensureDir(dir: string): Promise<void>;
|
|
6
|
+
export declare function ensurePrivateDir(dir: string): Promise<void>;
|
|
7
|
+
export declare function userRuntimeId(): string;
|
|
8
|
+
export declare function socketRuntimePrefix(): string;
|
|
9
|
+
export declare function readJson<T>(filePath: string, fallback: T): Promise<T>;
|
|
10
|
+
export declare function safeJson<T = unknown>(line: string, fallback: T): T;
|
|
11
|
+
export declare function walkFiles(root: string, predicate?: (filePath: string) => boolean, out?: string[]): Promise<string[]>;
|
|
12
|
+
export declare function compactWhitespace(text: string): string;
|
|
13
|
+
export declare function truncate(text: string, max?: number): string;
|
|
14
|
+
export declare function formatTimestamp(msOrIso: number | string | null | undefined): string;
|
|
15
|
+
export declare function stableId(value: string, length?: number): string;
|
|
16
|
+
export declare function parseDuration(value: string): number | null;
|
|
17
|
+
export declare function parseTimeSpec(value: string, now?: number): number | null;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
export function homeDir() {
|
|
6
|
+
return os.homedir();
|
|
7
|
+
}
|
|
8
|
+
export function expandHome(value) {
|
|
9
|
+
if (value === "~")
|
|
10
|
+
return homeDir();
|
|
11
|
+
if (value.startsWith("~/"))
|
|
12
|
+
return path.join(homeDir(), value.slice(2));
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
export function piAgentDir() {
|
|
16
|
+
return path.join(homeDir(), ".pi", "agent");
|
|
17
|
+
}
|
|
18
|
+
export async function exists(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
await fs.access(filePath);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function ensureDir(dir) {
|
|
28
|
+
await fs.mkdir(dir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
export async function ensurePrivateDir(dir) {
|
|
31
|
+
let stat;
|
|
32
|
+
try {
|
|
33
|
+
stat = await fs.lstat(dir);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error.code !== "ENOENT")
|
|
37
|
+
throw error;
|
|
38
|
+
await fs.mkdir(dir, { mode: 0o700 });
|
|
39
|
+
stat = await fs.lstat(dir);
|
|
40
|
+
}
|
|
41
|
+
if (stat.isSymbolicLink())
|
|
42
|
+
throw new Error(`${dir} must not be a symbolic link`);
|
|
43
|
+
if (!stat.isDirectory())
|
|
44
|
+
throw new Error(`${dir} is not a directory`);
|
|
45
|
+
if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
|
|
46
|
+
throw new Error(`${dir} is not owned by the current user`);
|
|
47
|
+
}
|
|
48
|
+
if ((stat.mode & 0o077) !== 0)
|
|
49
|
+
await fs.chmod(dir, 0o700);
|
|
50
|
+
}
|
|
51
|
+
export function userRuntimeId() {
|
|
52
|
+
if (typeof process.getuid === "function")
|
|
53
|
+
return String(process.getuid());
|
|
54
|
+
return stableId(homeDir(), 8);
|
|
55
|
+
}
|
|
56
|
+
export function socketRuntimePrefix() {
|
|
57
|
+
const base = process.platform === "win32" ? os.tmpdir() : "/tmp";
|
|
58
|
+
return path.join(base, `pi-mesh-${userRuntimeId()}-`);
|
|
59
|
+
}
|
|
60
|
+
export async function readJson(filePath, fallback) {
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function safeJson(line, fallback) {
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(line);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return fallback;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export async function walkFiles(root, predicate = () => true, out = []) {
|
|
77
|
+
let entries = [];
|
|
78
|
+
try {
|
|
79
|
+
entries = await fs.readdir(root, { withFileTypes: true });
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
const full = path.join(root, entry.name);
|
|
86
|
+
if (entry.isDirectory())
|
|
87
|
+
await walkFiles(full, predicate, out);
|
|
88
|
+
else if (entry.isFile() && predicate(full))
|
|
89
|
+
out.push(full);
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
export function compactWhitespace(text) {
|
|
94
|
+
return String(text || "").replace(/\s+/g, " ").trim();
|
|
95
|
+
}
|
|
96
|
+
export function truncate(text, max = 220) {
|
|
97
|
+
const value = String(text || "").trim();
|
|
98
|
+
if (value.length <= max)
|
|
99
|
+
return value;
|
|
100
|
+
return `${value.slice(0, Math.max(0, max - 1))}…`;
|
|
101
|
+
}
|
|
102
|
+
export function formatTimestamp(msOrIso) {
|
|
103
|
+
if (!msOrIso)
|
|
104
|
+
return "n/a";
|
|
105
|
+
if (typeof msOrIso === "string")
|
|
106
|
+
return msOrIso;
|
|
107
|
+
try {
|
|
108
|
+
return new Date(msOrIso).toISOString();
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return String(msOrIso);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export function stableId(value, length = 16) {
|
|
115
|
+
return createHash("sha256").update(value).digest("hex").slice(0, length);
|
|
116
|
+
}
|
|
117
|
+
export function parseDuration(value) {
|
|
118
|
+
const text = String(value || "").trim();
|
|
119
|
+
if (!text)
|
|
120
|
+
return null;
|
|
121
|
+
if (/^\d+$/.test(text))
|
|
122
|
+
return Number(text);
|
|
123
|
+
const matches = [...text.matchAll(/(\d+)(ms|s|m|h|d|w)/g)];
|
|
124
|
+
if (!matches.length || matches.map((m) => m[0]).join("") !== text)
|
|
125
|
+
return null;
|
|
126
|
+
let total = 0;
|
|
127
|
+
for (const [, rawNum, unit] of matches) {
|
|
128
|
+
const n = Number(rawNum);
|
|
129
|
+
if (unit === "ms")
|
|
130
|
+
total += n;
|
|
131
|
+
if (unit === "s")
|
|
132
|
+
total += n * 1000;
|
|
133
|
+
if (unit === "m")
|
|
134
|
+
total += n * 60 * 1000;
|
|
135
|
+
if (unit === "h")
|
|
136
|
+
total += n * 60 * 60 * 1000;
|
|
137
|
+
if (unit === "d")
|
|
138
|
+
total += n * 24 * 60 * 60 * 1000;
|
|
139
|
+
if (unit === "w")
|
|
140
|
+
total += n * 7 * 24 * 60 * 60 * 1000;
|
|
141
|
+
}
|
|
142
|
+
return total;
|
|
143
|
+
}
|
|
144
|
+
export function parseTimeSpec(value, now = Date.now()) {
|
|
145
|
+
const text = String(value || "").trim();
|
|
146
|
+
if (!text)
|
|
147
|
+
return null;
|
|
148
|
+
const duration = parseDuration(text);
|
|
149
|
+
if (duration !== null)
|
|
150
|
+
return now - duration;
|
|
151
|
+
if (/^\d{10,13}$/.test(text)) {
|
|
152
|
+
const num = Number(text);
|
|
153
|
+
return text.length === 10 ? num * 1000 : num;
|
|
154
|
+
}
|
|
155
|
+
const parsed = Date.parse(text);
|
|
156
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
157
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@streichsbaer/pi-mesh",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI and skill for coordinating Pi sessions through a local file/socket mesh",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"packageManager": "npm@11.17.0",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pi-mesh": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"clean": "shx rm -rf dist",
|
|
23
|
+
"dev": "tsx src/cli.ts",
|
|
24
|
+
"build": "npm run clean && tsc -p tsconfig.build.json && shx chmod +x dist/cli.js",
|
|
25
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
26
|
+
"check:test": "tsc -p tsconfig.test.json --noEmit",
|
|
27
|
+
"test": "vitest --run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"test:e2e:real": "tsx test/e2e/real-cli.ts",
|
|
30
|
+
"test:e2e:interactive": "tsx test/e2e/interactive-cli.ts",
|
|
31
|
+
"ci": "npm run check && npm run check:test && npm run build && npm test",
|
|
32
|
+
"prepublishOnly": "npm run clean && npm run ci"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@earendil-works/pi-coding-agent": "^0.80.2"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^24.12.4",
|
|
39
|
+
"node-pty": "^1.1.0",
|
|
40
|
+
"shx": "^0.4.0",
|
|
41
|
+
"tsx": "^4.20.6",
|
|
42
|
+
"typescript": "^5.9.3",
|
|
43
|
+
"vitest": "^4.1.9"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"pi",
|
|
47
|
+
"coding-agent",
|
|
48
|
+
"agents",
|
|
49
|
+
"cli",
|
|
50
|
+
"coordination"
|
|
51
|
+
],
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "git+https://github.com/streichsbaer/pi-mesh.git"
|
|
55
|
+
},
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/streichsbaer/pi-mesh/issues"
|
|
58
|
+
},
|
|
59
|
+
"homepage": "https://github.com/streichsbaer/pi-mesh#readme",
|
|
60
|
+
"author": "",
|
|
61
|
+
"license": "MIT",
|
|
62
|
+
"publishConfig": {
|
|
63
|
+
"access": "public"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=24.18.0"
|
|
67
|
+
},
|
|
68
|
+
"volta": {
|
|
69
|
+
"node": "24.18.0",
|
|
70
|
+
"npm": "11.17.0"
|
|
71
|
+
}
|
|
72
|
+
}
|