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.
- package/dist/index.cjs +135 -15
- package/dist/index.js +141 -21
- 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
|
-
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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
|
-
}
|
|
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 "${
|
|
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
|
|
675
|
-
success(`Done! ${
|
|
676
|
-
if (
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
-
}
|
|
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 "${
|
|
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
|
|
653
|
-
success(`Done! ${
|
|
654
|
-
if (
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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.
|
|
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);
|