@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 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
- ├── app.json # App configuration
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 chalk7 from "chalk";
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 = "app.json";
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 app.json`
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(path3.join(workDir, "app.json"), {
180
- name: `${prefix}/app.json`
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: "An app.json file already exists in this directory. Overwrite it?",
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("\n app.json saved\n"));
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/skills-cmd.ts
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
- chalk4.green(
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(chalk4.dim(" No skills installed"));
485
+ console.log(chalk5.dim(" No skills installed"));
394
486
  return;
395
487
  }
396
- console.log(chalk4.blue(`
488
+ console.log(chalk5.blue(`
397
489
  ${config.name} Skills:
398
490
  `));
399
491
  for (const skill of config.skills) {
400
- console.log(` ${chalk4.green("\u25CF")} ${chalk4.bold(skill.name)}`);
492
+ console.log(` ${chalk5.green("\u25CF")} ${chalk5.bold(skill.name)}`);
401
493
  if (skill.description) {
402
- console.log(` ${chalk4.dim(skill.description)}`);
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 chalk6 from "chalk";
502
+ import chalk7 from "chalk";
411
503
 
412
504
  // src/core/prompts.ts
413
- import chalk5 from "chalk";
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(chalk5.green(`Added Prompt #${config.prompts.length}`));
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
- chalk5.yellow(`Invalid index: ${index} (${config.prompts.length} total)`)
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
- chalk5.green(`Removed Prompt #${index}: "${removed.substring(0, 50)}..."`)
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(chalk6.red("Enter a valid numeric index"));
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(chalk6.dim(" No prompts yet"));
550
+ console.log(chalk7.dim(" No prompts yet"));
459
551
  return;
460
552
  }
461
- console.log(chalk6.blue("\n Prompts:\n"));
553
+ console.log(chalk7.blue("\n Prompts:\n"));
462
554
  prompts2.forEach((u, i) => {
463
- const marker = i === 0 ? chalk6.green("\u2605") : chalk6.dim("\u25CF");
464
- const label = i === 0 ? chalk6.dim(" (default)") : "";
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 fs5 from "fs";
565
+ import fs6 from "fs";
474
566
  var packageJson = JSON.parse(
475
- fs5.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
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(chalk7.red(`Packaging failed: ${err}`));
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.2",
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/FinpeakInc/skillpack.git"
8
+ "url": "git+https://github.com/CreminiAI/skillpack.git"
9
9
  },
10
10
  "bugs": {
11
- "url": "https://github.com/FinpeakInc/skillpack/issues"
11
+ "url": "https://github.com/CreminiAI/skillpack/issues"
12
12
  },
13
- "homepage": "https://github.com/FinpeakInc/skillpack#readme",
13
+ "homepage": "https://github.com/CreminiAI/skillpack#readme",
14
14
  "bin": {
15
15
  "skillpack": "dist/cli.js"
16
16
  },
@@ -3,8 +3,8 @@ cd "$(dirname "$0")"
3
3
 
4
4
  # Read the pack name
5
5
  PACK_NAME="Skills Pack"
6
- if [ -f "app.json" ] && command -v node &> /dev/null; then
7
- PACK_NAME=$(node -e "console.log(JSON.parse(require('fs').readFileSync('app.json','utf-8')).name)" 2>/dev/null || echo "Skills Pack")
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 ""
@@ -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") {
@@ -5,11 +5,11 @@ import { WebSocketServer } from "ws";
5
5
  import { handleWsConnection } from "./chat-proxy.js";
6
6
 
7
7
  /**
8
- * Read the app.json config.
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, "app.json"), "utf-8");
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 app.json and skills/
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