updose 0.1.0 → 0.2.0

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/dist/index.cjs +135 -15
  2. package/dist/index.js +141 -21
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -130,6 +130,71 @@ function createSpinner(message) {
130
130
  }
131
131
  };
132
132
  }
133
+ function createMultiSpinner(labels) {
134
+ const isTTY = process.stderr.isTTY ?? false;
135
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
136
+ const statuses = labels.map(() => "pending");
137
+ let frameIdx = 0;
138
+ let intervalId = null;
139
+ let started = false;
140
+ function truncate(text) {
141
+ const cols = process.stderr.columns ?? 80;
142
+ return text.length > cols ? `${text.slice(0, cols - 1)}\u2026` : text;
143
+ }
144
+ function render() {
145
+ if (!isTTY) return;
146
+ if (started) {
147
+ process.stderr.write(`\x1B[${labels.length}A`);
148
+ }
149
+ started = true;
150
+ const frame = frames[frameIdx++ % frames.length];
151
+ for (let i = 0; i < labels.length; i++) {
152
+ const status = statuses[i];
153
+ let icon;
154
+ if (status === "success") {
155
+ icon = import_chalk.default.green("\u2713");
156
+ } else if (status === "fail") {
157
+ icon = import_chalk.default.red("\u2717");
158
+ } else {
159
+ icon = import_chalk.default.cyan(frame);
160
+ }
161
+ process.stderr.write(`\x1B[K${icon} ${truncate(labels[i])}
162
+ `);
163
+ }
164
+ }
165
+ return {
166
+ start() {
167
+ if (isTTY) {
168
+ render();
169
+ intervalId = setInterval(render, SPINNER_INTERVAL_MS);
170
+ }
171
+ return this;
172
+ },
173
+ markSuccess(index) {
174
+ statuses[index] = "success";
175
+ if (!isTTY) {
176
+ process.stderr.write(`${import_chalk.default.green("\u2713")} ${labels[index]}
177
+ `);
178
+ }
179
+ },
180
+ markFail(index) {
181
+ statuses[index] = "fail";
182
+ if (!isTTY) {
183
+ process.stderr.write(`${import_chalk.default.red("\u2717")} ${labels[index]}
184
+ `);
185
+ }
186
+ },
187
+ stop() {
188
+ if (intervalId) {
189
+ clearInterval(intervalId);
190
+ intervalId = null;
191
+ }
192
+ if (isTTY) {
193
+ render();
194
+ }
195
+ }
196
+ };
197
+ }
133
198
 
134
199
  // src/core/targets.ts
135
200
  var import_node_path = require("path");
