@cremini/skillpack 1.0.9 → 1.1.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.
Files changed (40) hide show
  1. package/README.md +4 -4
  2. package/dist/cli.js +4 -0
  3. package/package.json +4 -3
  4. package/runtime/README.md +11 -1
  5. package/runtime/server/dist/adapters/markdown.js +74 -0
  6. package/runtime/server/dist/adapters/markdown.js.map +1 -0
  7. package/runtime/server/dist/adapters/slack.js +369 -0
  8. package/runtime/server/dist/adapters/slack.js.map +1 -0
  9. package/runtime/server/dist/adapters/telegram.js +199 -0
  10. package/runtime/server/dist/adapters/telegram.js.map +1 -0
  11. package/runtime/server/dist/adapters/types.js +2 -0
  12. package/runtime/server/dist/adapters/types.js.map +1 -0
  13. package/runtime/server/dist/adapters/web.js +201 -0
  14. package/runtime/server/dist/adapters/web.js.map +1 -0
  15. package/runtime/server/dist/agent.js +223 -0
  16. package/runtime/server/dist/agent.js.map +1 -0
  17. package/runtime/server/dist/config.js +79 -0
  18. package/runtime/server/dist/config.js.map +1 -0
  19. package/runtime/server/dist/index.js +146 -0
  20. package/runtime/server/dist/index.js.map +1 -0
  21. package/runtime/server/dist/lifecycle.js +85 -0
  22. package/runtime/server/dist/lifecycle.js.map +1 -0
  23. package/runtime/server/dist/memory.js +195 -0
  24. package/runtime/server/dist/memory.js.map +1 -0
  25. package/runtime/server/package-lock.json +4028 -244
  26. package/runtime/server/package.json +13 -3
  27. package/runtime/start.bat +40 -4
  28. package/runtime/start.sh +30 -2
  29. package/runtime/web/index.html +145 -18
  30. package/runtime/web/js/api-key-dialog.js +153 -0
  31. package/runtime/web/js/api.js +25 -0
  32. package/runtime/web/js/chat-apps-dialog.js +194 -0
  33. package/runtime/web/{app.js → js/chat.js} +112 -193
  34. package/runtime/web/js/config.js +16 -0
  35. package/runtime/web/js/main.js +56 -0
  36. package/runtime/web/js/settings.js +205 -0
  37. package/runtime/web/styles.css +311 -10
  38. package/runtime/server/chat-proxy.js +0 -229
  39. package/runtime/server/index.js +0 -63
  40. package/runtime/server/routes.js +0 -104
