@lgtm-hq/ai-skills 0.5.4 → 0.6.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/lib/gateway-commands.js +4 -3
- package/lib/install.js +142 -82
- package/lib/lockfile.js +3 -1
- package/lib/options.js +10 -6
- package/lib/package-version.js +22 -0
- package/lib/skills-runner.js +3 -3
- package/lib/ui.js +37 -0
- package/package.json +5 -2
package/lib/gateway-commands.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadVendors } from "./catalog.js";
|
|
2
|
+
import { getPackageVersion } from "./package-version.js";
|
|
2
3
|
import { pruneMissingLockEntries, readLockfile, writeLockfile } from "./lockfile.js";
|
|
3
4
|
import { resolveScope } from "./options.js";
|
|
4
5
|
import { buildSkillsArguments, buildSkillsRemoveArguments, runSkills } from "./skills-runner.js";
|
|
@@ -60,7 +61,7 @@ export async function updateSkills(options, dependencies = {}) {
|
|
|
60
61
|
);
|
|
61
62
|
await writeLock({
|
|
62
63
|
...prunedLock,
|
|
63
|
-
gatewayVersion:
|
|
64
|
+
gatewayVersion: getPackageVersion(),
|
|
64
65
|
skills,
|
|
65
66
|
});
|
|
66
67
|
return { pruned, updated };
|
|
@@ -147,7 +148,7 @@ function resolveSources(skills, vendors) {
|
|
|
147
148
|
for (const [name, entry] of Object.entries(skills)) {
|
|
148
149
|
const source =
|
|
149
150
|
entry.vendor === "lgtm-hq"
|
|
150
|
-
? `lgtm-hq/ai-skills@v${
|
|
151
|
+
? `lgtm-hq/ai-skills@v${getPackageVersion()}`
|
|
151
152
|
: resolveVendorSource(entry, vendors);
|
|
152
153
|
sources.set(source, [...(sources.get(source) ?? []), name]);
|
|
153
154
|
}
|
|
@@ -180,7 +181,7 @@ function resolveVendorSource(entry, vendors) {
|
|
|
180
181
|
*/
|
|
181
182
|
function sourceSha(vendor, currentSha, vendors) {
|
|
182
183
|
if (vendor === "lgtm-hq") {
|
|
183
|
-
return `v${
|
|
184
|
+
return `v${getPackageVersion()}`;
|
|
184
185
|
}
|
|
185
186
|
return vendors.find((candidate) => candidate.id === vendor)?.sha ?? currentSha;
|
|
186
187
|
}
|
package/lib/install.js
CHANGED
|
@@ -1,107 +1,167 @@
|
|
|
1
|
-
import { createInterface } from "node:readline/promises";
|
|
2
|
-
import { stdin, stdout } from "node:process";
|
|
3
|
-
|
|
4
1
|
import { loadBundles, loadVendorIndex, loadVendors } from "./catalog.js";
|
|
5
2
|
import { mergeLockEntries, readLockfile, writeLockfile } from "./lockfile.js";
|
|
6
3
|
import { resolveScope } from "./options.js";
|
|
4
|
+
import { getPackageVersion } from "./package-version.js";
|
|
7
5
|
import { buildSkillsArguments, runSkills } from "./skills-runner.js";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Prompt for a numbered item.
|
|
11
|
-
*
|
|
12
|
-
* @template T
|
|
13
|
-
* @param {string} label - Prompt heading.
|
|
14
|
-
* @param {T[]} items - Selectable values.
|
|
15
|
-
* @param {(item: T) => string} describe - Human-readable item formatter.
|
|
16
|
-
* @param {{question: (query: string) => Promise<string>}} prompt - Interactive prompt.
|
|
17
|
-
* @returns {Promise<T>} Selected value.
|
|
18
|
-
*/
|
|
19
|
-
async function choose(label, items, describe, prompt) {
|
|
20
|
-
stdout.write(`${label}\n`);
|
|
21
|
-
items.forEach((item, index) => stdout.write(` ${index + 1}. ${describe(item)}\n`));
|
|
22
|
-
const response = await prompt.question("> ");
|
|
23
|
-
const selection = Number.parseInt(response, 10) - 1;
|
|
24
|
-
if (!Number.isInteger(selection) || !items[selection]) {
|
|
25
|
-
throw new Error("Choose a listed number");
|
|
26
|
-
}
|
|
27
|
-
return items[selection];
|
|
28
|
-
}
|
|
6
|
+
import { createClackUi, KNOWN_AGENTS } from "./ui.js";
|
|
29
7
|
|
|
30
8
|
/**
|
|
31
9
|
* Fill unset install selections through the terminal picker.
|
|
32
10
|
*
|
|
11
|
+
* Happy path asks for catalog source (+ vendor skill) and agents. Scope defaults
|
|
12
|
+
* to global, installs symlink (not copy), and uses overwrite for the gateway
|
|
13
|
+
* fail-closed `--on-conflict` API without prompting — upstream `skills` does
|
|
14
|
+
* not implement conflict policies yet.
|
|
15
|
+
*
|
|
33
16
|
* @param {{agents: string[], bundle: string | null, copy: boolean, global: boolean, onConflict: string | null, project: boolean, skills: string[], vendor: string | null, yes: boolean}} options - Initial install options.
|
|
34
|
-
* @param {
|
|
17
|
+
* @param {ReturnType<typeof createClackUi>} [ui] - Injectable interactive UI.
|
|
35
18
|
* @returns {Promise<typeof options>} Fully selected options.
|
|
36
19
|
*/
|
|
37
|
-
export async function completeInteractively(options,
|
|
20
|
+
export async function completeInteractively(options, ui = createClackUi()) {
|
|
21
|
+
ui.intro("ai-skills gateway");
|
|
22
|
+
|
|
38
23
|
const bundles = await loadBundles();
|
|
39
24
|
const vendors = await loadVendors();
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
25
|
+
const sourceChoices = [
|
|
26
|
+
...Object.entries(bundles.groups).map(([id, bundle]) => ({
|
|
27
|
+
value: `bundle:${id}`,
|
|
28
|
+
label: `${bundle.name} — ${bundle.description}`,
|
|
29
|
+
})),
|
|
30
|
+
...vendors.vendors.map((vendor) => ({
|
|
31
|
+
value: `vendor:${vendor.id}`,
|
|
32
|
+
label: `${vendor.repo} @ ${vendor.sha.slice(0, 12)}`,
|
|
33
|
+
})),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const sourceValue = await cancelable(
|
|
37
|
+
ui,
|
|
38
|
+
ui.select({
|
|
39
|
+
message: "Install from which catalog?",
|
|
40
|
+
options: sourceChoices,
|
|
41
|
+
}),
|
|
56
42
|
);
|
|
43
|
+
const [sourceKind, sourceId] = splitSourceValue(sourceValue);
|
|
57
44
|
|
|
58
|
-
if (
|
|
59
|
-
const bundle = bundles.groups[
|
|
60
|
-
options.bundle =
|
|
45
|
+
if (sourceKind === "bundle") {
|
|
46
|
+
const bundle = bundles.groups[sourceId];
|
|
47
|
+
options.bundle = sourceId;
|
|
61
48
|
options.skills = bundle.skills;
|
|
62
49
|
} else {
|
|
63
|
-
const index = await loadVendorIndex(
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
50
|
+
const index = await loadVendorIndex(sourceId);
|
|
51
|
+
const skillName = await cancelable(
|
|
52
|
+
ui,
|
|
53
|
+
ui.select({
|
|
54
|
+
message: `Skill from ${index.vendor.repo}`,
|
|
55
|
+
options: index.skills.map((skill) => ({
|
|
56
|
+
value: skill.name,
|
|
57
|
+
label: skill.name,
|
|
58
|
+
})),
|
|
59
|
+
}),
|
|
69
60
|
);
|
|
70
|
-
options.vendor =
|
|
71
|
-
options.skills = [
|
|
61
|
+
options.vendor = sourceId;
|
|
62
|
+
options.skills = [skillName];
|
|
72
63
|
}
|
|
73
64
|
|
|
74
65
|
if (options.agents.length === 0) {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (!options.onConflict) {
|
|
90
|
-
options.onConflict = await choose(
|
|
91
|
-
"When an installed skill conflicts:",
|
|
92
|
-
["keep", "overwrite", "skip"],
|
|
93
|
-
(item) => item,
|
|
94
|
-
prompt,
|
|
66
|
+
const selected = await cancelable(
|
|
67
|
+
ui,
|
|
68
|
+
ui.multiselect({
|
|
69
|
+
message: "Install into which agents?",
|
|
70
|
+
options: [
|
|
71
|
+
...KNOWN_AGENTS,
|
|
72
|
+
{
|
|
73
|
+
value: "__detect__",
|
|
74
|
+
label: "Detect installed agents (may target many tools)",
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
initialValues: KNOWN_AGENTS.map((agent) => agent.value),
|
|
78
|
+
required: true,
|
|
79
|
+
}),
|
|
95
80
|
);
|
|
81
|
+
if (selected.length === 1 && selected[0] === "__detect__") {
|
|
82
|
+
options.agents = [];
|
|
83
|
+
ui.note(
|
|
84
|
+
"Leaving agents unset so the upstream skills CLI can detect targets.",
|
|
85
|
+
"Agent detection",
|
|
86
|
+
);
|
|
87
|
+
} else {
|
|
88
|
+
options.agents = selected.filter((agent) => agent !== "__detect__");
|
|
89
|
+
}
|
|
96
90
|
}
|
|
91
|
+
|
|
97
92
|
if (!options.global && !options.project) {
|
|
98
|
-
const scope = await
|
|
93
|
+
const scope = await cancelable(
|
|
94
|
+
ui,
|
|
95
|
+
ui.select({
|
|
96
|
+
message: "Install scope",
|
|
97
|
+
options: [
|
|
98
|
+
{ value: "global", label: "Global (user home — recommended)" },
|
|
99
|
+
{ value: "project", label: "Project (this repository)" },
|
|
100
|
+
],
|
|
101
|
+
initialValue: "global",
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
99
104
|
options.global = scope === "global";
|
|
100
105
|
options.project = scope === "project";
|
|
101
106
|
}
|
|
107
|
+
|
|
108
|
+
// Symlink is the default; only offer copy as an advanced opt-in.
|
|
109
|
+
if (!options.copy) {
|
|
110
|
+
const advanced = await cancelable(
|
|
111
|
+
ui,
|
|
112
|
+
ui.confirm({
|
|
113
|
+
message: "Show advanced options (copy files instead of symlink)?",
|
|
114
|
+
initialValue: false,
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
if (advanced) {
|
|
118
|
+
options.copy = await cancelable(
|
|
119
|
+
ui,
|
|
120
|
+
ui.confirm({
|
|
121
|
+
message: "Copy files into each agent instead of symlinking from ~/.agents/skills?",
|
|
122
|
+
initialValue: false,
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!options.onConflict) {
|
|
129
|
+
// Gateway still requires an explicit policy for -y; interactive defaults to
|
|
130
|
+
// overwrite so we do not ask a jargon question for a no-op upstream flag.
|
|
131
|
+
options.onConflict = "overwrite";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
ui.outro("Starting install…");
|
|
102
135
|
return options;
|
|
103
136
|
}
|
|
104
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Split a `kind:id` catalog selection value.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} value - Combined select value.
|
|
142
|
+
* @returns {[string, string]} Kind and id.
|
|
143
|
+
*/
|
|
144
|
+
function splitSourceValue(value) {
|
|
145
|
+
const separator = value.indexOf(":");
|
|
146
|
+
return [value.slice(0, separator), value.slice(separator + 1)];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Abort when Clack reports cancel (Ctrl+C / escape).
|
|
151
|
+
*
|
|
152
|
+
* @template T
|
|
153
|
+
* @param {ReturnType<typeof createClackUi>} ui - UI adapter.
|
|
154
|
+
* @param {Promise<T>} valuePromise - Pending prompt result.
|
|
155
|
+
* @returns {Promise<Exclude<T, symbol>>} Resolved non-cancel value.
|
|
156
|
+
*/
|
|
157
|
+
async function cancelable(ui, valuePromise) {
|
|
158
|
+
const value = await valuePromise;
|
|
159
|
+
if (ui.isCancel(value)) {
|
|
160
|
+
throw new Error("Install cancelled");
|
|
161
|
+
}
|
|
162
|
+
return /** @type {Exclude<T, symbol>} */ (value);
|
|
163
|
+
}
|
|
164
|
+
|
|
105
165
|
/**
|
|
106
166
|
* Install a first-party bundle or SHA-pinned vendor skill.
|
|
107
167
|
*
|
|
@@ -112,6 +172,11 @@ export async function completeInteractively(options, prompt) {
|
|
|
112
172
|
* @returns {Promise<void>} Resolves when the skills CLI succeeds.
|
|
113
173
|
*/
|
|
114
174
|
export async function install(options, run = runSkills, now = () => new Date(), lockEnvironment) {
|
|
175
|
+
if (options.onConflict && options.onConflict !== "overwrite") {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`--on-conflict=${options.onConflict} is unsupported: upstream skills CLI has no conflict policy. Omit the flag, use overwrite, or remove the existing skill first.`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
115
180
|
let source;
|
|
116
181
|
let selectedOptions = options;
|
|
117
182
|
let vendor;
|
|
@@ -141,7 +206,7 @@ export async function install(options, run = runSkills, now = () => new Date(),
|
|
|
141
206
|
skills: bundle.skills,
|
|
142
207
|
};
|
|
143
208
|
}
|
|
144
|
-
const packageVersion =
|
|
209
|
+
const packageVersion = getPackageVersion();
|
|
145
210
|
source = `lgtm-hq/ai-skills@v${packageVersion}`;
|
|
146
211
|
}
|
|
147
212
|
const scope = resolveScope(selectedOptions);
|
|
@@ -184,7 +249,7 @@ async function createLockEntries(options, vendor, now) {
|
|
|
184
249
|
agents: options.agents,
|
|
185
250
|
installedAt,
|
|
186
251
|
repo: "lgtm-hq/ai-skills",
|
|
187
|
-
sha: `v${
|
|
252
|
+
sha: `v${getPackageVersion()}`,
|
|
188
253
|
skillPath: `skills/${name}/SKILL.md`,
|
|
189
254
|
vendor: "lgtm-hq",
|
|
190
255
|
},
|
|
@@ -221,10 +286,5 @@ async function createLockEntries(options, vendor, now) {
|
|
|
221
286
|
* @returns {Promise<void>} Resolves when installation completes.
|
|
222
287
|
*/
|
|
223
288
|
export async function installInteractively(options) {
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
await install(await completeInteractively(options, prompt));
|
|
227
|
-
} finally {
|
|
228
|
-
prompt.close();
|
|
229
|
-
}
|
|
289
|
+
await install(await completeInteractively(options));
|
|
230
290
|
}
|
package/lib/lockfile.js
CHANGED
|
@@ -2,6 +2,8 @@ import { access, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
|
|
5
|
+
import { getPackageVersion } from "./package-version.js";
|
|
6
|
+
|
|
5
7
|
const LOCKFILE_VERSION = 1;
|
|
6
8
|
|
|
7
9
|
const AGENT_SKILL_PATHS = {
|
|
@@ -156,7 +158,7 @@ export async function isSkillInstalled(name, entry, scope, environment = {}) {
|
|
|
156
158
|
*/
|
|
157
159
|
function createLockfile(scope) {
|
|
158
160
|
return {
|
|
159
|
-
gatewayVersion:
|
|
161
|
+
gatewayVersion: getPackageVersion(),
|
|
160
162
|
scope,
|
|
161
163
|
skills: {},
|
|
162
164
|
version: LOCKFILE_VERSION,
|
package/lib/options.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Minimum supported version of the upstream skills CLI.
|
|
2
|
+
* Minimum supported version of the upstream skills CLI (npm package `skills`).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Pin a real published 1.x floor so `bunx skills@^…` resolves. Upstream is on
|
|
5
|
+
* the 1.x line (there is no 0.16.0). Caret allows compatible 1.x patches.
|
|
6
6
|
*/
|
|
7
|
-
export const MINIMUM_SKILLS_VERSION = "
|
|
7
|
+
export const MINIMUM_SKILLS_VERSION = "1.5.0";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Parse wrapper command-line arguments.
|
|
@@ -113,8 +113,12 @@ export function validateUnattendedOptions(options) {
|
|
|
113
113
|
if (options.skills.length === 0 && !options.bundle) {
|
|
114
114
|
throw new Error("-y requires at least one --skill for a vendor");
|
|
115
115
|
}
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
// Upstream `skills` has no conflict policy. Accept omitted/`overwrite` only so
|
|
117
|
+
// keep|skip cannot silently no-op under a fail-closed API.
|
|
118
|
+
if (options.onConflict && options.onConflict !== "overwrite") {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`--on-conflict=${options.onConflict} is unsupported: upstream skills CLI has no conflict policy. Omit the flag, use overwrite, or remove the existing skill first.`,
|
|
121
|
+
);
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the gateway package version for install pins and lock metadata.
|
|
7
|
+
*
|
|
8
|
+
* `bunx` / direct `node bin/ai-skills.js` do not set `npm_package_version`, so
|
|
9
|
+
* fall back to the on-disk `package.json` shipped in the published tarball.
|
|
10
|
+
*
|
|
11
|
+
* @param {{env?: NodeJS.ProcessEnv, packageJsonPath?: string}} [options] - Injectable paths/env for tests.
|
|
12
|
+
* @returns {string} Semver package version.
|
|
13
|
+
*/
|
|
14
|
+
export function getPackageVersion(options = {}) {
|
|
15
|
+
const env = options.env ?? process.env;
|
|
16
|
+
if (env.npm_package_version) {
|
|
17
|
+
return env.npm_package_version;
|
|
18
|
+
}
|
|
19
|
+
const packageJsonPath =
|
|
20
|
+
options.packageJsonPath ?? join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
21
|
+
return JSON.parse(readFileSync(packageJsonPath, "utf8")).version;
|
|
22
|
+
}
|
package/lib/skills-runner.js
CHANGED
|
@@ -25,9 +25,9 @@ export function buildSkillsArguments(options, source) {
|
|
|
25
25
|
if (options.copy) {
|
|
26
26
|
args.push("--copy");
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
// Gateway still accepts --on-conflict for unattended fail-closed API stability,
|
|
29
|
+
// but upstream `skills` (through 1.5.x) does not implement that flag — do not
|
|
30
|
+
// forward a no-op that implies conflict handling the CLI does not provide.
|
|
31
31
|
if (options.yes) {
|
|
32
32
|
args.push("-y");
|
|
33
33
|
}
|
package/lib/ui.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as clack from "@clack/prompts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agents the gateway surfaces in interactive install.
|
|
5
|
+
*
|
|
6
|
+
* Keep labels user-facing; values match upstream `skills -a` identifiers.
|
|
7
|
+
*/
|
|
8
|
+
export const KNOWN_AGENTS = [
|
|
9
|
+
{ value: "claude-code", label: "Claude Code (~/.claude/skills)" },
|
|
10
|
+
{ value: "cursor", label: "Cursor (~/.cursor/skills)" },
|
|
11
|
+
{ value: "codex", label: "Codex (~/.codex/skills)" },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build the default interactive UI (Clack).
|
|
16
|
+
*
|
|
17
|
+
* @returns {{
|
|
18
|
+
* intro: (message: string) => void,
|
|
19
|
+
* outro: (message: string) => void,
|
|
20
|
+
* note: (message: string, title?: string) => void,
|
|
21
|
+
* select: (options: {message: string, options: {value: string, label: string}[], initialValue?: string}) => Promise<string | symbol>,
|
|
22
|
+
* multiselect: (options: {message: string, options: {value: string, label: string}[], initialValues?: string[], required?: boolean}) => Promise<string[] | symbol>,
|
|
23
|
+
* confirm: (options: {message: string, initialValue?: boolean}) => Promise<boolean | symbol>,
|
|
24
|
+
* isCancel: (value: unknown) => boolean,
|
|
25
|
+
* }} Interactive UI adapter.
|
|
26
|
+
*/
|
|
27
|
+
export function createClackUi() {
|
|
28
|
+
return {
|
|
29
|
+
intro: clack.intro,
|
|
30
|
+
outro: clack.outro,
|
|
31
|
+
note: clack.note,
|
|
32
|
+
select: clack.select,
|
|
33
|
+
multiselect: clack.multiselect,
|
|
34
|
+
confirm: clack.confirm,
|
|
35
|
+
isCancel: clack.isCancel,
|
|
36
|
+
};
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lgtm-hq/ai-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Pinned installer gateway for first-party and vendor AI skills.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/lgtm-hq/ai-skills#readme",
|
|
@@ -28,5 +28,8 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"test": "bun test --coverage"
|
|
30
30
|
},
|
|
31
|
-
"type": "module"
|
|
31
|
+
"type": "module",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@clack/prompts": "1.7.0"
|
|
34
|
+
}
|
|
32
35
|
}
|