@astrofoundry/pi-astro 0.2.1

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.
@@ -0,0 +1,162 @@
1
+ import { spawn } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import type { AgentConfig } from "./discovery.ts";
6
+
7
+ interface AssistantMessageContent {
8
+ type: string;
9
+ text?: string;
10
+ }
11
+
12
+ interface AssistantMessage {
13
+ role?: string;
14
+ content?: AssistantMessageContent[];
15
+ }
16
+
17
+ interface AgentEndEvent {
18
+ type: "agent_end";
19
+ messages?: AssistantMessage[];
20
+ }
21
+
22
+ interface UnknownEvent {
23
+ type?: string;
24
+ [key: string]: unknown;
25
+ }
26
+
27
+ export interface SpawnResult {
28
+ text: string;
29
+ exitCode: number;
30
+ stderr: string;
31
+ }
32
+
33
+ function getPiInvocation(args: string[]): { command: string; args: string[] } {
34
+ const currentScript = process.argv[1];
35
+ const isBunVirtualScript = currentScript?.startsWith("/$bunfs/root/");
36
+ if (currentScript && !isBunVirtualScript && fs.existsSync(currentScript)) {
37
+ return { command: process.execPath, args: [currentScript, ...args] };
38
+ }
39
+
40
+ const execName = path.basename(process.execPath).toLowerCase();
41
+ const isGenericRuntime = /^(node|bun)(\.exe)?$/.test(execName);
42
+ if (!isGenericRuntime) {
43
+ return { command: process.execPath, args };
44
+ }
45
+
46
+ return { command: "pi", args };
47
+ }
48
+
49
+ function extractTextFromMessages(messages: AssistantMessage[]): string {
50
+ for (let i = messages.length - 1; i >= 0; i--) {
51
+ const msg = messages[i];
52
+ if (msg.role !== "assistant" || !msg.content) continue;
53
+ const text = msg.content
54
+ .filter((part) => part.type === "text" && typeof part.text === "string")
55
+ .map((part) => part.text)
56
+ .join("\n")
57
+ .trim();
58
+ if (text.length > 0) return text;
59
+ }
60
+ return "";
61
+ }
62
+
63
+ function writeSystemPromptTempFile(agentName: string, prompt: string): { dir: string; filePath: string } {
64
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), `astro-agent-${agentName}-`));
65
+ const filePath = path.join(dir, "system-prompt.txt");
66
+ fs.writeFileSync(filePath, prompt, "utf-8");
67
+ return { dir, filePath };
68
+ }
69
+
70
+ export interface RunAgentOptions {
71
+ agent: AgentConfig;
72
+ task: string;
73
+ cwd: string;
74
+ signal?: AbortSignal | undefined;
75
+ }
76
+
77
+ export async function runAgent(options: RunAgentOptions): Promise<SpawnResult> {
78
+ const { agent, task, cwd, signal } = options;
79
+
80
+ const args = ["--mode", "json", "-p", "--no-session"];
81
+ let tmpDir: string | null = null;
82
+
83
+ if (agent.systemPrompt.trim()) {
84
+ const tmp = writeSystemPromptTempFile(agent.name, agent.systemPrompt);
85
+ tmpDir = tmp.dir;
86
+ args.push("--append-system-prompt", tmp.filePath);
87
+ }
88
+
89
+ args.push(`Task: ${task}`);
90
+
91
+ try {
92
+ return await new Promise<SpawnResult>((resolve) => {
93
+ const invocation = getPiInvocation(args);
94
+ const proc = spawn(invocation.command, invocation.args, {
95
+ cwd,
96
+ shell: false,
97
+ stdio: ["ignore", "pipe", "pipe"],
98
+ });
99
+
100
+ let stdoutBuffer = "";
101
+ let stderrBuffer = "";
102
+ const collectedMessages: AssistantMessage[] = [];
103
+
104
+ const handleLine = (line: string): void => {
105
+ if (!line.trim()) return;
106
+ let event: UnknownEvent;
107
+ try {
108
+ event = JSON.parse(line) as UnknownEvent;
109
+ } catch {
110
+ return;
111
+ }
112
+ if (event.type === "agent_end") {
113
+ const end = event as AgentEndEvent;
114
+ if (end.messages) collectedMessages.push(...end.messages);
115
+ }
116
+ };
117
+
118
+ proc.stdout.on("data", (chunk: Buffer) => {
119
+ stdoutBuffer += chunk.toString("utf-8");
120
+ let newlineIndex = stdoutBuffer.indexOf("\n");
121
+ while (newlineIndex !== -1) {
122
+ const line = stdoutBuffer.slice(0, newlineIndex);
123
+ stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1);
124
+ handleLine(line);
125
+ newlineIndex = stdoutBuffer.indexOf("\n");
126
+ }
127
+ });
128
+
129
+ proc.stderr.on("data", (chunk: Buffer) => {
130
+ stderrBuffer += chunk.toString("utf-8");
131
+ });
132
+
133
+ const abortHandler = (): void => {
134
+ proc.kill("SIGTERM");
135
+ };
136
+ signal?.addEventListener("abort", abortHandler, { once: true });
137
+
138
+ proc.on("close", (code) => {
139
+ signal?.removeEventListener("abort", abortHandler);
140
+ if (stdoutBuffer.trim()) handleLine(stdoutBuffer);
141
+ resolve({
142
+ text: extractTextFromMessages(collectedMessages),
143
+ exitCode: code ?? 0,
144
+ stderr: stderrBuffer,
145
+ });
146
+ });
147
+
148
+ proc.on("error", (err) => {
149
+ signal?.removeEventListener("abort", abortHandler);
150
+ resolve({
151
+ text: "",
152
+ exitCode: 1,
153
+ stderr: stderrBuffer + "\n" + err.message,
154
+ });
155
+ });
156
+ });
157
+ } finally {
158
+ if (tmpDir) {
159
+ fs.rmSync(tmpDir, { recursive: true, force: true });
160
+ }
161
+ }
162
+ }
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@astrofoundry/pi-astro",
3
+ "version": "0.2.1",
4
+ "description": "Personal pi customizations (extensions, skills, prompts, themes) for the pi coding agent.",
5
+ "keywords": [
6
+ "pi-package"
7
+ ],
8
+ "license": "ISC",
9
+ "type": "module",
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "pi": {
14
+ "extensions": [
15
+ "./extensions"
16
+ ],
17
+ "skills": [
18
+ "./skills"
19
+ ],
20
+ "prompts": [
21
+ "./prompts"
22
+ ],
23
+ "themes": [
24
+ "./themes"
25
+ ]
26
+ },
27
+ "files": [
28
+ "extensions",
29
+ "skills",
30
+ "prompts",
31
+ "themes",
32
+ "README.md"
33
+ ],
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/astrofoundry/pi-astro.git"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/astrofoundry/pi-astro/issues"
40
+ },
41
+ "homepage": "https://github.com/astrofoundry/pi-astro#readme",
42
+ "peerDependencies": {
43
+ "@mariozechner/pi-agent-core": "*",
44
+ "@mariozechner/pi-ai": "*",
45
+ "@mariozechner/pi-coding-agent": "*",
46
+ "@mariozechner/pi-tui": "*",
47
+ "typebox": "*"
48
+ },
49
+ "devDependencies": {
50
+ "@eslint/js": "^10.0.1",
51
+ "@mariozechner/pi-agent-core": "^0.70.0",
52
+ "@mariozechner/pi-ai": "^0.70.0",
53
+ "@mariozechner/pi-coding-agent": "^0.70.0",
54
+ "@mariozechner/pi-tui": "^0.70.0",
55
+ "@types/node": "^25.6.0",
56
+ "eslint": "^10.2.1",
57
+ "globals": "^17.5.0",
58
+ "typebox": "^1.1.33",
59
+ "typescript": "^6.0.3",
60
+ "typescript-eslint": "^8.59.0",
61
+ "vitest": "^4.1.5"
62
+ },
63
+ "scripts": {
64
+ "test": "vitest run --passWithNoTests",
65
+ "lint": "eslint .",
66
+ "typecheck": "tsc --noEmit",
67
+ "check": "tsc --noEmit && eslint . && vitest run --passWithNoTests",
68
+ "release:patch": "v=$(pnpm version patch --no-git-tag-version) && git add -A && git commit -m \"Release $v\" && git tag $v && git push && git push origin $v",
69
+ "release:minor": "v=$(pnpm version minor --no-git-tag-version) && git add -A && git commit -m \"Release $v\" && git tag $v && git push && git push origin $v",
70
+ "release:major": "v=$(pnpm version major --no-git-tag-version) && git add -A && git commit -m \"Release $v\" && git tag $v && git push && git push origin $v"
71
+ }
72
+ }
File without changes
File without changes
File without changes