@evandu/super-pi 0.1.110 → 0.1.112
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/dist/.super-pi-check-inputs.json +2 -2
- package/dist/client/.super-pi-client-sources.json +9 -7
- package/dist/client/assets/{CodeMirrorEditor-DdNOmxYW.js → CodeMirrorEditor-moFTk5SE.js} +2 -2
- package/dist/client/assets/{dist-BcnfVRW2.js → dist-BR4s0dNg.js} +1 -1
- package/dist/client/assets/{dist-D_JtGl0X.js → dist-BUuPRG15.js} +1 -1
- package/dist/client/assets/{dist-BaXshVGE.js → dist-Bds_PUYK.js} +1 -1
- package/dist/client/assets/{dist-D6LObgqS.js → dist-BsO40qxQ.js} +1 -1
- package/dist/client/assets/{dist-DFYECMpZ.js → dist-CCYnr6ul.js} +1 -1
- package/dist/client/assets/{dist-VljXHWNt.js → dist-CL65aMXY.js} +1 -1
- package/dist/client/assets/{dist-CmNELnsL.js → dist-D9m04q-s.js} +1 -1
- package/dist/client/assets/{dist-CUsER3MR.js → dist-DIfmMr8s.js} +1 -1
- package/dist/client/assets/{dist-r4nQMwvT.js → dist-DPDGd2jl.js} +1 -1
- package/dist/client/assets/{dist-BMx01Qs-.js → dist-DPvy57Ph.js} +1 -1
- package/dist/client/assets/{dist-B0owH_FO.js → dist-DQjJMys5.js} +1 -1
- package/dist/client/assets/{dist-DfUFnKum.js → dist-Dnr5BxAi.js} +1 -1
- package/dist/client/assets/{dist-DyYBaVQ5.js → dist-DrbUrVWB.js} +1 -1
- package/dist/client/assets/{dist-CyuGJahk.js → dist-DuWhzonJ.js} +1 -1
- package/dist/client/assets/{dist-CiFn8_Gb.js → dist-TgNJUr7p.js} +1 -1
- package/dist/client/assets/{dist-CB4FCjuA.js → dist-rhPxFuD1.js} +1 -1
- package/dist/client/assets/{index-DmN1fZRn.js → index-BixdV7aQ.js} +23 -23
- package/dist/client/assets/index-CGXMw9LP.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/application/run-service.d.ts +7 -0
- package/dist/server/application/run-service.js +30 -0
- package/dist/server/file-name-index.d.ts +144 -0
- package/dist/server/file-name-index.js +324 -0
- package/dist/server/http-server.js +112 -3
- package/dist/server/index.js +10 -0
- package/dist/server/server-context.d.ts +9 -0
- package/dist/server/skills.d.ts +23 -0
- package/dist/server/skills.js +171 -0
- package/dist/server/store.d.ts +9 -0
- package/dist/server/store.js +11 -0
- package/dist/server/types.d.ts +7 -0
- package/package.json +4 -1
- package/scripts/patch-brace-expansion.mjs +1 -1
- package/dist/client/assets/index-CGlVRKg9.css +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SkillSummary } from "./types.js";
|
|
2
|
+
import type { ActiveMemory } from "./active-memory.js";
|
|
3
|
+
/**
|
|
4
|
+
* Loads and returns all available skills for a given workspace and global environment.
|
|
5
|
+
* Priority is given to Pi SDK's DefaultResourceLoader which scans project and user skills.
|
|
6
|
+
* Falls back to direct filesystem scanning if Pi SDK is unavailable.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getAvailableSkills(workspacePath?: string, options?: {
|
|
9
|
+
memory?: ActiveMemory;
|
|
10
|
+
workspaceId?: string;
|
|
11
|
+
}): Promise<SkillSummary[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Reads the markdown content of a specific skill by name or ID.
|
|
14
|
+
*/
|
|
15
|
+
export declare function readSkill(nameOrId: string, workspacePath?: string, options?: {
|
|
16
|
+
memory?: ActiveMemory;
|
|
17
|
+
workspaceId?: string;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
content: string;
|
|
23
|
+
} | null>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
/**
|
|
6
|
+
* Loads and returns all available skills for a given workspace and global environment.
|
|
7
|
+
* Priority is given to Pi SDK's DefaultResourceLoader which scans project and user skills.
|
|
8
|
+
* Falls back to direct filesystem scanning if Pi SDK is unavailable.
|
|
9
|
+
*/
|
|
10
|
+
export async function getAvailableSkills(workspacePath, options) {
|
|
11
|
+
const cwd = workspacePath && existsSync(workspacePath) ? workspacePath : process.cwd();
|
|
12
|
+
const skillsMap = new Map();
|
|
13
|
+
// 1. Try loading via Pi SDK DefaultResourceLoader
|
|
14
|
+
try {
|
|
15
|
+
const sdk = await import("@earendil-works/pi-coding-agent").catch(() => null);
|
|
16
|
+
if (sdk?.DefaultResourceLoader) {
|
|
17
|
+
const agentDir = join(homedir(), ".pi", "agent");
|
|
18
|
+
const loader = new sdk.DefaultResourceLoader({ cwd, agentDir });
|
|
19
|
+
await loader.reload();
|
|
20
|
+
const res = loader.getSkills();
|
|
21
|
+
if (Array.isArray(res?.skills)) {
|
|
22
|
+
for (const s of res.skills) {
|
|
23
|
+
if (!s.name)
|
|
24
|
+
continue;
|
|
25
|
+
const scope = s.sourceInfo?.scope === "project" ? "workspace" : "global";
|
|
26
|
+
skillsMap.set(s.name, {
|
|
27
|
+
id: `skill:${s.name}`,
|
|
28
|
+
name: s.name,
|
|
29
|
+
description: s.description || "",
|
|
30
|
+
scope,
|
|
31
|
+
filePath: s.filePath,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Ignore and proceed to filesystem fallback
|
|
39
|
+
}
|
|
40
|
+
// 2. Fallback: Direct directory scan if SDK loaded nothing or was unavailable
|
|
41
|
+
if (skillsMap.size === 0) {
|
|
42
|
+
const scanDirs = [
|
|
43
|
+
{ dir: join(cwd, ".pi", "skills"), scope: "workspace" },
|
|
44
|
+
{ dir: join(cwd, ".agents", "skills"), scope: "workspace" },
|
|
45
|
+
{ dir: join(homedir(), ".pi", "agent", "skills"), scope: "global" },
|
|
46
|
+
{ dir: join(homedir(), ".agents", "skills"), scope: "global" },
|
|
47
|
+
];
|
|
48
|
+
for (const { dir, scope } of scanDirs) {
|
|
49
|
+
if (!existsSync(dir))
|
|
50
|
+
continue;
|
|
51
|
+
try {
|
|
52
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
let skillFile = "";
|
|
55
|
+
let skillName = entry.name;
|
|
56
|
+
if (entry.isDirectory()) {
|
|
57
|
+
const candidate = join(dir, entry.name, "SKILL.md");
|
|
58
|
+
if (existsSync(candidate)) {
|
|
59
|
+
skillFile = candidate;
|
|
60
|
+
skillName = entry.name;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
64
|
+
skillFile = join(dir, entry.name);
|
|
65
|
+
skillName = entry.name.replace(/\.md$/, "");
|
|
66
|
+
}
|
|
67
|
+
if (skillFile) {
|
|
68
|
+
try {
|
|
69
|
+
const text = await readFile(skillFile, "utf-8");
|
|
70
|
+
const frontmatterMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
|
|
71
|
+
let description = "";
|
|
72
|
+
let parsedName = skillName;
|
|
73
|
+
if (frontmatterMatch) {
|
|
74
|
+
const nameMatch = /^name:\s*(.+)$/m.exec(frontmatterMatch[1]);
|
|
75
|
+
if (nameMatch)
|
|
76
|
+
parsedName = nameMatch[1].trim();
|
|
77
|
+
const descMatch = /^description:\s*(.+)$/m.exec(frontmatterMatch[1]);
|
|
78
|
+
if (descMatch)
|
|
79
|
+
description = descMatch[1].trim();
|
|
80
|
+
}
|
|
81
|
+
if (!skillsMap.has(parsedName)) {
|
|
82
|
+
skillsMap.set(parsedName, {
|
|
83
|
+
id: `skill:${parsedName}`,
|
|
84
|
+
name: parsedName,
|
|
85
|
+
description,
|
|
86
|
+
scope,
|
|
87
|
+
filePath: skillFile,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Ignore unreadable file
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Ignore unreadable directory
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// 3. Optional: merge active memory notes skills if available and not colliding
|
|
103
|
+
if (options?.memory && options?.workspaceId) {
|
|
104
|
+
try {
|
|
105
|
+
const memoryEntries = await options.memory.notes.apply(options.workspaceId, "", { target: "skills", action: "list" });
|
|
106
|
+
if (Array.isArray(memoryEntries)) {
|
|
107
|
+
for (const entry of memoryEntries) {
|
|
108
|
+
const firstLine = entry.content.split(/\r?\n/, 1)[0].replace(/^#+\s*/, "").trim();
|
|
109
|
+
const name = firstLine || "unnamed-skill";
|
|
110
|
+
if (!skillsMap.has(name) && !skillsMap.has(entry.id)) {
|
|
111
|
+
skillsMap.set(`mem:${entry.id}`, {
|
|
112
|
+
id: `skill:${entry.id}`,
|
|
113
|
+
name,
|
|
114
|
+
description: "Active memory skill",
|
|
115
|
+
scope: "memory",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Memory may not be ready or active
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return Array.from(skillsMap.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Reads the markdown content of a specific skill by name or ID.
|
|
129
|
+
*/
|
|
130
|
+
export async function readSkill(nameOrId, workspacePath, options) {
|
|
131
|
+
const cleanId = nameOrId.startsWith("skill:") ? nameOrId.slice("skill:".length) : nameOrId;
|
|
132
|
+
const skills = await getAvailableSkills(workspacePath, options);
|
|
133
|
+
const found = skills.find((s) => s.name === cleanId || s.name === nameOrId || s.id === nameOrId || s.id === `skill:${cleanId}`);
|
|
134
|
+
if (!found)
|
|
135
|
+
return null;
|
|
136
|
+
// If it's a file on disk
|
|
137
|
+
if (found.filePath && existsSync(found.filePath)) {
|
|
138
|
+
try {
|
|
139
|
+
const content = await readFile(found.filePath, "utf-8");
|
|
140
|
+
return {
|
|
141
|
+
id: found.id,
|
|
142
|
+
name: found.name,
|
|
143
|
+
description: found.description,
|
|
144
|
+
content,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// If it's stored in active memory
|
|
152
|
+
if (found.scope === "memory" && options?.memory && options?.workspaceId) {
|
|
153
|
+
try {
|
|
154
|
+
const entryId = found.id.startsWith("skill:") ? found.id.slice("skill:".length) : found.id;
|
|
155
|
+
const memoryEntries = await options.memory.notes.apply(options.workspaceId, "", { target: "skills", action: "list" });
|
|
156
|
+
const entry = memoryEntries.find((e) => e.id === entryId || e.id === found.name);
|
|
157
|
+
if (entry) {
|
|
158
|
+
return {
|
|
159
|
+
id: found.id,
|
|
160
|
+
name: found.name,
|
|
161
|
+
description: found.description,
|
|
162
|
+
content: entry.content,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
package/dist/server/store.d.ts
CHANGED
|
@@ -91,6 +91,15 @@ export declare class Store {
|
|
|
91
91
|
*/
|
|
92
92
|
setDefaultModel(model?: ModelSelection): Promise<void>;
|
|
93
93
|
snapshot(): AppState;
|
|
94
|
+
/**
|
|
95
|
+
* Whether a conversation already holds a message with this id.
|
|
96
|
+
*
|
|
97
|
+
* A LIVE read with no clone, because its one caller is the send path's duplicate check: a resend
|
|
98
|
+
* that arrives within milliseconds of the original must be answered without paying for a full
|
|
99
|
+
* copy of every conversation in the app. The answer is a boolean and the message is not handed
|
|
100
|
+
* out, so nothing can mutate the store through it.
|
|
101
|
+
*/
|
|
102
|
+
hasMessage(conversationId: string, messageId: string): boolean;
|
|
94
103
|
/**
|
|
95
104
|
* One conversation and its workspace, as DETACHED copies, without cloning the whole state.
|
|
96
105
|
*
|
package/dist/server/store.js
CHANGED
|
@@ -684,6 +684,17 @@ export class Store {
|
|
|
684
684
|
}
|
|
685
685
|
}
|
|
686
686
|
snapshot() { return structuredClone(this.state); }
|
|
687
|
+
/**
|
|
688
|
+
* Whether a conversation already holds a message with this id.
|
|
689
|
+
*
|
|
690
|
+
* A LIVE read with no clone, because its one caller is the send path's duplicate check: a resend
|
|
691
|
+
* that arrives within milliseconds of the original must be answered without paying for a full
|
|
692
|
+
* copy of every conversation in the app. The answer is a boolean and the message is not handed
|
|
693
|
+
* out, so nothing can mutate the store through it.
|
|
694
|
+
*/
|
|
695
|
+
hasMessage(conversationId, messageId) {
|
|
696
|
+
return Boolean(this.state.conversations.find((item) => item.id === conversationId)?.messages.some((message) => message.id === messageId));
|
|
697
|
+
}
|
|
687
698
|
/**
|
|
688
699
|
* One conversation and its workspace, as DETACHED copies, without cloning the whole state.
|
|
689
700
|
*
|
package/dist/server/types.d.ts
CHANGED
|
@@ -164,6 +164,13 @@ export type MessageAttachment = {
|
|
|
164
164
|
content: string;
|
|
165
165
|
};
|
|
166
166
|
export type MessageAttachments = MessageAttachment[];
|
|
167
|
+
export interface SkillSummary {
|
|
168
|
+
id: string;
|
|
169
|
+
name: string;
|
|
170
|
+
description: string;
|
|
171
|
+
scope?: "workspace" | "global" | "memory";
|
|
172
|
+
filePath?: string;
|
|
173
|
+
}
|
|
167
174
|
/**
|
|
168
175
|
* ONE PARAMETER OF A TOOL CALL, as a call block lists it, and the boundary that keeps Pi's arguments out
|
|
169
176
|
* of `state.json`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evandu/super-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.112",
|
|
4
4
|
"description": "Web control plane and companion for Pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -89,12 +89,14 @@
|
|
|
89
89
|
"scripts/files-missing-path.test.mjs",
|
|
90
90
|
"scripts/file-changes.test.mjs",
|
|
91
91
|
"scripts/file-index.test.mjs",
|
|
92
|
+
"scripts/file-name-index.test.mjs",
|
|
92
93
|
"scripts/file-content-search.test.mjs",
|
|
93
94
|
"scripts/file-diff-client.test.mjs",
|
|
94
95
|
"scripts/task-tool.test.mjs",
|
|
95
96
|
"scripts/tool-register-env.test.mjs",
|
|
96
97
|
"scripts/bg-task-zombie.test.mjs",
|
|
97
98
|
"scripts/scoped-sidebar-tabs.test.mjs",
|
|
99
|
+
"scripts/send-retry.test.mjs",
|
|
98
100
|
"scripts/git-diff-view.test.mjs",
|
|
99
101
|
"scripts/fork-server.test.mjs",
|
|
100
102
|
"scripts/styles-no-dangling.test.mjs",
|
|
@@ -115,6 +117,7 @@
|
|
|
115
117
|
"scripts/static-client-assets.test.mjs",
|
|
116
118
|
"scripts/cli.test.mjs",
|
|
117
119
|
"scripts/check-runner.test.mjs",
|
|
120
|
+
"scripts/skills-discovery.test.mjs",
|
|
118
121
|
"scripts/shared-chrome.test.mjs"
|
|
119
122
|
],
|
|
120
123
|
"check:browser": [
|
|
@@ -220,7 +220,7 @@ function findVulnerableCopies() {
|
|
|
220
220
|
try { version = JSON.parse(readFileSync(manifest, "utf8")).version ?? ""; } catch { continue; }
|
|
221
221
|
if (!version) continue;
|
|
222
222
|
if (compareVersions(version, VULNERABLE_BELOW) >= 0) continue;
|
|
223
|
-
found.push({ directory, version
|
|
223
|
+
found.push({ directory, version });
|
|
224
224
|
}
|
|
225
225
|
return found;
|
|
226
226
|
}
|