@@ -480,18 +545,37 @@ function runSkillInstall(command, cwd, agents) {
480
545
  const parts = command.split(/\s+/);
481
546
  const [exe, ...args] = parts;
482
547
  if (!exe) {
483
- throw new Error(`Invalid skill command: "${command}"`);
548
+ return Promise.reject(new Error(`Invalid skill command: "${command}"`));
484
549
  }
485
550
  if (agents.length > 0) {
486
551
  args.push("-a", ...agents);
487
552
  }
488
553
  args.push("--copy", "-y");
489
- validateCommand([exe, ...args]);
490
- (0, import_node_child_process.execSync)([exe, ...args].join(" "), {
491
- cwd,
492
- stdio: "inherit"
554
+ try {
555
+ validateCommand([exe, ...args]);
556
+ } catch (err) {
557
+ return Promise.reject(err);
558
+ }
559
+ return new Promise((resolve2, reject) => {
560
+ (0, import_node_child_process.exec)([exe, ...args].join(" "), { cwd }, (err) => {
561
+ if (err) {
562
+ reject(err);
563
+ } else {
564
+ resolve2();
565
+ }
566
+ });
493
567
  });
494
568
  }
569
+ function formatSkillLabel(command) {
570
+ const ghMatch = command.match(
571
+ /github\.com\/([^/\s]+\/[^/\s]+?)(?:\.git)?(?:\s|$)/
572
+ );
573
+ const skillMatch = command.match(/--skill\s+(\S+)/);
574
+ if (ghMatch && skillMatch) {
575
+ return `${ghMatch[1]} > ${skillMatch[1]}`;
576
+ }
577
+ return command.replace(/^npx\s+skills\s+add\s+/, "");
578
+ }
495
579
 
496
580
  // src/utils/path.ts
497
581
  var import_node_path3 = require("path");
@@ -510,6 +594,25 @@ function ensureWithinDir(root, destPath) {
510
594
  }
511
595
 
512
596
  // src/commands/add.ts
597
+ var SKILL_CONCURRENCY = 5;
598
+ async function settledPool(tasks, limit) {
599
+ const results = new Array(tasks.length);
600
+ let next = 0;
601
+ async function worker() {
602
+ while (next < tasks.length) {
603
+ const i = next++;
604
+ try {
605
+ results[i] = { status: "fulfilled", value: await tasks[i]() };
606
+ } catch (reason) {
607
+ results[i] = { status: "rejected", reason };
608
+ }
609
+ }
610
+ }
611
+ await Promise.all(
612
+ Array.from({ length: Math.min(limit, tasks.length) }, worker)
613
+ );
614
+ return results;
615
+ }
513
616
  async function addCommand(repo, options) {
514
617
  try {
515
618
  const cwd = process.cwd();
@@ -657,23 +760,40 @@ async function addCommand(repo, options) {
657
760
  console.log();
658
761
  info("Installing skills...\n");
659
762
  const agents = selectedTargets.map(getAgentName);
660
- for (const skill of skillsManifest.skills) {
661
- try {
662
- runSkillInstall(skill, cwd, agents);
663
- success(`Installed skill: ${skill}`);
763
+ const labels = skillsManifest.skills.map(formatSkillLabel);
764
+ const spinner = createMultiSpinner(labels).start();
765
+ const results = await settledPool(
766
+ skillsManifest.skills.map(
767
+ (skill, i) => () => runSkillInstall(skill, cwd, agents).then(
768
+ () => spinner.markSuccess(i),
769
+ (err) => {
770
+ spinner.markFail(i);
771
+ throw err;
772
+ }
773
+ )
774
+ ),
775
+ SKILL_CONCURRENCY
776
+ );
777
+ spinner.stop();
778
+ for (const result of results) {
779
+ if (result.status === "fulfilled") {
664
780
  skillsInstalled++;
665
- } catch (err) {
781
+ }
782
+ }
783
+ for (let i = 0; i < results.length; i++) {
784
+ const r = results[i];
785
+ if (r.status === "rejected") {
666
786
  warn(
667
- `Failed to install skill "${skill}": ${err instanceof Error ? err.message : String(err)}`
787
+ `Failed to install skill "${skillsManifest.skills[i]}": ${r.reason instanceof Error ? r.reason.message : String(r.reason)}`
668
788
  );
669
789
  }
670
790
  }
671
791
  }
672
792
  }
673
793
  console.log();
674
- const total = installed + skillsInstalled;
675
- success(`Done! ${total} file(s) installed, ${skipped} skipped.`);
676
- if (total > 0) {
794
+ const summary = skillsInstalled > 0 ? `${installed} file(s) + ${skillsInstalled} skill(s)` : `${installed} file(s)`;
795
+ success(`Done! ${summary} installed, ${skipped} skipped.`);
796
+ if (installed + skillsInstalled > 0) {
677
797
  await recordDownload(repo).catch(() => {
678
798
  });
679
799
  }
@@ -1307,7 +1427,7 @@ function formatResult(bp) {
1307
1427
 
1308
1428
  // src/index.ts
1309
1429
  var program = new import_commander.Command();
1310
- program.name("updose").description("AI coding tool boilerplate marketplace").version("0.1.0");
1430
+ program.name("updose").description("AI coding tool boilerplate marketplace").version("0.2.0");
1311
1431
  program.command("add <repo>").description("Install a boilerplate").option("-y, --yes", "Skip all prompts and use defaults").option("--dry-run", "Preview install without writing files").action(addCommand);
1312
1432
  program.command("search [query]").description("Search for boilerplates").option("--target <target>", "Filter by target (claude, codex, gemini)").option("--tag <tag>", "Filter by tag").option("--author <author>", "Filter by author").action(searchCommand);
1313
1433
  program.command("init").description("Scaffold a new boilerplate repository").action(initCommand);
package/dist/index.js CHANGED
@@ -108,6 +108,71 @@ function createSpinner(message) {
108
108
  }
109
109
  };
110
110
  }
111
+ function createMultiSpinner(labels) {
112
+ const isTTY = process.stderr.isTTY ?? false;
113
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
114
+ const statuses = labels.map(() => "pending");
115
+ let frameIdx = 0;
116
+ let intervalId = null;
117
+ let started = false;
118
+ function truncate(text) {
119
+ const cols = process.stderr.columns ?? 80;
120
+ return text.length > cols ? `${text.slice(0, cols - 1)}\u2026` : text;
121
+ }
122
+ function render() {
123
+ if (!isTTY) return;
124
+ if (started) {
125
+ process.stderr.write(`\x1B[${labels.length}A`);
126
+ }
127
+ started = true;
128
+ const frame = frames[frameIdx++ % frames.length];
129
+ for (let i = 0; i < labels.length; i++) {
130
+ const status = statuses[i];
131
+ let icon;
132
+ if (status === "success") {
133
+ icon = chalk.green("\u2713");
134
+ } else if (status === "fail") {
135
+ icon = chalk.red("\u2717");
136
+ } else {
137
+ icon = chalk.cyan(frame);
138
+ }
139
+ process.stderr.write(`\x1B[K${icon} ${truncate(labels[i])}
140
+ `);
141
+ }
142
+ }
143
+ return {
144
+ start() {
145
+ if (isTTY) {
146
+ render();
147
+ intervalId = setInterval(render, SPINNER_INTERVAL_MS);
148
+ }
149
+ return this;
150
+ },
151
+ markSuccess(index) {
152
+ statuses[index] = "success";
153
+ if (!isTTY) {
154
+ process.stderr.write(`${chalk.green("\u2713")} ${labels[index]}
155
+ `);
156
+ }
157
+ },
158
+ markFail(index) {
159
+ statuses[index] = "fail";
160
+ if (!isTTY) {
161
+ process.stderr.write(`${chalk.red("\u2717")} ${labels[index]}
162
+ `);
163
+ }
164
+ },
165
+ stop() {
166
+ if (intervalId) {
167
+ clearInterval(intervalId);
168
+ intervalId = null;
169
+ }
170
+ if (isTTY) {
171
+ render();
172
+ }
173
+ }
174
+ };
175
+ }
111
176
 
112
177
  // src/core/targets.ts
113
178
  import { join } from "path";
@@ -427,7 +492,7 @@ async function resolveConflict(filePath, isMainDoc2, skipPrompts) {
427
492
  }
428
493
 
429
494
  // src/core/skills.ts
430
- import { execSync } from "child_process";
495
+ import { exec } from "child_process";
431
496
  function parseSkills(raw) {
432
497
  if (typeof raw !== "object" || raw === null) {
433
498
  throw new Error("Invalid skills.json: expected an object");
@@ -458,18 +523,37 @@ function runSkillInstall(command, cwd, agents) {
458
523
  const parts = command.split(/\s+/);
459
524
  const [exe, ...args] = parts;
460
525
  if (!exe) {
461
- throw new Error(`Invalid skill command: "${command}"`);
526
+ return Promise.reject(new Error(`Invalid skill command: "${command}"`));
462
527
  }
463
528
  if (agents.length > 0) {
464
529
  args.push("-a", ...agents);
465
530
  }
466
531
  args.push("--copy", "-y");
467
- validateCommand([exe, ...args]);
468
- execSync([exe, ...args].join(" "), {
469
- cwd,
470
- stdio: "inherit"
532
+ try {
533
+ validateCommand([exe, ...args]);
534
+ } catch (err) {
535
+ return Promise.reject(err);
536
+ }
537
+ return new Promise((resolve2, reject) => {
538
+ exec([exe, ...args].join(" "), { cwd }, (err) => {
539
+ if (err) {
540
+ reject(err);
541
+ } else {
542
+ resolve2();
543
+ }
544
+ });
471
545
  });
472
546
  }
547
+ function formatSkillLabel(command) {
548
+ const ghMatch = command.match(
549
+ /github\.com\/([^/\s]+\/[^/\s]+?)(?:\.git)?(?:\s|$)/
550
+ );
551
+ const skillMatch = command.match(/--skill\s+(\S+)/);
552
+ if (ghMatch && skillMatch) {
553
+ return `${ghMatch[1]} > ${skillMatch[1]}`;
554
+ }
555
+ return command.replace(/^npx\s+skills\s+add\s+/, "");
556
+ }
473
557
 
474
558
  // src/utils/path.ts
475
559
  import { resolve, sep } from "path";
@@ -488,6 +572,25 @@ function ensureWithinDir(root, destPath) {
488
572
  }
489
573
 
490
574
  // src/commands/add.ts
575
+ var SKILL_CONCURRENCY = 5;
576
+ async function settledPool(tasks, limit) {
577
+ const results = new Array(tasks.length);
578
+ let next = 0;
579
+ async function worker() {
580
+ while (next < tasks.length) {
581
+ const i = next++;
582
+ try {
583
+ results[i] = { status: "fulfilled", value: await tasks[i]() };
584
+ } catch (reason) {
585
+ results[i] = { status: "rejected", reason };
586
+ }
587
+ }
588
+ }
589
+ await Promise.all(
590
+ Array.from({ length: Math.min(limit, tasks.length) }, worker)
591
+ );
592
+ return results;
593
+ }
491
594
  async function addCommand(repo, options) {
492
595
  try {
493
596
  const cwd = process.cwd();
@@ -635,23 +738,40 @@ async function addCommand(repo, options) {
635
738
  console.log();
636
739
  info("Installing skills...\n");
637
740
  const agents = selectedTargets.map(getAgentName);
638
- for (const skill of skillsManifest.skills) {
639
- try {
640
- runSkillInstall(skill, cwd, agents);
641
- success(`Installed skill: ${skill}`);
741
+ const labels = skillsManifest.skills.map(formatSkillLabel);
742
+ const spinner = createMultiSpinner(labels).start();
743
+ const results = await settledPool(
744
+ skillsManifest.skills.map(
745
+ (skill, i) => () => runSkillInstall(skill, cwd, agents).then(
746
+ () => spinner.markSuccess(i),
747
+ (err) => {
748
+ spinner.markFail(i);
749
+ throw err;
750
+ }
751
+ )
752
+ ),
753
+ SKILL_CONCURRENCY
754
+ );
755
+ spinner.stop();
756
+ for (const result of results) {
757
+ if (result.status === "fulfilled") {
642
758
  skillsInstalled++;
643
- } catch (err) {
759
+ }
760
+ }
761
+ for (let i = 0; i < results.length; i++) {
762
+ const r = results[i];
763
+ if (r.status === "rejected") {
644
764
  warn(
645
- `Failed to install skill "${skill}": ${err instanceof Error ? err.message : String(err)}`
765
+ `Failed to install skill "${skillsManifest.skills[i]}": ${r.reason instanceof Error ? r.reason.message : String(r.reason)}`
646
766
  );
647
767
  }
648
768
  }
649
769
  }
650
770
  }
651
771
  console.log();
652
- const total = installed + skillsInstalled;
653
- success(`Done! ${total} file(s) installed, ${skipped} skipped.`);
654
- if (total > 0) {
772
+ const summary = skillsInstalled > 0 ? `${installed} file(s) + ${skillsInstalled} skill(s)` : `${installed} file(s)`;
773
+ success(`Done! ${summary} installed, ${skipped} skipped.`);
774
+ if (installed + skillsInstalled > 0) {
655
775
  await recordDownload(repo).catch(() => {
656
776
  });
657
777
  }
@@ -664,7 +784,7 @@ async function addCommand(repo, options) {
664
784
  }
665
785
 
666
786
  // src/commands/init.ts
667
- import { execSync as execSync2 } from "child_process";
787
+ import { execSync } from "child_process";
668
788
  import { basename, join as join2 } from "path";
669
789
  import prompts2 from "prompts";
670
790
  var DEFAULT_VERSION = "0.1.0";
@@ -698,7 +818,7 @@ Add your Gemini instructions here.
698
818
  }
699
819
  function getGitHubUsername() {
700
820
  try {
701
- const ghUser = execSync2("git config github.user", {
821
+ const ghUser = execSync("git config github.user", {
702
822
  encoding: "utf-8",
703
823
  stdio: ["pipe", "pipe", "pipe"]
704
824
  }).trim();
@@ -706,7 +826,7 @@ function getGitHubUsername() {
706
826
  } catch {
707
827
  }
708
828
  try {
709
- const login2 = execSync2("gh api user --jq .login", {
829
+ const login2 = execSync("gh api user --jq .login", {
710
830
  encoding: "utf-8",
711
831
  stdio: ["pipe", "pipe", "pipe"],
712
832
  timeout: GH_CLI_TIMEOUT_MS
@@ -1075,7 +1195,7 @@ async function logoutCommand() {
1075
1195
  }
1076
1196
 
1077
1197
  // src/commands/publish.ts
1078
- import { execSync as execSync3 } from "child_process";
1198
+ import { execSync as execSync2 } from "child_process";
1079
1199
  import { readFile as readFile3 } from "fs/promises";
1080
1200
  import { join as join4 } from "path";
1081
1201
  import chalk3 from "chalk";
@@ -1209,7 +1329,7 @@ Make sure you have pushed your code to GitHub.`
1209
1329
  }
1210
1330
  function detectRepo(cwd) {
1211
1331
  try {
1212
- const remoteUrl = execSync3("git remote get-url origin", {
1332
+ const remoteUrl = execSync2("git remote get-url origin", {
1213
1333
  cwd,
1214
1334
  encoding: "utf-8",
1215
1335
  stdio: ["pipe", "pipe", "pipe"]
@@ -1285,7 +1405,7 @@ function formatResult(bp) {
1285
1405
 
1286
1406
  // src/index.ts
1287
1407
  var program = new Command();
1288
- program.name("updose").description("AI coding tool boilerplate marketplace").version("0.1.0");
1408
+ program.name("updose").description("AI coding tool boilerplate marketplace").version("0.2.0");
1289
1409
  program.command("add <repo>").description("Install a boilerplate").option("-y, --yes", "Skip all prompts and use defaults").option("--dry-run", "Preview install without writing files").action(addCommand);
1290
1410
  program.command("search [query]").description("Search for boilerplates").option("--target <target>", "Filter by target (claude, codex, gemini)").option("--tag <tag>", "Filter by tag").option("--author <author>", "Filter by author").action(searchCommand);
1291
1411
  program.command("init").description("Scaffold a new boilerplate repository").action(initCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "updose",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "AI coding tool boilerplate marketplace",
6
6
  "main": "dist/index.cjs",