@cremini/skillpack 1.0.8 → 1.0.9-im.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.
- package/README.md +3 -1
- package/dist/cli.js +45 -11
- package/package.json +3 -2
- package/runtime/server/dist/adapters/markdown.js +74 -0
- package/runtime/server/dist/adapters/markdown.js.map +1 -0
- package/runtime/server/dist/adapters/slack.js +369 -0
- package/runtime/server/dist/adapters/slack.js.map +1 -0
- package/runtime/server/dist/adapters/telegram.js +199 -0
- package/runtime/server/dist/adapters/telegram.js.map +1 -0
- package/runtime/server/dist/adapters/types.js +2 -0
- package/runtime/server/dist/adapters/types.js.map +1 -0
- package/runtime/server/dist/adapters/web.js +168 -0
- package/runtime/server/dist/adapters/web.js.map +1 -0
- package/runtime/server/dist/agent.js +219 -0
- package/runtime/server/dist/agent.js.map +1 -0
- package/runtime/server/dist/config.js +73 -0
- package/runtime/server/dist/config.js.map +1 -0
- package/runtime/server/dist/index.js +122 -0
- package/runtime/server/dist/index.js.map +1 -0
- package/runtime/server/package-lock.json +2768 -121
- package/runtime/server/package.json +13 -3
- package/runtime/start.bat +2 -2
- package/runtime/start.sh +1 -1
- package/runtime/web/index.html +58 -15
- package/runtime/web/js/api.js +13 -0
- package/runtime/web/{app.js → js/chat.js} +126 -193
- package/runtime/web/js/config.js +15 -0
- package/runtime/web/js/main.js +47 -0
- package/runtime/web/js/settings.js +132 -0
- package/runtime/web/styles.css +171 -0
- package/runtime/server/chat-proxy.js +0 -161
- package/runtime/server/index.js +0 -63
- package/runtime/server/routes.js +0 -104
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { state, loadConfig } from "./config.js";
|
|
2
|
+
import { initSettings } from "./settings.js";
|
|
3
|
+
import { initChat, showWelcome } from "./chat.js";
|
|
4
|
+
|
|
5
|
+
async function init() {
|
|
6
|
+
try {
|
|
7
|
+
const config = await loadConfig();
|
|
8
|
+
|
|
9
|
+
// Set Pack Name & Description
|
|
10
|
+
const elName = document.getElementById("pack-name");
|
|
11
|
+
const elDesc = document.getElementById("pack-desc");
|
|
12
|
+
if (elName) elName.textContent = config.name || "Skills Pack";
|
|
13
|
+
if (elDesc) elDesc.textContent = config.description || "";
|
|
14
|
+
document.title = config.name || "Skills Pack";
|
|
15
|
+
|
|
16
|
+
// Set Sidebar Skills list
|
|
17
|
+
const skillsList = document.getElementById("skills-list");
|
|
18
|
+
if (skillsList && config.skills) {
|
|
19
|
+
skillsList.innerHTML = config.skills
|
|
20
|
+
.map(
|
|
21
|
+
(s) =>
|
|
22
|
+
`<li><div class="skill-name">${s.name}</div><div class="skill-desc">${s.description}</div></li>`,
|
|
23
|
+
)
|
|
24
|
+
.join("");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Pre-fill prompt if exactly one
|
|
28
|
+
if (config.prompts && config.prompts.length === 1) {
|
|
29
|
+
const input = document.getElementById("user-input");
|
|
30
|
+
if (input) {
|
|
31
|
+
input.value = config.prompts[0];
|
|
32
|
+
input.style.height = "auto";
|
|
33
|
+
input.style.height = Math.min(input.scrollHeight, 120) + "px";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
showWelcome(config);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error("Initialization Failed:", err);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
initSettings();
|
|
43
|
+
initChat();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Start application
|
|
47
|
+
init();
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { state } from "./config.js";
|
|
2
|
+
import { saveConfigData } from "./api.js";
|
|
3
|
+
|
|
4
|
+
// DOM Elements
|
|
5
|
+
let dialog;
|
|
6
|
+
let settingsBtn;
|
|
7
|
+
let closeBtn;
|
|
8
|
+
let saveBtn;
|
|
9
|
+
let providerSelect;
|
|
10
|
+
let apiKeyInput;
|
|
11
|
+
let telegramTokenInput;
|
|
12
|
+
let slackBotTokenInput;
|
|
13
|
+
let slackAppTokenInput;
|
|
14
|
+
let keyStatus;
|
|
15
|
+
|
|
16
|
+
export function initSettings() {
|
|
17
|
+
dialog = document.getElementById("settings-dialog");
|
|
18
|
+
settingsBtn = document.getElementById("open-settings-btn");
|
|
19
|
+
closeBtn = document.getElementById("close-settings-btn");
|
|
20
|
+
saveBtn = document.getElementById("save-settings-btn");
|
|
21
|
+
|
|
22
|
+
providerSelect = document.getElementById("provider-select");
|
|
23
|
+
apiKeyInput = document.getElementById("api-key-input");
|
|
24
|
+
telegramTokenInput = document.getElementById("telegram-token-input");
|
|
25
|
+
slackBotTokenInput = document.getElementById("slack-bot-token-input");
|
|
26
|
+
slackAppTokenInput = document.getElementById("slack-app-token-input");
|
|
27
|
+
keyStatus = document.getElementById("key-status");
|
|
28
|
+
|
|
29
|
+
if (!dialog) return;
|
|
30
|
+
|
|
31
|
+
// Open/Close dialog
|
|
32
|
+
if (settingsBtn) {
|
|
33
|
+
settingsBtn.addEventListener("click", () => {
|
|
34
|
+
populateForm();
|
|
35
|
+
dialog.showModal();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (closeBtn) {
|
|
40
|
+
closeBtn.addEventListener("click", () => {
|
|
41
|
+
dialog.close();
|
|
42
|
+
keyStatus.textContent = ""; // clear status on close
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Save Settings
|
|
47
|
+
if (saveBtn) {
|
|
48
|
+
saveBtn.addEventListener("click", handleSave);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Placeholder logic
|
|
52
|
+
if (providerSelect) {
|
|
53
|
+
providerSelect.addEventListener("change", updatePlaceholder);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function updatePlaceholder() {
|
|
58
|
+
const p = providerSelect.value;
|
|
59
|
+
if (p === "openai") apiKeyInput.placeholder = "sk-proj-...";
|
|
60
|
+
else if (p === "anthropic") apiKeyInput.placeholder = "sk-ant-api03-...";
|
|
61
|
+
else apiKeyInput.placeholder = "sk-...";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function populateForm() {
|
|
65
|
+
const config = state.config;
|
|
66
|
+
if (!config) return;
|
|
67
|
+
|
|
68
|
+
if (config.hasApiKey) {
|
|
69
|
+
keyStatus.textContent = "API key configured";
|
|
70
|
+
keyStatus.className = "status-text success";
|
|
71
|
+
} else {
|
|
72
|
+
keyStatus.textContent = "";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (config.provider) {
|
|
76
|
+
providerSelect.value = config.provider;
|
|
77
|
+
}
|
|
78
|
+
updatePlaceholder();
|
|
79
|
+
|
|
80
|
+
const adapters = config.adapters || {};
|
|
81
|
+
if (adapters.telegram && adapters.telegram.token) {
|
|
82
|
+
telegramTokenInput.value = adapters.telegram.token;
|
|
83
|
+
} else {
|
|
84
|
+
telegramTokenInput.value = "";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (adapters.slack) {
|
|
88
|
+
slackBotTokenInput.value = adapters.slack.botToken || "";
|
|
89
|
+
slackAppTokenInput.value = adapters.slack.appToken || "";
|
|
90
|
+
} else {
|
|
91
|
+
slackBotTokenInput.value = "";
|
|
92
|
+
slackAppTokenInput.value = "";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function handleSave() {
|
|
97
|
+
const key = apiKeyInput.value.trim();
|
|
98
|
+
const provider = providerSelect.value;
|
|
99
|
+
const telegramToken = telegramTokenInput.value.trim();
|
|
100
|
+
const slackBotToken = slackBotTokenInput.value.trim();
|
|
101
|
+
const slackAppToken = slackAppTokenInput.value.trim();
|
|
102
|
+
|
|
103
|
+
const adapters = {};
|
|
104
|
+
if (telegramToken) adapters.telegram = { token: telegramToken };
|
|
105
|
+
if (slackBotToken || slackAppToken) {
|
|
106
|
+
adapters.slack = {
|
|
107
|
+
botToken: slackBotToken || undefined,
|
|
108
|
+
appToken: slackAppToken || undefined
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const updates = { provider, adapters };
|
|
113
|
+
if (key) {
|
|
114
|
+
updates.key = key;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const res = await saveConfigData(updates);
|
|
119
|
+
keyStatus.textContent = "Settings saved";
|
|
120
|
+
keyStatus.className = "status-text success";
|
|
121
|
+
apiKeyInput.value = ""; // clear after save
|
|
122
|
+
|
|
123
|
+
// Update local config
|
|
124
|
+
state.config.provider = res.provider;
|
|
125
|
+
state.config.adapters = res.adapters;
|
|
126
|
+
if (key) state.config.hasApiKey = true;
|
|
127
|
+
|
|
128
|
+
} catch (err) {
|
|
129
|
+
keyStatus.textContent = "Save failed: " + err.message;
|
|
130
|
+
keyStatus.className = "status-text error";
|
|
131
|
+
}
|
|
132
|
+
}
|
package/runtime/web/styles.css
CHANGED
|
@@ -772,3 +772,174 @@ body {
|
|
|
772
772
|
.thinking-content p:last-child {
|
|
773
773
|
margin-bottom: 0;
|
|
774
774
|
}
|
|
775
|
+
|
|
776
|
+
/* ---- Settings Modal ---- */
|
|
777
|
+
.settings-trigger-btn {
|
|
778
|
+
display: flex;
|
|
779
|
+
align-items: center;
|
|
780
|
+
justify-content: center;
|
|
781
|
+
gap: 8px;
|
|
782
|
+
width: 100%;
|
|
783
|
+
padding: 12px;
|
|
784
|
+
background: var(--bg-tertiary);
|
|
785
|
+
border: 1px solid var(--border-color);
|
|
786
|
+
border-radius: var(--radius-sm);
|
|
787
|
+
color: var(--text-primary);
|
|
788
|
+
font-weight: 500;
|
|
789
|
+
cursor: pointer;
|
|
790
|
+
transition: all 0.2s ease;
|
|
791
|
+
}
|
|
792
|
+
.settings-trigger-btn:hover {
|
|
793
|
+
background: var(--bg-secondary);
|
|
794
|
+
border-color: var(--accent);
|
|
795
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.03);
|
|
796
|
+
transform: translateY(-1px);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
.settings-modal {
|
|
800
|
+
margin: auto;
|
|
801
|
+
padding: 0;
|
|
802
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
803
|
+
border-radius: 16px;
|
|
804
|
+
background: var(--bg-secondary);
|
|
805
|
+
width: 520px;
|
|
806
|
+
max-width: 90vw;
|
|
807
|
+
box-shadow: 0 20px 40px rgba(0,0,0,0.1), 0 0 0 1px var(--border-color);
|
|
808
|
+
font-family: inherit;
|
|
809
|
+
color: var(--text-primary);
|
|
810
|
+
opacity: 0;
|
|
811
|
+
transform: scale(0.95) translateY(10px);
|
|
812
|
+
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
813
|
+
}
|
|
814
|
+
.settings-modal[open] {
|
|
815
|
+
opacity: 1;
|
|
816
|
+
transform: scale(1) translateY(0);
|
|
817
|
+
}
|
|
818
|
+
.settings-modal::backdrop {
|
|
819
|
+
background: rgba(0, 0, 0, 0.2);
|
|
820
|
+
backdrop-filter: blur(4px);
|
|
821
|
+
-webkit-backdrop-filter: blur(4px);
|
|
822
|
+
opacity: 0;
|
|
823
|
+
transition: opacity 0.3s ease;
|
|
824
|
+
}
|
|
825
|
+
.settings-modal[open]::backdrop {
|
|
826
|
+
opacity: 1;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
.settings-modal-header {
|
|
830
|
+
display: flex;
|
|
831
|
+
justify-content: space-between;
|
|
832
|
+
align-items: center;
|
|
833
|
+
padding: 24px 24px 12px 24px;
|
|
834
|
+
background: transparent;
|
|
835
|
+
border-top-left-radius: 16px;
|
|
836
|
+
border-top-right-radius: 16px;
|
|
837
|
+
}
|
|
838
|
+
.settings-modal-header h2 { font-size: 18px; font-weight: 600; margin: 0; color: var(--text-primary); letter-spacing: -0.01em; }
|
|
839
|
+
.close-btn {
|
|
840
|
+
background: var(--bg-tertiary);
|
|
841
|
+
border: 1px solid var(--border-color);
|
|
842
|
+
border-radius: 8px;
|
|
843
|
+
width: 32px;
|
|
844
|
+
height: 32px;
|
|
845
|
+
display: flex;
|
|
846
|
+
align-items: center;
|
|
847
|
+
justify-content: center;
|
|
848
|
+
font-size: 18px;
|
|
849
|
+
cursor: pointer;
|
|
850
|
+
color: var(--text-secondary);
|
|
851
|
+
transition: all 0.2s ease;
|
|
852
|
+
}
|
|
853
|
+
.close-btn:hover {
|
|
854
|
+
background: var(--bg-primary);
|
|
855
|
+
color: var(--text-primary);
|
|
856
|
+
border-color: var(--text-secondary);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.settings-sections {
|
|
860
|
+
padding: 24px;
|
|
861
|
+
max-height: calc(85vh - 140px);
|
|
862
|
+
overflow-y: auto;
|
|
863
|
+
}
|
|
864
|
+
.settings-section {
|
|
865
|
+
margin-bottom: 28px;
|
|
866
|
+
padding-bottom: 28px;
|
|
867
|
+
border-bottom: 1px solid var(--border-color);
|
|
868
|
+
}
|
|
869
|
+
.settings-section:last-child {
|
|
870
|
+
margin-bottom: 0;
|
|
871
|
+
padding-bottom: 0;
|
|
872
|
+
border-bottom: none;
|
|
873
|
+
}
|
|
874
|
+
.section-title {
|
|
875
|
+
font-size: 14px;
|
|
876
|
+
font-weight: 600;
|
|
877
|
+
color: var(--text-primary);
|
|
878
|
+
margin-top: 0;
|
|
879
|
+
margin-bottom: 16px;
|
|
880
|
+
text-transform: uppercase;
|
|
881
|
+
letter-spacing: 0.5px;
|
|
882
|
+
opacity: 0.8;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
.form-group { margin-bottom: 16px; }
|
|
886
|
+
.form-group:last-child { margin-bottom: 0; }
|
|
887
|
+
.form-group label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 8px; color: var(--text-primary); }
|
|
888
|
+
.form-input {
|
|
889
|
+
width: 100%;
|
|
890
|
+
padding: 12px 14px;
|
|
891
|
+
background: var(--bg-tertiary);
|
|
892
|
+
border: 1px solid var(--border-color);
|
|
893
|
+
border-radius: var(--radius-sm);
|
|
894
|
+
font-size: 14px;
|
|
895
|
+
outline: none;
|
|
896
|
+
color: var(--text-primary);
|
|
897
|
+
transition: all 0.2s ease;
|
|
898
|
+
}
|
|
899
|
+
.form-input:focus {
|
|
900
|
+
border-color: var(--accent);
|
|
901
|
+
background: var(--bg-secondary);
|
|
902
|
+
box-shadow: 0 0 0 3px var(--accent-glow);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/* Specific to provider-select in Settings Modal to match form-input */
|
|
906
|
+
.settings-modal .provider-select-wrapper select {
|
|
907
|
+
padding: 12px 14px;
|
|
908
|
+
font-size: 14px;
|
|
909
|
+
transition: all 0.2s ease;
|
|
910
|
+
}
|
|
911
|
+
.settings-modal .provider-select-wrapper select:focus {
|
|
912
|
+
border-color: var(--accent);
|
|
913
|
+
background: var(--bg-secondary);
|
|
914
|
+
box-shadow: 0 0 0 3px var(--accent-glow);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
.settings-modal-footer {
|
|
918
|
+
padding: 16px 24px 24px 24px;
|
|
919
|
+
background: transparent;
|
|
920
|
+
border-bottom-left-radius: 16px;
|
|
921
|
+
border-bottom-right-radius: 16px;
|
|
922
|
+
display: flex;
|
|
923
|
+
justify-content: flex-end;
|
|
924
|
+
align-items: center;
|
|
925
|
+
}
|
|
926
|
+
.primary-btn {
|
|
927
|
+
padding: 10px 20px;
|
|
928
|
+
background: var(--accent);
|
|
929
|
+
color: #fff;
|
|
930
|
+
border: none;
|
|
931
|
+
border-radius: var(--radius-sm);
|
|
932
|
+
font-weight: 500;
|
|
933
|
+
font-size: 14px;
|
|
934
|
+
cursor: pointer;
|
|
935
|
+
transition: all 0.2s ease;
|
|
936
|
+
}
|
|
937
|
+
.primary-btn:hover {
|
|
938
|
+
background: var(--accent-hover);
|
|
939
|
+
box-shadow: 0 4px 12px var(--accent-glow);
|
|
940
|
+
transform: translateY(-1px);
|
|
941
|
+
}
|
|
942
|
+
.primary-btn:active {
|
|
943
|
+
transform: translateY(0);
|
|
944
|
+
}
|
|
945
|
+
|
|
@@ -1,161 +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
|
-
/**
|
|
16
|
-
* Handle incoming WebSocket connection using pi-coding-agent
|
|
17
|
-
* @param {import("ws").WebSocket} ws
|
|
18
|
-
* @param {object} options
|
|
19
|
-
* @param {string} options.apiKey - OpenAI API Key
|
|
20
|
-
* @param {string} options.rootDir - Pack root directory
|
|
21
|
-
*/
|
|
22
|
-
export async function handleWsConnection(
|
|
23
|
-
ws,
|
|
24
|
-
{ apiKey, rootDir, provider = "openai", modelId = "gpt-5.4" },
|
|
25
|
-
) {
|
|
26
|
-
try {
|
|
27
|
-
// Create an in-memory auth storage to avoid touching disk
|
|
28
|
-
const authStorage = AuthStorage.inMemory({
|
|
29
|
-
[provider]: { type: "api_key", key: apiKey },
|
|
30
|
-
});
|
|
31
|
-
authStorage.setRuntimeApiKey(provider, apiKey);
|
|
32
|
-
|
|
33
|
-
const modelRegistry = new ModelRegistry(authStorage);
|
|
34
|
-
const model = modelRegistry.find(provider, modelId);
|
|
35
|
-
|
|
36
|
-
const sessionManager = SessionManager.inMemory();
|
|
37
|
-
|
|
38
|
-
const skillsPath = path.resolve(rootDir, "skills");
|
|
39
|
-
log(`[ChatProxy] Loading additional skills from: ${skillsPath}`);
|
|
40
|
-
|
|
41
|
-
const resourceLoader = new DefaultResourceLoader({
|
|
42
|
-
cwd: rootDir,
|
|
43
|
-
additionalSkillPaths: [skillsPath], // 手动加载 rootDir/skills
|
|
44
|
-
});
|
|
45
|
-
await resourceLoader.reload();
|
|
46
|
-
|
|
47
|
-
const { session } = await createAgentSession({
|
|
48
|
-
cwd: rootDir, // Allow pi-coding-agent to find skills in this pack's directory
|
|
49
|
-
authStorage,
|
|
50
|
-
modelRegistry,
|
|
51
|
-
sessionManager,
|
|
52
|
-
resourceLoader,
|
|
53
|
-
model,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Stream agent events to the WebSocket
|
|
57
|
-
session.subscribe((event) => {
|
|
58
|
-
switch (event.type) {
|
|
59
|
-
case "agent_start":
|
|
60
|
-
log("\n=== [PI-CODING-AGENT SESSION START] ===");
|
|
61
|
-
log("System Prompt:\n", session.systemPrompt);
|
|
62
|
-
log("========================================\n");
|
|
63
|
-
ws.send(JSON.stringify({ type: "agent_start" }));
|
|
64
|
-
break;
|
|
65
|
-
|
|
66
|
-
case "message_start":
|
|
67
|
-
log(`\n--- [Message Start: ${event.message?.role}] ---`);
|
|
68
|
-
if (event.message?.role === "user") {
|
|
69
|
-
log(JSON.stringify(event.message.content, null, 2));
|
|
70
|
-
}
|
|
71
|
-
ws.send(
|
|
72
|
-
JSON.stringify({
|
|
73
|
-
type: "message_start",
|
|
74
|
-
role: event.message?.role,
|
|
75
|
-
}),
|
|
76
|
-
);
|
|
77
|
-
break;
|
|
78
|
-
|
|
79
|
-
case "message_update":
|
|
80
|
-
if (event.assistantMessageEvent?.type === "text_delta") {
|
|
81
|
-
write(event.assistantMessageEvent.delta);
|
|
82
|
-
ws.send(
|
|
83
|
-
JSON.stringify({
|
|
84
|
-
type: "text_delta",
|
|
85
|
-
delta: event.assistantMessageEvent.delta,
|
|
86
|
-
}),
|
|
87
|
-
);
|
|
88
|
-
} else if (event.assistantMessageEvent?.type === "thinking_delta") {
|
|
89
|
-
ws.send(
|
|
90
|
-
JSON.stringify({
|
|
91
|
-
type: "thinking_delta",
|
|
92
|
-
delta: event.assistantMessageEvent.delta,
|
|
93
|
-
}),
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
break;
|
|
97
|
-
|
|
98
|
-
case "message_end":
|
|
99
|
-
log(`\n--- [Message End: ${event.message?.role}] ---`);
|
|
100
|
-
ws.send(
|
|
101
|
-
JSON.stringify({
|
|
102
|
-
type: "message_end",
|
|
103
|
-
role: event.message?.role,
|
|
104
|
-
}),
|
|
105
|
-
);
|
|
106
|
-
break;
|
|
107
|
-
|
|
108
|
-
case "tool_execution_start":
|
|
109
|
-
log(`\n>>> [Tool Execution Start: ${event.toolName}] >>>`);
|
|
110
|
-
log("Args:", JSON.stringify(event.args, null, 2));
|
|
111
|
-
ws.send(
|
|
112
|
-
JSON.stringify({
|
|
113
|
-
type: "tool_start",
|
|
114
|
-
toolName: event.toolName,
|
|
115
|
-
toolInput: event.args,
|
|
116
|
-
}),
|
|
117
|
-
);
|
|
118
|
-
break;
|
|
119
|
-
|
|
120
|
-
case "tool_execution_end":
|
|
121
|
-
log(`<<< [Tool Execution End: ${event.toolName}] <<<`);
|
|
122
|
-
log(`Error: ${event.isError ? "Yes" : "No"}`);
|
|
123
|
-
ws.send(
|
|
124
|
-
JSON.stringify({
|
|
125
|
-
type: "tool_end",
|
|
126
|
-
toolName: event.toolName,
|
|
127
|
-
isError: event.isError,
|
|
128
|
-
result: event.result,
|
|
129
|
-
}),
|
|
130
|
-
);
|
|
131
|
-
break;
|
|
132
|
-
|
|
133
|
-
case "agent_end":
|
|
134
|
-
log("\n=== [PI-CODING-AGENT SESSION END] ===\n");
|
|
135
|
-
ws.send(JSON.stringify({ type: "agent_end" }));
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Listen for incoming messages from the frontend
|
|
141
|
-
ws.on("message", async (data) => {
|
|
142
|
-
try {
|
|
143
|
-
const payload = JSON.parse(data.toString());
|
|
144
|
-
if (payload.text) {
|
|
145
|
-
// Send prompt to the agent, the session will handle message history natively
|
|
146
|
-
await session.prompt(payload.text);
|
|
147
|
-
ws.send(JSON.stringify({ done: true }));
|
|
148
|
-
}
|
|
149
|
-
} catch (err) {
|
|
150
|
-
ws.send(JSON.stringify({ error: String(err) }));
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
ws.on("close", () => {
|
|
155
|
-
session.dispose();
|
|
156
|
-
});
|
|
157
|
-
} catch (err) {
|
|
158
|
-
ws.send(JSON.stringify({ error: String(err) }));
|
|
159
|
-
ws.close();
|
|
160
|
-
}
|
|
161
|
-
}
|
package/runtime/server/index.js
DELETED
|
@@ -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);
|
package/runtime/server/routes.js
DELETED
|
@@ -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
|
-
}
|