@cremini/skillpack 1.2.2 → 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.
Files changed (3) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +184 -6
  3. package/package.json +1 -1
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 minimal:
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
@@ -1787,20 +1787,120 @@ function parseSkillMd(filePath) {
1787
1787
  return null;
1788
1788
  }
1789
1789
  const frontmatter = frontmatterMatch[1];
1790
- const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
1791
- const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
1792
- if (!nameMatch) {
1790
+ const name = readFrontmatterField(frontmatter, "name");
1791
+ if (!name) {
1793
1792
  return null;
1794
1793
  }
1795
1794
  return {
1796
- name: nameMatch[1].trim(),
1797
- description: descMatch ? descMatch[1].trim() : "",
1795
+ name,
1796
+ description: readFrontmatterField(frontmatter, "description") ?? "",
1798
1797
  dir: path2.dirname(filePath)
1799
1798
  };
1800
1799
  } catch {
1801
1800
  return null;
1802
1801
  }
1803
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
+ }
1804
1904
  function syncSkillDescriptions(workDir, config) {
1805
1905
  const descriptionByName = /* @__PURE__ */ new Map();
1806
1906
  for (const skill of scanInstalledSkills(workDir)) {
@@ -1880,6 +1980,12 @@ async function zipCommand(workDir) {
1880
1980
  archive.file(getPackPath(workDir), {
1881
1981
  name: `${prefix}/${PACK_FILE}`
1882
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
+ }
1883
1989
  const skillsDir = path3.join(workDir, "skills");
1884
1990
  if (fs3.existsSync(skillsDir)) {
1885
1991
  archive.directory(skillsDir, `${prefix}/skills`);
@@ -5018,6 +5124,8 @@ var BUILTIN_SKILL_CREATOR_DESCRIPTION = "Create new skills, modify and improve e
5018
5124
  var BUILTIN_SKILL_CREATOR_TEMPLATE_DIR = fileURLToPath(
5019
5125
  new URL("../templates/builtin-skills/skill-creator", import.meta.url)
5020
5126
  );
5127
+ var PACK_AGENTS_FILE = "AGENTS.md";
5128
+ var PACK_SOUL_FILE = "SOUL.md";
5021
5129
  function materializeBuiltinSkillCreator(rootDir, skillsPath) {
5022
5130
  if (!fs8.existsSync(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR)) {
5023
5131
  log(
@@ -5082,6 +5190,57 @@ function overrideBuiltinSkillCreator(base, materializedSkill) {
5082
5190
  diagnostics: base.diagnostics
5083
5191
  };
5084
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
+ }
5085
5244
  function getAssistantDiagnostics(message) {
5086
5245
  if (!message || message.role !== "assistant") {
5087
5246
  return null;
@@ -5190,10 +5349,27 @@ var PackAgent = class {
5190
5349
  `[PackAgent] Materialized built-in skill-creator to: ${materializedSkillCreator.filePath}`
5191
5350
  );
5192
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
+ );
5193
5366
  const resourceLoader = new DefaultResourceLoader({
5194
5367
  cwd: rootDir,
5195
5368
  additionalSkillPaths: [skillsPath],
5196
- skillsOverride: (base) => overrideBuiltinSkillCreator(base, materializedSkillCreator)
5369
+ skillsOverride: (base) => overrideBuiltinSkillCreator(base, materializedSkillCreator),
5370
+ agentsFilesOverride: () => ({ agentsFiles: [] }),
5371
+ systemPromptOverride: () => void 0,
5372
+ appendSystemPromptOverride: () => packPromptFiles.promptBlock ? [packPromptFiles.promptBlock] : []
5197
5373
  });
5198
5374
  await resourceLoader.reload();
5199
5375
  const tools = createCodingTools(workspaceDir);
@@ -6206,6 +6382,8 @@ async function runCommand(directory) {
6206
6382
  console.warn(chalk4.yellow(` Warning: Some skills could not be installed: ${err}`));
6207
6383
  }
6208
6384
  }
6385
+ syncSkillDescriptions(workDir, config);
6386
+ saveConfig(workDir, config);
6209
6387
  await startServer({
6210
6388
  rootDir: workDir,
6211
6389
  daemonRun: process.env.DAEMON_RUN === "1"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cremini/skillpack",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Pack AI Skills into Local Agents",
5
5
  "type": "module",
6
6
  "repository": {