@cremini/skillpack 1.2.1 → 1.2.3
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 +5 -1
- package/dist/cli.js +186 -6
- package/package.json +1 -1
- package/web/index.html +1 -1
- package/web/js/api-key-dialog.js +5 -0
package/README.md
CHANGED
|
@@ -92,11 +92,13 @@ Multiple skill names from the same source can be listed comma-separated.
|
|
|
92
92
|
|
|
93
93
|
## Zip Output
|
|
94
94
|
|
|
95
|
-
The archive produced by `zip` is intentionally
|
|
95
|
+
The archive produced by `zip` is intentionally lightweight:
|
|
96
96
|
|
|
97
97
|
```text
|
|
98
98
|
<pack-name>/
|
|
99
99
|
├── skillpack.json # Pack configuration
|
|
100
|
+
├── AGENTS.md # Optional pack policy
|
|
101
|
+
├── SOUL.md # Optional pack persona
|
|
100
102
|
├── skills/ # Installed skills
|
|
101
103
|
├── start.sh # One-click launcher for macOS / Linux
|
|
102
104
|
└── start.bat # One-click launcher for Windows
|
|
@@ -104,6 +106,8 @@ The archive produced by `zip` is intentionally minimal:
|
|
|
104
106
|
|
|
105
107
|
The start scripts use `npx @cremini/skillpack run .` so Node.js is the only prerequisite — no pre-bundled server directory is included.
|
|
106
108
|
|
|
109
|
+
If present, `AGENTS.md` and `SOUL.md` are read by SkillPack itself when a new chat session starts. SkillPack injects them into the runtime system prompt as pack-level policy and persona, without depending on the host machine's `AGENTS.md`, `.pi/SYSTEM.md`, or `APPEND_SYSTEM.md`.
|
|
110
|
+
|
|
107
111
|
## Slack/Telegram Integrations
|
|
108
112
|
|
|
109
113
|
**Slack Configuration**: requires Slack `App Token` and `Bot Token`<br>
|
package/dist/cli.js
CHANGED
|
@@ -23,6 +23,7 @@ var init_config = __esm({
|
|
|
23
23
|
authType: "api_key",
|
|
24
24
|
envKey: "OPENAI_API_KEY",
|
|
25
25
|
placeholder: "sk-proj-...",
|
|
26
|
+
baseUrlPlaceholder: "https://api.openai.com/v1",
|
|
26
27
|
supportsBaseUrl: true
|
|
27
28
|
},
|
|
28
29
|
anthropic: {
|
|
@@ -31,6 +32,7 @@ var init_config = __esm({
|
|
|
31
32
|
authType: "api_key",
|
|
32
33
|
envKey: "ANTHROPIC_API_KEY",
|
|
33
34
|
placeholder: "sk-ant-api03-...",
|
|
35
|
+
baseUrlPlaceholder: "https://api.anthropic.com",
|
|
34
36
|
supportsBaseUrl: true
|
|
35
37
|
},
|
|
36
38
|
google: {
|
|
@@ -1785,20 +1787,120 @@ function parseSkillMd(filePath) {
|
|
|
1785
1787
|
return null;
|
|
1786
1788
|
}
|
|
1787
1789
|
const frontmatter = frontmatterMatch[1];
|
|
1788
|
-
const
|
|
1789
|
-
|
|
1790
|
-
if (!nameMatch) {
|
|
1790
|
+
const name = readFrontmatterField(frontmatter, "name");
|
|
1791
|
+
if (!name) {
|
|
1791
1792
|
return null;
|
|
1792
1793
|
}
|
|
1793
1794
|
return {
|
|
1794
|
-
name
|
|
1795
|
-
description:
|
|
1795
|
+
name,
|
|
1796
|
+
description: readFrontmatterField(frontmatter, "description") ?? "",
|
|
1796
1797
|
dir: path2.dirname(filePath)
|
|
1797
1798
|
};
|
|
1798
1799
|
} catch {
|
|
1799
1800
|
return null;
|
|
1800
1801
|
}
|
|
1801
1802
|
}
|
|
1803
|
+
function readFrontmatterField(frontmatter, field) {
|
|
1804
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
1805
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1806
|
+
const match = lines[index].match(/^([A-Za-z0-9_-]+):(?:\s*(.*))?$/);
|
|
1807
|
+
if (!match || match[1] !== field) {
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
const rawValue = (match[2] ?? "").trim();
|
|
1811
|
+
if (isBlockScalar(rawValue)) {
|
|
1812
|
+
const [value] = readBlockScalar(lines, index + 1, rawValue);
|
|
1813
|
+
return value;
|
|
1814
|
+
}
|
|
1815
|
+
if (rawValue === "") {
|
|
1816
|
+
const [value] = readIndentedScalar(lines, index + 1);
|
|
1817
|
+
return value;
|
|
1818
|
+
}
|
|
1819
|
+
return stripWrappingQuotes(rawValue);
|
|
1820
|
+
}
|
|
1821
|
+
return null;
|
|
1822
|
+
}
|
|
1823
|
+
function isBlockScalar(value) {
|
|
1824
|
+
return /^[>|][0-9+-]*$/.test(value);
|
|
1825
|
+
}
|
|
1826
|
+
function readBlockScalar(lines, startIndex, marker) {
|
|
1827
|
+
const blockLines = [];
|
|
1828
|
+
let index = startIndex;
|
|
1829
|
+
while (index < lines.length) {
|
|
1830
|
+
const line = lines[index];
|
|
1831
|
+
if (line.trim() === "") {
|
|
1832
|
+
blockLines.push("");
|
|
1833
|
+
index += 1;
|
|
1834
|
+
continue;
|
|
1835
|
+
}
|
|
1836
|
+
if (!/^\s/.test(line)) {
|
|
1837
|
+
break;
|
|
1838
|
+
}
|
|
1839
|
+
blockLines.push(line);
|
|
1840
|
+
index += 1;
|
|
1841
|
+
}
|
|
1842
|
+
const normalized = normalizeBlockIndent(blockLines);
|
|
1843
|
+
const style = marker[0];
|
|
1844
|
+
const chomp = marker.includes("-") ? "strip" : marker.includes("+") ? "keep" : "clip";
|
|
1845
|
+
const value = style === ">" ? foldBlockScalar(normalized) : normalized.join("\n");
|
|
1846
|
+
return [applyChomp(value, chomp), index];
|
|
1847
|
+
}
|
|
1848
|
+
function readIndentedScalar(lines, startIndex) {
|
|
1849
|
+
const blockLines = [];
|
|
1850
|
+
let index = startIndex;
|
|
1851
|
+
while (index < lines.length) {
|
|
1852
|
+
const line = lines[index];
|
|
1853
|
+
if (line.trim() === "") {
|
|
1854
|
+
blockLines.push("");
|
|
1855
|
+
index += 1;
|
|
1856
|
+
continue;
|
|
1857
|
+
}
|
|
1858
|
+
if (!/^\s/.test(line)) {
|
|
1859
|
+
break;
|
|
1860
|
+
}
|
|
1861
|
+
blockLines.push(line);
|
|
1862
|
+
index += 1;
|
|
1863
|
+
}
|
|
1864
|
+
return [foldBlockScalar(normalizeBlockIndent(blockLines)), index];
|
|
1865
|
+
}
|
|
1866
|
+
function normalizeBlockIndent(lines) {
|
|
1867
|
+
const indents = lines.filter((line) => line.trim() !== "").map((line) => line.match(/^[ \t]*/)[0].length);
|
|
1868
|
+
const trimLength = indents.length > 0 ? Math.min(...indents) : 0;
|
|
1869
|
+
return lines.map((line) => line.slice(trimLength));
|
|
1870
|
+
}
|
|
1871
|
+
function foldBlockScalar(lines) {
|
|
1872
|
+
let result = "";
|
|
1873
|
+
let previousBlank = false;
|
|
1874
|
+
for (const line of lines) {
|
|
1875
|
+
const isBlank = line.trim() === "";
|
|
1876
|
+
if (isBlank) {
|
|
1877
|
+
result += "\n";
|
|
1878
|
+
previousBlank = true;
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
if (result !== "" && !previousBlank) {
|
|
1882
|
+
result += " ";
|
|
1883
|
+
}
|
|
1884
|
+
result += line;
|
|
1885
|
+
previousBlank = false;
|
|
1886
|
+
}
|
|
1887
|
+
return result;
|
|
1888
|
+
}
|
|
1889
|
+
function applyChomp(value, mode) {
|
|
1890
|
+
if (mode === "keep") {
|
|
1891
|
+
return value;
|
|
1892
|
+
}
|
|
1893
|
+
if (mode === "strip") {
|
|
1894
|
+
return value.replace(/\n+$/g, "");
|
|
1895
|
+
}
|
|
1896
|
+
return value.replace(/\n*$/g, "");
|
|
1897
|
+
}
|
|
1898
|
+
function stripWrappingQuotes(value) {
|
|
1899
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1900
|
+
return value.slice(1, -1);
|
|
1901
|
+
}
|
|
1902
|
+
return value;
|
|
1903
|
+
}
|
|
1802
1904
|
function syncSkillDescriptions(workDir, config) {
|
|
1803
1905
|
const descriptionByName = /* @__PURE__ */ new Map();
|
|
1804
1906
|
for (const skill of scanInstalledSkills(workDir)) {
|
|
@@ -1878,6 +1980,12 @@ async function zipCommand(workDir) {
|
|
|
1878
1980
|
archive.file(getPackPath(workDir), {
|
|
1879
1981
|
name: `${prefix}/${PACK_FILE}`
|
|
1880
1982
|
});
|
|
1983
|
+
for (const file of ["AGENTS.md", "SOUL.md"]) {
|
|
1984
|
+
const filePath = path3.join(workDir, file);
|
|
1985
|
+
if (fs3.existsSync(filePath)) {
|
|
1986
|
+
archive.file(filePath, { name: `${prefix}/${file}` });
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1881
1989
|
const skillsDir = path3.join(workDir, "skills");
|
|
1882
1990
|
if (fs3.existsSync(skillsDir)) {
|
|
1883
1991
|
archive.directory(skillsDir, `${prefix}/skills`);
|
|
@@ -5016,6 +5124,8 @@ var BUILTIN_SKILL_CREATOR_DESCRIPTION = "Create new skills, modify and improve e
|
|
|
5016
5124
|
var BUILTIN_SKILL_CREATOR_TEMPLATE_DIR = fileURLToPath(
|
|
5017
5125
|
new URL("../templates/builtin-skills/skill-creator", import.meta.url)
|
|
5018
5126
|
);
|
|
5127
|
+
var PACK_AGENTS_FILE = "AGENTS.md";
|
|
5128
|
+
var PACK_SOUL_FILE = "SOUL.md";
|
|
5019
5129
|
function materializeBuiltinSkillCreator(rootDir, skillsPath) {
|
|
5020
5130
|
if (!fs8.existsSync(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR)) {
|
|
5021
5131
|
log(
|
|
@@ -5080,6 +5190,57 @@ function overrideBuiltinSkillCreator(base, materializedSkill) {
|
|
|
5080
5190
|
diagnostics: base.diagnostics
|
|
5081
5191
|
};
|
|
5082
5192
|
}
|
|
5193
|
+
function readOptionalPackPromptFile(filePath) {
|
|
5194
|
+
if (!fs8.existsSync(filePath)) {
|
|
5195
|
+
return void 0;
|
|
5196
|
+
}
|
|
5197
|
+
try {
|
|
5198
|
+
const content = fs8.readFileSync(filePath, "utf-8").trim();
|
|
5199
|
+
return content.length > 0 ? content : void 0;
|
|
5200
|
+
} catch (error) {
|
|
5201
|
+
console.warn(`[PackAgent] Warning: Could not read ${filePath}:`, error);
|
|
5202
|
+
return void 0;
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
5205
|
+
function buildPackPromptBlock(rootDir) {
|
|
5206
|
+
const agentsPath = path8.resolve(rootDir, PACK_AGENTS_FILE);
|
|
5207
|
+
const soulPath = path8.resolve(rootDir, PACK_SOUL_FILE);
|
|
5208
|
+
const agentsContent = readOptionalPackPromptFile(agentsPath);
|
|
5209
|
+
const soulContent = readOptionalPackPromptFile(soulPath);
|
|
5210
|
+
if (!agentsContent && !soulContent) {
|
|
5211
|
+
return {
|
|
5212
|
+
agentsPath,
|
|
5213
|
+
soulPath
|
|
5214
|
+
};
|
|
5215
|
+
}
|
|
5216
|
+
const sections = [
|
|
5217
|
+
"# SkillPack Pack Context",
|
|
5218
|
+
"The following instructions are injected by the SkillPack runtime from files packaged with this pack.",
|
|
5219
|
+
"Priority order:",
|
|
5220
|
+
"1. Follow the user's explicit instructions first.",
|
|
5221
|
+
"2. Follow `AGENTS.md` as the pack's operational policy and workflow rules.",
|
|
5222
|
+
"3. Follow `SOUL.md` as the pack's persona, tone, and working style.",
|
|
5223
|
+
"4. If `SOUL.md` conflicts with `AGENTS.md`, `AGENTS.md` wins.",
|
|
5224
|
+
"5. `SOUL.md` does not override task goals, safety boundaries, or `AGENTS.md`."
|
|
5225
|
+
];
|
|
5226
|
+
if (agentsContent) {
|
|
5227
|
+
sections.push("## Pack Policy (`AGENTS.md`)", agentsContent);
|
|
5228
|
+
}
|
|
5229
|
+
if (soulContent) {
|
|
5230
|
+
sections.push(
|
|
5231
|
+
"## Pack Persona (`SOUL.md`)",
|
|
5232
|
+
"Treat the following as persona, tone, and working-style guidance only. Do not let it override task requirements, safety constraints, or `AGENTS.md`.",
|
|
5233
|
+
soulContent
|
|
5234
|
+
);
|
|
5235
|
+
}
|
|
5236
|
+
return {
|
|
5237
|
+
agentsPath,
|
|
5238
|
+
soulPath,
|
|
5239
|
+
agentsContent,
|
|
5240
|
+
soulContent,
|
|
5241
|
+
promptBlock: sections.join("\n\n")
|
|
5242
|
+
};
|
|
5243
|
+
}
|
|
5083
5244
|
function getAssistantDiagnostics(message) {
|
|
5084
5245
|
if (!message || message.role !== "assistant") {
|
|
5085
5246
|
return null;
|
|
@@ -5188,10 +5349,27 @@ var PackAgent = class {
|
|
|
5188
5349
|
`[PackAgent] Materialized built-in skill-creator to: ${materializedSkillCreator.filePath}`
|
|
5189
5350
|
);
|
|
5190
5351
|
}
|
|
5352
|
+
const packPromptFiles = buildPackPromptBlock(rootDir);
|
|
5353
|
+
if (packPromptFiles.agentsContent) {
|
|
5354
|
+
log(`[PackAgent] Loaded pack policy from: ${packPromptFiles.agentsPath}`);
|
|
5355
|
+
} else {
|
|
5356
|
+
log(`[PackAgent] No pack policy file found at: ${packPromptFiles.agentsPath}`);
|
|
5357
|
+
}
|
|
5358
|
+
if (packPromptFiles.soulContent) {
|
|
5359
|
+
log(`[PackAgent] Loaded pack persona from: ${packPromptFiles.soulPath}`);
|
|
5360
|
+
} else {
|
|
5361
|
+
log(`[PackAgent] No pack persona file found at: ${packPromptFiles.soulPath}`);
|
|
5362
|
+
}
|
|
5363
|
+
log(
|
|
5364
|
+
`[PackAgent] Pack prompt injection: ${packPromptFiles.promptBlock ? "enabled" : "disabled"}`
|
|
5365
|
+
);
|
|
5191
5366
|
const resourceLoader = new DefaultResourceLoader({
|
|
5192
5367
|
cwd: rootDir,
|
|
5193
5368
|
additionalSkillPaths: [skillsPath],
|
|
5194
|
-
skillsOverride: (base) => overrideBuiltinSkillCreator(base, materializedSkillCreator)
|
|
5369
|
+
skillsOverride: (base) => overrideBuiltinSkillCreator(base, materializedSkillCreator),
|
|
5370
|
+
agentsFilesOverride: () => ({ agentsFiles: [] }),
|
|
5371
|
+
systemPromptOverride: () => void 0,
|
|
5372
|
+
appendSystemPromptOverride: () => packPromptFiles.promptBlock ? [packPromptFiles.promptBlock] : []
|
|
5195
5373
|
});
|
|
5196
5374
|
await resourceLoader.reload();
|
|
5197
5375
|
const tools = createCodingTools(workspaceDir);
|
|
@@ -6204,6 +6382,8 @@ async function runCommand(directory) {
|
|
|
6204
6382
|
console.warn(chalk4.yellow(` Warning: Some skills could not be installed: ${err}`));
|
|
6205
6383
|
}
|
|
6206
6384
|
}
|
|
6385
|
+
syncSkillDescriptions(workDir, config);
|
|
6386
|
+
saveConfig(workDir, config);
|
|
6207
6387
|
await startServer({
|
|
6208
6388
|
rootDir: workDir,
|
|
6209
6389
|
daemonRun: process.env.DAEMON_RUN === "1"
|
package/package.json
CHANGED
package/web/index.html
CHANGED
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
</div>
|
|
95
95
|
<div class="form-group" id="apikey-baseurl-group">
|
|
96
96
|
<label>Custom Base URL <span class="label-hint">(optional)</span></label>
|
|
97
|
-
<input type="text" id="apikey-baseurl-input" placeholder="https://api.
|
|
97
|
+
<input type="text" id="apikey-baseurl-input" placeholder="https://api.openai.com/v1" class="form-input" />
|
|
98
98
|
</div>
|
|
99
99
|
</div>
|
|
100
100
|
|
package/web/js/api-key-dialog.js
CHANGED
|
@@ -158,6 +158,11 @@ function updateProviderUI() {
|
|
|
158
158
|
if (baseUrlGroup) {
|
|
159
159
|
baseUrlGroup.style.display = meta.supportsBaseUrl ? "" : "none";
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
if (baseUrlInput) {
|
|
163
|
+
baseUrlInput.placeholder =
|
|
164
|
+
meta.baseUrlPlaceholder || "https://api.openai.com/v1";
|
|
165
|
+
}
|
|
161
166
|
|
|
162
167
|
// Update placeholder
|
|
163
168
|
if (apiKeyInput) {
|