@@ -1,229 +0,0 @@
1
- import path from "node:path";
2
- import {
3
- AuthStorage,
4
- createAgentSession,
5
- ModelRegistry,
6
- SessionManager,
7
- DefaultResourceLoader,
8
- } from "@mariozechner/pi-coding-agent";
9
-
10
- const DEBUG = true;
11
-
12
- const log = (...args) => DEBUG && console.log(...args);
13
- const write = (data) => DEBUG && process.stdout.write(data);
14
-
15
- function getAssistantDiagnostics(message) {
16
- if (!message || message.role !== "assistant") {
17
- return null;
18
- }
19
-
20
- const stopReason = message.stopReason;
21
- const errorMessage =
22
- message.errorMessage ||
23
- (stopReason === "error" || stopReason === "aborted"
24
- ? `Request ${stopReason}`
25
- : "");
26
-
27
- const content = Array.isArray(message.content) ? message.content : [];
28
- const text = content
29
- .filter((item) => item?.type === "text")
30
- .map((item) => item.text || "")
31
- .join("")
32
- .trim();
33
- const toolCalls = content.filter((item) => item?.type === "toolCall").length;
34
-
35
- return {
36
- stopReason,
37
- errorMessage,
38
- hasText: text.length > 0,
39
- toolCalls,
40
- };
41
- }
42
-
43
- /**
44
- * Handle incoming WebSocket connection using pi-coding-agent
45
- * @param {import("ws").WebSocket} ws
46
- * @param {object} options
47
- * @param {string} options.apiKey - OpenAI API Key
48
- * @param {string} options.rootDir - Pack root directory
49
- */
50
- export async function handleWsConnection(
51
- ws,
52
- { apiKey, rootDir, provider = "openai", modelId = "gpt-5.4" },
53
- ) {
54
- try {
55
- let turnHadVisibleOutput = false;
56
-
57
- // Create an in-memory auth storage to avoid touching disk
58
- const authStorage = AuthStorage.inMemory({
59
- [provider]: { type: "api_key", key: apiKey },
60
- });
61
- authStorage.setRuntimeApiKey(provider, apiKey);
62
-
63
- const modelRegistry = new ModelRegistry(authStorage);
64
- const model = modelRegistry.find(provider, modelId);
65
-
66
- const sessionManager = SessionManager.inMemory();
67
-
68
- const skillsPath = path.resolve(rootDir, "skills");
69
- log(`[ChatProxy] Loading additional skills from: ${skillsPath}`);
70
-
71
- const resourceLoader = new DefaultResourceLoader({
72
- cwd: rootDir,
73
- additionalSkillPaths: [skillsPath], // 手动加载 rootDir/skills
74
- });
75
- await resourceLoader.reload();
76
-
77
- const { session } = await createAgentSession({
78
- cwd: rootDir, // Allow pi-coding-agent to find skills in this pack's directory
79
- authStorage,
80
- modelRegistry,
81
- sessionManager,
82
- resourceLoader,
83
- model,
84
- });
85
-
86
- // Stream agent events to the WebSocket
87
- session.subscribe((event) => {
88
- switch (event.type) {
89
- case "agent_start":
90
- log("\n=== [PI-CODING-AGENT SESSION START] ===");
91
- log("System Prompt:\n", session.systemPrompt);
92
- log("========================================\n");
93
- ws.send(JSON.stringify({ type: "agent_start" }));
94
- break;
95
-
96
- case "message_start":
97
- log(`\n--- [Message Start: ${event.message?.role}] ---`);
98
- if (event.message?.role === "user") {
99
- log(JSON.stringify(event.message.content, null, 2));
100
- }
101
- ws.send(
102
- JSON.stringify({
103
- type: "message_start",
104
- role: event.message?.role,
105
- }),
106
- );
107
- break;
108
-
109
- case "message_update":
110
- if (event.assistantMessageEvent?.type === "text_delta") {
111
- turnHadVisibleOutput = true;
112
- write(event.assistantMessageEvent.delta);
113
- ws.send(
114
- JSON.stringify({
115
- type: "text_delta",
116
- delta: event.assistantMessageEvent.delta,
117
- }),
118
- );
119
- } else if (event.assistantMessageEvent?.type === "thinking_delta") {
120
- turnHadVisibleOutput = true;
121
- ws.send(
122
- JSON.stringify({
123
- type: "thinking_delta",
124
- delta: event.assistantMessageEvent.delta,
125
- }),
126
- );
127
- }
128
- break;
129
-
130
- case "message_end":
131
- log(`\n--- [Message End: ${event.message?.role}] ---`);
132
- if (event.message?.role === "assistant") {
133
- const diagnostics = getAssistantDiagnostics(event.message);
134
- if (diagnostics) {
135
- log(
136
- `[Assistant Diagnostics] stopReason=${diagnostics.stopReason || "unknown"} text=${diagnostics.hasText ? "yes" : "no"} toolCalls=${diagnostics.toolCalls}`,
137
- );
138
- if (diagnostics.errorMessage) {
139
- log(`[Assistant Error] ${diagnostics.errorMessage}`);
140
- }
141
- }
142
- }
143
- ws.send(
144
- JSON.stringify({
145
- type: "message_end",
146
- role: event.message?.role,
147
- }),
148
- );
149
- break;
150
-
151
- case "tool_execution_start":
152
- turnHadVisibleOutput = true;
153
- log(`\n>>> [Tool Execution Start: ${event.toolName}] >>>`);
154
- log("Args:", JSON.stringify(event.args, null, 2));
155
- ws.send(
156
- JSON.stringify({
157
- type: "tool_start",
158
- toolName: event.toolName,
159
- toolInput: event.args,
160
- }),
161
- );
162
- break;
163
-
164
- case "tool_execution_end":
165
- turnHadVisibleOutput = true;
166
- log(`<<< [Tool Execution End: ${event.toolName}] <<<`);
167
- log(`Error: ${event.isError ? "Yes" : "No"}`);
168
- ws.send(
169
- JSON.stringify({
170
- type: "tool_end",
171
- toolName: event.toolName,
172
- isError: event.isError,
173
- result: event.result,
174
- }),
175
- );
176
- break;
177
-
178
- case "agent_end":
179
- log("\n=== [PI-CODING-AGENT SESSION END] ===\n");
180
- ws.send(JSON.stringify({ type: "agent_end" }));
181
- break;
182
- }
183
- });
184
-
185
- // Listen for incoming messages from the frontend
186
- ws.on("message", async (data) => {
187
- try {
188
- const payload = JSON.parse(data.toString());
189
- if (payload.text) {
190
- turnHadVisibleOutput = false;
191
-
192
- // Send prompt to the agent, the session will handle message history natively
193
- await session.prompt(payload.text);
194
-
195
- const lastMessage = session.state.messages.at(-1);
196
- const diagnostics = getAssistantDiagnostics(lastMessage);
197
- if (diagnostics?.errorMessage) {
198
- ws.send(JSON.stringify({ error: diagnostics.errorMessage }));
199
- return;
200
- }
201
-
202
- if (
203
- diagnostics &&
204
- !diagnostics.hasText &&
205
- diagnostics.toolCalls === 0 &&
206
- !turnHadVisibleOutput
207
- ) {
208
- const emptyResponseError =
209
- "Assistant returned no visible output. Check the server logs for stopReason/provider details.";
210
- log(`[Assistant Warning] ${emptyResponseError}`);
211
- ws.send(JSON.stringify({ error: emptyResponseError }));
212
- return;
213
- }
214
-
215
- ws.send(JSON.stringify({ done: true }));
216
- }
217
- } catch (err) {
218
- ws.send(JSON.stringify({ error: String(err) }));
219
- }
220
- });
221
-
222
- ws.on("close", () => {
223
- session.dispose();
224
- });
225
- } catch (err) {
226
- ws.send(JSON.stringify({ error: String(err) }));
227
- ws.close();
228
- }
229
- }
@@ -1,63 +0,0 @@
1
- import express from "express";
2
- import path from "node:path";
3
- import fs from "node:fs";
4
- import { fileURLToPath } from "node:url";
5
- import { createServer } from "node:http";
6
- import { exec } from "node:child_process";
7
- import { registerRoutes } from "./routes.js";
8
-
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
-
11
- const rootDir = process.env.PACK_ROOT || path.join(__dirname, "..");
12
-
13
- const webDir = fs.existsSync(path.join(rootDir, "web"))
14
- ? path.join(rootDir, "web")
15
- : path.join(__dirname, "..", "web");
16
-
17
- const app = express();
18
- app.use(express.json());
19
-
20
- // Static file serving
21
- app.use(express.static(webDir));
22
-
23
- const server = createServer(app);
24
-
25
- // Register API routes
26
- registerRoutes(app, server, rootDir);
27
-
28
- const HOST = process.env.HOST || "127.0.0.1";
29
- const DEFAULT_PORT = 26313;
30
-
31
- server.once("listening", () => {
32
- const address = server.address();
33
- const actualPort = typeof address === "string" ? address : address.port;
34
- const url = `http://${HOST}:${actualPort}`;
35
- console.log(`\n Skills Pack Server`);
36
- console.log(` Running at ${url}\n`);
37
-
38
- // Open the browser automatically
39
- const cmd =
40
- process.platform === "darwin"
41
- ? `open ${url}`
42
- : process.platform === "win32"
43
- ? `start ${url}`
44
- : `xdg-open ${url}`;
45
- exec(cmd, () => {});
46
- });
47
-
48
- function tryListen(port) {
49
- server.listen(port, HOST);
50
-
51
- server.once("error", (err) => {
52
- if (err.code === "EADDRINUSE") {
53
- console.log(` Port ${port} is in use, trying ${port + 1}...`);
54
- server.close();
55
- tryListen(port + 1);
56
- } else {
57
- throw err;
58
- }
59
- });
60
- }
61
-
62
- const startPort = Number(process.env.PORT) || DEFAULT_PORT;
63
- tryListen(startPort);
@@ -1,104 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { WebSocketServer } from "ws";
4
-
5
- import { handleWsConnection } from "./chat-proxy.js";
6
-
7
- /**
8
- * Read the skillpack.json config.
9
- * @param {string} rootDir
10
- */
11
- function getPackConfig(rootDir) {
12
- const raw = fs.readFileSync(path.join(rootDir, "skillpack.json"), "utf-8");
13
- return JSON.parse(raw);
14
- }
15
-
16
- /**
17
- * Register all API routes.
18
- * @param {import("express").Express} app
19
- * @param {import("node:http").Server} server
20
- * @param {string} rootDir - Root directory containing skillpack.json and skills/
21
- */
22
- export function registerRoutes(app, server, rootDir) {
23
- // API key and provider are stored in runtime memory
24
- let apiKey = "";
25
- let currentProvider = "openai";
26
-
27
- if (process.env.OPENAI_API_KEY) {
28
- apiKey = process.env.OPENAI_API_KEY;
29
- currentProvider = "openai";
30
- } else if (process.env.ANTHROPIC_API_KEY) {
31
- apiKey = process.env.ANTHROPIC_API_KEY;
32
- currentProvider = "anthropic";
33
- }
34
-
35
- // Get pack config
36
- app.get("/api/config", (req, res) => {
37
- const config = getPackConfig(rootDir);
38
- res.json({
39
- name: config.name,
40
- description: config.description,
41
- prompts: config.prompts || [],
42
- skills: config.skills || [],
43
- hasApiKey: !!apiKey,
44
- provider: currentProvider,
45
- });
46
- });
47
-
48
- // Get skills list
49
- app.get("/api/skills", (req, res) => {
50
- const config = getPackConfig(rootDir);
51
- res.json(config.skills || []);
52
- });
53
-
54
- // Set API key
55
- app.post("/api/config/key", (req, res) => {
56
- const { key, provider } = req.body;
57
- if (!key) {
58
- return res.status(400).json({ error: "API key is required" });
59
- }
60
- apiKey = key;
61
- if (provider) {
62
- currentProvider = provider;
63
- }
64
- res.json({ success: true, provider: currentProvider });
65
- });
66
-
67
- // WebSocket chat service
68
- const wss = new WebSocketServer({ noServer: true });
69
-
70
- server.on("upgrade", (request, socket, head) => {
71
- if (request.url.startsWith("/api/chat")) {
72
- wss.handleUpgrade(request, socket, head, (ws) => {
73
- wss.emit("connection", ws, request);
74
- });
75
- } else {
76
- socket.destroy();
77
- }
78
- });
79
-
80
- wss.on("connection", (ws, request) => {
81
- const url = new URL(
82
- request.url,
83
- `http://${request.headers.host || "127.0.0.1"}`,
84
- );
85
- const reqProvider = url.searchParams.get("provider") || currentProvider;
86
-
87
- if (!apiKey) {
88
- ws.send(JSON.stringify({ error: "Please set an API key first" }));
89
- ws.close();
90
- return;
91
- }
92
-
93
- const config = getPackConfig(rootDir);
94
-
95
- const modelId = reqProvider === "anthropic" ? "claude-opus-4-6" : "gpt-5.4";
96
-
97
- handleWsConnection(ws, { apiKey, rootDir, provider: reqProvider, modelId });
98
- });
99
-
100
- // Clear session
101
- app.delete("/api/chat", (req, res) => {
102
- res.json({ success: true });
103
- });
104
- }