@cremini/skillpack 1.0.2 → 1.0.4
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 +12 -2
- package/dist/cli.js +123 -26
- package/package.json +4 -4
- package/runtime/scripts/start.sh +2 -2
- package/runtime/server/index.js +18 -14
- package/runtime/server/routes.js +3 -3
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SkillPack - Pack AI Skills into Standalone Apps
|
|
1
|
+
# SkillPack.sh - Pack AI Skills into Standalone Apps
|
|
2
2
|
|
|
3
3
|
Go to [skillpack.sh](https://skillpack.sh) to pack skills and try existing skill packs.
|
|
4
4
|
|
|
@@ -27,6 +27,15 @@ Step-by-Step
|
|
|
27
27
|
3. Add prompts to orchestrate and organize skills you added to accomplish tasks
|
|
28
28
|
4. (Optional) bundle the result as a zip
|
|
29
29
|
|
|
30
|
+
### Initialize from an Existing Config
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx @cremini/skillpack init --config ./skillpack.json
|
|
34
|
+
npx @cremini/skillpack init output --config https://example.com/skillpack.json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This loads a local or remote config, writes `skillpack.json` into the target directory, installs any pending skills, and skips zip packaging unless you pass `--bundle`.
|
|
38
|
+
|
|
30
39
|
### Step-by-Step Commands
|
|
31
40
|
|
|
32
41
|
```bash
|
|
@@ -47,6 +56,7 @@ npx @cremini/skillpack build
|
|
|
47
56
|
| Command | Description |
|
|
48
57
|
| ------------------------ | ------------------------------------- |
|
|
49
58
|
| `create` | Create a skill pack interactively |
|
|
59
|
+
| `init` | Initialize from a config path or URL |
|
|
50
60
|
| `skills add <source>` | Add a skill |
|
|
51
61
|
| `skills remove <name>` | Remove a skill |
|
|
52
62
|
| `skills list` | List installed skills |
|
|
@@ -61,7 +71,7 @@ The extracted archive looks like this:
|
|
|
61
71
|
|
|
62
72
|
```text
|
|
63
73
|
skillpack/
|
|
64
|
-
├──
|
|
74
|
+
├── skillpack.json # Pack configuration
|
|
65
75
|
├── skills/ # Collected SKILL.md files
|
|
66
76
|
├── server/ # Express backend
|
|
67
77
|
├── web/ # Web chat UI
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import chalk8 from "chalk";
|
|
6
6
|
|
|
7
7
|
// src/commands/create.ts
|
|
8
8
|
import inquirer from "inquirer";
|
|
@@ -11,7 +11,7 @@ import chalk3 from "chalk";
|
|
|
11
11
|
// src/core/pack-config.ts
|
|
12
12
|
import fs from "fs";
|
|
13
13
|
import path from "path";
|
|
14
|
-
var PACK_FILE = "
|
|
14
|
+
var PACK_FILE = "skillpack.json";
|
|
15
15
|
function getPackPath(workDir) {
|
|
16
16
|
return path.join(workDir, PACK_FILE);
|
|
17
17
|
}
|
|
@@ -28,7 +28,7 @@ function loadConfig(workDir) {
|
|
|
28
28
|
const filePath = getPackPath(workDir);
|
|
29
29
|
if (!fs.existsSync(filePath)) {
|
|
30
30
|
throw new Error(
|
|
31
|
-
`Could not find ${PACK_FILE}. Run npx @cremini/skillpack create first or work in a directory that contains
|
|
31
|
+
`Could not find ${PACK_FILE}. Run npx @cremini/skillpack create first or work in a directory that contains ${PACK_FILE}`
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
@@ -154,6 +154,7 @@ function getRuntimeDir() {
|
|
|
154
154
|
}
|
|
155
155
|
async function bundle(workDir) {
|
|
156
156
|
const config = loadConfig(workDir);
|
|
157
|
+
saveConfig(workDir, config);
|
|
157
158
|
const zipName = `${config.name}.zip`;
|
|
158
159
|
const zipPath = path3.join(workDir, zipName);
|
|
159
160
|
const runtimeDir = getRuntimeDir();
|
|
@@ -176,8 +177,8 @@ async function bundle(workDir) {
|
|
|
176
177
|
archive.on("error", (err) => reject(err));
|
|
177
178
|
archive.pipe(output);
|
|
178
179
|
const prefix = config.name;
|
|
179
|
-
archive.file(
|
|
180
|
-
name: `${prefix}
|
|
180
|
+
archive.file(getPackPath(workDir), {
|
|
181
|
+
name: `${prefix}/${PACK_FILE}`
|
|
181
182
|
});
|
|
182
183
|
const skillsDir = path3.join(workDir, "skills");
|
|
183
184
|
if (fs3.existsSync(skillsDir)) {
|
|
@@ -233,7 +234,7 @@ async function createCommand(directory) {
|
|
|
233
234
|
{
|
|
234
235
|
type: "confirm",
|
|
235
236
|
name: "overwrite",
|
|
236
|
-
message:
|
|
237
|
+
message: `Overwrite the existing ${PACK_FILE}?`,
|
|
237
238
|
default: false
|
|
238
239
|
}
|
|
239
240
|
]);
|
|
@@ -343,7 +344,9 @@ async function createCommand(directory) {
|
|
|
343
344
|
promptIndex++;
|
|
344
345
|
}
|
|
345
346
|
saveConfig(workDir, config);
|
|
346
|
-
console.log(chalk3.green(
|
|
347
|
+
console.log(chalk3.green(`
|
|
348
|
+
${PACK_FILE} saved
|
|
349
|
+
`));
|
|
347
350
|
const { shouldBundle } = await inquirer.prompt([
|
|
348
351
|
{
|
|
349
352
|
type: "confirm",
|
|
@@ -363,8 +366,97 @@ async function createCommand(directory) {
|
|
|
363
366
|
}
|
|
364
367
|
}
|
|
365
368
|
|
|
366
|
-
// src/commands/
|
|
369
|
+
// src/commands/init.ts
|
|
370
|
+
import fs5 from "fs";
|
|
371
|
+
import path5 from "path";
|
|
372
|
+
import inquirer2 from "inquirer";
|
|
367
373
|
import chalk4 from "chalk";
|
|
374
|
+
function isHttpUrl(value) {
|
|
375
|
+
try {
|
|
376
|
+
const url = new URL(value);
|
|
377
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
378
|
+
} catch {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function validateConfigShape(value, source) {
|
|
383
|
+
if (!value || typeof value !== "object") {
|
|
384
|
+
throw new Error(`Invalid config from ${source}: expected a JSON object`);
|
|
385
|
+
}
|
|
386
|
+
const config = value;
|
|
387
|
+
if (typeof config.name !== "string" || !config.name.trim()) {
|
|
388
|
+
throw new Error(`Invalid config from ${source}: "name" is required`);
|
|
389
|
+
}
|
|
390
|
+
if (typeof config.description !== "string") {
|
|
391
|
+
throw new Error(`Invalid config from ${source}: "description" must be a string`);
|
|
392
|
+
}
|
|
393
|
+
if (typeof config.version !== "string") {
|
|
394
|
+
throw new Error(`Invalid config from ${source}: "version" must be a string`);
|
|
395
|
+
}
|
|
396
|
+
if (!Array.isArray(config.prompts) || !config.prompts.every((p) => typeof p === "string")) {
|
|
397
|
+
throw new Error(`Invalid config from ${source}: "prompts" must be a string array`);
|
|
398
|
+
}
|
|
399
|
+
if (!Array.isArray(config.skills)) {
|
|
400
|
+
throw new Error(`Invalid config from ${source}: "skills" must be an array`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
async function readConfigSource(source) {
|
|
404
|
+
let raw = "";
|
|
405
|
+
if (isHttpUrl(source)) {
|
|
406
|
+
const response = await fetch(source);
|
|
407
|
+
if (!response.ok) {
|
|
408
|
+
throw new Error(`Failed to download config: ${response.status} ${response.statusText}`);
|
|
409
|
+
}
|
|
410
|
+
raw = await response.text();
|
|
411
|
+
} else {
|
|
412
|
+
const filePath = path5.resolve(source);
|
|
413
|
+
raw = fs5.readFileSync(filePath, "utf-8");
|
|
414
|
+
}
|
|
415
|
+
const parsed = JSON.parse(raw);
|
|
416
|
+
validateConfigShape(parsed, source);
|
|
417
|
+
return parsed;
|
|
418
|
+
}
|
|
419
|
+
async function initCommand(directory, options) {
|
|
420
|
+
const workDir = directory ? path5.resolve(directory) : process.cwd();
|
|
421
|
+
if (directory) {
|
|
422
|
+
fs5.mkdirSync(workDir, { recursive: true });
|
|
423
|
+
}
|
|
424
|
+
if (configExists(workDir)) {
|
|
425
|
+
const { overwrite } = await inquirer2.prompt([
|
|
426
|
+
{
|
|
427
|
+
type: "confirm",
|
|
428
|
+
name: "overwrite",
|
|
429
|
+
message: `A ${PACK_FILE} file already exists in this directory. Overwrite it?`,
|
|
430
|
+
default: false
|
|
431
|
+
}
|
|
432
|
+
]);
|
|
433
|
+
if (!overwrite) {
|
|
434
|
+
console.log(chalk4.yellow("Cancelled"));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const config = await readConfigSource(options.config);
|
|
439
|
+
saveConfig(workDir, config);
|
|
440
|
+
console.log(chalk4.blue(`
|
|
441
|
+
Initialize ${config.name} from ${options.config}
|
|
442
|
+
`));
|
|
443
|
+
await installPendingSkills(workDir, config);
|
|
444
|
+
if (options.bundle) {
|
|
445
|
+
await bundle(workDir);
|
|
446
|
+
}
|
|
447
|
+
console.log(chalk4.green(`
|
|
448
|
+
${PACK_FILE} saved
|
|
449
|
+
`));
|
|
450
|
+
console.log(chalk4.green(" Initialization complete.\n"));
|
|
451
|
+
if (!options.bundle) {
|
|
452
|
+
console.log(
|
|
453
|
+
chalk4.dim(" Run npx @cremini/skillpack build to create the zip when needed\n")
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/commands/skills-cmd.ts
|
|
459
|
+
import chalk5 from "chalk";
|
|
368
460
|
function registerSkillsCommand(program2) {
|
|
369
461
|
const skills = program2.command("skills").description("Manage skills in the app");
|
|
370
462
|
skills.command("add <source>").description("Add a skill from a git repo, URL, or local path").option("-s, --skill <names...>", "Specify skill name(s)").action(async (source, opts) => {
|
|
@@ -379,7 +471,7 @@ function registerSkillsCommand(program2) {
|
|
|
379
471
|
});
|
|
380
472
|
saveConfig(workDir, config);
|
|
381
473
|
console.log(
|
|
382
|
-
|
|
474
|
+
chalk5.green(
|
|
383
475
|
`Skill list updated (${config.skills.length} total). Skills will be installed during build.`
|
|
384
476
|
)
|
|
385
477
|
);
|
|
@@ -390,16 +482,16 @@ function registerSkillsCommand(program2) {
|
|
|
390
482
|
skills.command("list").description("List installed skills").action(() => {
|
|
391
483
|
const config = loadConfig(process.cwd());
|
|
392
484
|
if (config.skills.length === 0) {
|
|
393
|
-
console.log(
|
|
485
|
+
console.log(chalk5.dim(" No skills installed"));
|
|
394
486
|
return;
|
|
395
487
|
}
|
|
396
|
-
console.log(
|
|
488
|
+
console.log(chalk5.blue(`
|
|
397
489
|
${config.name} Skills:
|
|
398
490
|
`));
|
|
399
491
|
for (const skill of config.skills) {
|
|
400
|
-
console.log(` ${
|
|
492
|
+
console.log(` ${chalk5.green("\u25CF")} ${chalk5.bold(skill.name)}`);
|
|
401
493
|
if (skill.description) {
|
|
402
|
-
console.log(` ${
|
|
494
|
+
console.log(` ${chalk5.dim(skill.description)}`);
|
|
403
495
|
}
|
|
404
496
|
}
|
|
405
497
|
console.log();
|
|
@@ -407,29 +499,29 @@ function registerSkillsCommand(program2) {
|
|
|
407
499
|
}
|
|
408
500
|
|
|
409
501
|
// src/commands/prompts-cmd.ts
|
|
410
|
-
import
|
|
502
|
+
import chalk7 from "chalk";
|
|
411
503
|
|
|
412
504
|
// src/core/prompts.ts
|
|
413
|
-
import
|
|
505
|
+
import chalk6 from "chalk";
|
|
414
506
|
function addPrompt(workDir, text) {
|
|
415
507
|
const config = loadConfig(workDir);
|
|
416
508
|
config.prompts.push(text);
|
|
417
509
|
saveConfig(workDir, config);
|
|
418
|
-
console.log(
|
|
510
|
+
console.log(chalk6.green(`Added Prompt #${config.prompts.length}`));
|
|
419
511
|
}
|
|
420
512
|
function removePrompt(workDir, index) {
|
|
421
513
|
const config = loadConfig(workDir);
|
|
422
514
|
const idx = index - 1;
|
|
423
515
|
if (idx < 0 || idx >= config.prompts.length) {
|
|
424
516
|
console.log(
|
|
425
|
-
|
|
517
|
+
chalk6.yellow(`Invalid index: ${index} (${config.prompts.length} total)`)
|
|
426
518
|
);
|
|
427
519
|
return false;
|
|
428
520
|
}
|
|
429
521
|
const removed = config.prompts.splice(idx, 1)[0];
|
|
430
522
|
saveConfig(workDir, config);
|
|
431
523
|
console.log(
|
|
432
|
-
|
|
524
|
+
chalk6.green(`Removed Prompt #${index}: "${removed.substring(0, 50)}..."`)
|
|
433
525
|
);
|
|
434
526
|
return true;
|
|
435
527
|
}
|
|
@@ -447,7 +539,7 @@ function registerPromptsCommand(program2) {
|
|
|
447
539
|
prompts.command("remove <index>").description("Remove a prompt by number, starting from 1").action((index) => {
|
|
448
540
|
const num = parseInt(index, 10);
|
|
449
541
|
if (isNaN(num)) {
|
|
450
|
-
console.log(
|
|
542
|
+
console.log(chalk7.red("Enter a valid numeric index"));
|
|
451
543
|
return;
|
|
452
544
|
}
|
|
453
545
|
removePrompt(process.cwd(), num);
|
|
@@ -455,13 +547,13 @@ function registerPromptsCommand(program2) {
|
|
|
455
547
|
prompts.command("list").description("List all prompts").action(() => {
|
|
456
548
|
const prompts2 = listPrompts(process.cwd());
|
|
457
549
|
if (prompts2.length === 0) {
|
|
458
|
-
console.log(
|
|
550
|
+
console.log(chalk7.dim(" No prompts yet"));
|
|
459
551
|
return;
|
|
460
552
|
}
|
|
461
|
-
console.log(
|
|
553
|
+
console.log(chalk7.blue("\n Prompts:\n"));
|
|
462
554
|
prompts2.forEach((u, i) => {
|
|
463
|
-
const marker = i === 0 ?
|
|
464
|
-
const label = i === 0 ?
|
|
555
|
+
const marker = i === 0 ? chalk7.green("\u2605") : chalk7.dim("\u25CF");
|
|
556
|
+
const label = i === 0 ? chalk7.dim(" (default)") : "";
|
|
465
557
|
const display = u.length > 80 ? u.substring(0, 80) + "..." : u;
|
|
466
558
|
console.log(` ${marker} #${i + 1}${label} ${display}`);
|
|
467
559
|
});
|
|
@@ -470,22 +562,27 @@ function registerPromptsCommand(program2) {
|
|
|
470
562
|
}
|
|
471
563
|
|
|
472
564
|
// src/cli.ts
|
|
473
|
-
import
|
|
565
|
+
import fs6 from "fs";
|
|
474
566
|
var packageJson = JSON.parse(
|
|
475
|
-
|
|
567
|
+
fs6.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
|
|
476
568
|
);
|
|
477
569
|
var program = new Command();
|
|
478
570
|
program.name("skillpack").description("Assemble, package, and run Agent Skills packs").version(packageJson.version);
|
|
479
571
|
program.command("create [directory]").description("Create a skills pack interactively").action(async (directory) => {
|
|
480
572
|
await createCommand(directory);
|
|
481
573
|
});
|
|
574
|
+
program.command("init [directory]").description("Initialize a skills pack from a local config file or URL").requiredOption("--config <path-or-url>", "Path or URL to a skillpack.json file").option("--bundle", "Bundle as a zip after initialization").action(
|
|
575
|
+
async (directory, options) => {
|
|
576
|
+
await initCommand(directory, options);
|
|
577
|
+
}
|
|
578
|
+
);
|
|
482
579
|
registerSkillsCommand(program);
|
|
483
580
|
registerPromptsCommand(program);
|
|
484
581
|
program.command("build").description("Package the current pack as a zip file").action(async () => {
|
|
485
582
|
try {
|
|
486
583
|
await bundle(process.cwd());
|
|
487
584
|
} catch (err) {
|
|
488
|
-
console.error(
|
|
585
|
+
console.error(chalk8.red(`Packaging failed: ${err}`));
|
|
489
586
|
process.exit(1);
|
|
490
587
|
}
|
|
491
588
|
});
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cremini/skillpack",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Turn Skills into a Standalone App with UI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/
|
|
8
|
+
"url": "git+https://github.com/CreminiAI/skillpack.git"
|
|
9
9
|
},
|
|
10
10
|
"bugs": {
|
|
11
|
-
"url": "https://github.com/
|
|
11
|
+
"url": "https://github.com/CreminiAI/skillpack/issues"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://github.com/
|
|
13
|
+
"homepage": "https://github.com/CreminiAI/skillpack#readme",
|
|
14
14
|
"bin": {
|
|
15
15
|
"skillpack": "dist/cli.js"
|
|
16
16
|
},
|
package/runtime/scripts/start.sh
CHANGED
|
@@ -3,8 +3,8 @@ cd "$(dirname "$0")"
|
|
|
3
3
|
|
|
4
4
|
# Read the pack name
|
|
5
5
|
PACK_NAME="Skills Pack"
|
|
6
|
-
if [ -f "
|
|
7
|
-
PACK_NAME=$(node -e "console.log(JSON.parse(require('fs').readFileSync('
|
|
6
|
+
if [ -f "skillpack.json" ] && command -v node &> /dev/null; then
|
|
7
|
+
PACK_NAME=$(node -e "console.log(JSON.parse(require('fs').readFileSync('skillpack.json','utf-8')).name)" 2>/dev/null || echo "Skills Pack")
|
|
8
8
|
fi
|
|
9
9
|
|
|
10
10
|
echo ""
|
package/runtime/server/index.js
CHANGED
|
@@ -28,21 +28,25 @@ registerRoutes(app, server, rootDir);
|
|
|
28
28
|
const HOST = process.env.HOST || "127.0.0.1";
|
|
29
29
|
const DEFAULT_PORT = 26313;
|
|
30
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
|
+
|
|
31
48
|
function tryListen(port) {
|
|
32
|
-
server.listen(port, HOST
|
|
33
|
-
const url = `http://${HOST}:${port}`;
|
|
34
|
-
console.log(`\n Skills Pack Server`);
|
|
35
|
-
console.log(` Running at ${url}\n`);
|
|
36
|
-
|
|
37
|
-
// Open the browser automatically
|
|
38
|
-
const cmd =
|
|
39
|
-
process.platform === "darwin"
|
|
40
|
-
? `open ${url}`
|
|
41
|
-
: process.platform === "win32"
|
|
42
|
-
? `start ${url}`
|
|
43
|
-
: `xdg-open ${url}`;
|
|
44
|
-
exec(cmd, () => {});
|
|
45
|
-
});
|
|
49
|
+
server.listen(port, HOST);
|
|
46
50
|
|
|
47
51
|
server.once("error", (err) => {
|
|
48
52
|
if (err.code === "EADDRINUSE") {
|
package/runtime/server/routes.js
CHANGED
|
@@ -5,11 +5,11 @@ import { WebSocketServer } from "ws";
|
|
|
5
5
|
import { handleWsConnection } from "./chat-proxy.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Read the
|
|
8
|
+
* Read the skillpack.json config.
|
|
9
9
|
* @param {string} rootDir
|
|
10
10
|
*/
|
|
11
11
|
function getPackConfig(rootDir) {
|
|
12
|
-
const raw = fs.readFileSync(path.join(rootDir, "
|
|
12
|
+
const raw = fs.readFileSync(path.join(rootDir, "skillpack.json"), "utf-8");
|
|
13
13
|
return JSON.parse(raw);
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@ function getPackConfig(rootDir) {
|
|
|
17
17
|
* Register all API routes.
|
|
18
18
|
* @param {import("express").Express} app
|
|
19
19
|
* @param {import("node:http").Server} server
|
|
20
|
-
* @param {string} rootDir - Root directory containing
|
|
20
|
+
* @param {string} rootDir - Root directory containing skillpack.json and skills/
|
|
21
21
|
*/
|
|
22
22
|
export function registerRoutes(app, server, rootDir) {
|
|
23
23
|
// API key and provider are stored in runtime memory
|