@devrev-computer/skills 1.0.0 → 2.0.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 (2) hide show
  1. package/bin/install.mjs +435 -93
  2. package/package.json +5 -1
package/bin/install.mjs CHANGED
@@ -1,158 +1,500 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { readdir, cp, mkdir, stat, readFile } from "node:fs/promises";
3
+ import { readdir, cp, mkdir, stat, readFile, rm } from "node:fs/promises";
4
4
  import { join, dirname } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { homedir } from "node:os";
7
+ import { existsSync } from "node:fs";
6
8
  import { parseArgs } from "node:util";
9
+ import * as p from "@clack/prompts";
10
+ import pc from "picocolors";
7
11
 
8
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
13
  const skillsSource = join(__dirname, "..", "skills");
10
14
 
15
+ // ── Skill catalog ──────────────────────────────────────────────
16
+
11
17
  const SKILLS = [
12
- "account-evaluation",
13
- "account-research",
14
- "create-workflow-template",
15
- "customer-brief",
16
- "deal-review-meddpicc",
17
- "next-step-for-opportunity",
18
- "opportunity-feature-prioritizer",
19
- "sales-call-plan-coach",
20
- "sales-context",
21
- "sales-search-and-lookup",
22
- "skill-creator",
23
- "trace-diagnosis",
18
+ { name: "account-evaluation", category: "sales", desc: "Account health report — pipeline, engagement, and external data" },
19
+ { name: "account-research", category: "sales", desc: "Company/person research — web, DevRev, and enrichment layers" },
20
+ { name: "create-workflow-template", category: "platform", desc: "Generate DevRev workflow template JSON from natural language" },
21
+ { name: "customer-brief", category: "sales", desc: "Pre-meeting briefing with internal and external context" },
22
+ { name: "deal-review-meddpicc", category: "sales", desc: "Full MEDDPICC deal assessment with evidence-tagged scoring" },
23
+ { name: "next-step-for-opportunity", category: "sales", desc: "Extract planned actions from meetings, emails, discussions" },
24
+ { name: "opportunity-feature-prioritizer", category: "product", desc: "Prioritize features based on open opportunity pipeline" },
25
+ { name: "sales-call-plan-coach", category: "sales", desc: "Interactive call plan coaching — Franklin Covey methodology" },
26
+ { name: "sales-context", category: "sales", desc: "Always-active skill for consistent sales terminology" },
27
+ { name: "sales-search-and-lookup", category: "sales", desc: "Query pipeline, forecast, accounts via natural language" },
28
+ { name: "skill-creator", category: "meta", desc: "Create, modify, evaluate, and benchmark skills" },
29
+ { name: "trace-diagnosis", category: "eng", desc: "Debug Computer agent traces and auto-create tickets" },
30
+ ];
31
+
32
+ const SKILL_NAMES = SKILLS.map((s) => s.name);
33
+
34
+ // ── Logo ───────────────────────────────────────────────────────
35
+
36
+ const GRAYS = [
37
+ "\x1b[38;5;251m",
38
+ "\x1b[38;5;248m",
39
+ "\x1b[38;5;245m",
40
+ "\x1b[38;5;242m",
41
+ "\x1b[38;5;239m",
42
+ ];
43
+ const RESET = "\x1b[0m";
44
+
45
+ const LOGO = [
46
+ " ██████╗ ██████╗ ███╗ ███╗██████╗ ██╗ ██╗████████╗███████╗██████╗ ",
47
+ "██╔════╝██╔═══██╗████╗ ████║██╔══██╗██║ ██║╚══██╔══╝██╔════╝██╔══██╗",
48
+ "██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║ ██║ █████╗ ██████╔╝",
49
+ "██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██║ ██║ ██╔══╝ ██╔══██╗",
50
+ "╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚██████╔╝ ██║ ███████╗██║ ██║",
24
51
  ];
25
52
 
53
+ function showLogo() {
54
+ console.log();
55
+ LOGO.forEach((line, i) => {
56
+ console.log(` ${GRAYS[i]}${line}${RESET}`);
57
+ });
58
+ console.log();
59
+ console.log(
60
+ ` ${pc.dim("DevRev Computer Skills")} ${pc.dim("v")}${pc.dim(getVersion())}`
61
+ );
62
+ console.log();
63
+ }
64
+
65
+ function getVersion() {
66
+ try {
67
+ const pkg = JSON.parse(
68
+ require("node:fs").readFileSync(join(__dirname, "..", "package.json"), "utf-8")
69
+ );
70
+ return pkg.version;
71
+ } catch {
72
+ return "1.0.0";
73
+ }
74
+ }
75
+
76
+ // ── Helpers ────────────────────────────────────────────────────
77
+
78
+ function shortenPath(fullPath) {
79
+ const home = homedir();
80
+ if (fullPath.startsWith(home)) {
81
+ return "~" + fullPath.slice(home.length);
82
+ }
83
+ return fullPath;
84
+ }
85
+
86
+ function getGlobalSkillsDir() {
87
+ return join(homedir(), ".claude", "skills");
88
+ }
89
+
90
+ function getProjectSkillsDir(cwd) {
91
+ return join(cwd, ".claude", "skills");
92
+ }
93
+
94
+ function hasProjectClaude(cwd) {
95
+ return existsSync(join(cwd, ".claude")) || existsSync(join(cwd, "CLAUDE.md"));
96
+ }
97
+
98
+ async function getInstalledSkills(dir) {
99
+ try {
100
+ const entries = await readdir(dir, { withFileTypes: true });
101
+ return entries
102
+ .filter((e) => e.isDirectory() && SKILL_NAMES.includes(e.name))
103
+ .map((e) => e.name);
104
+ } catch {
105
+ return [];
106
+ }
107
+ }
108
+
109
+ // ── Non-interactive (flags) ────────────────────────────────────
110
+
26
111
  function usage() {
27
112
  console.log(`
28
- Usage: computer-skills [options]
113
+ ${pc.bold("Usage:")} computer-skills ${pc.dim("[command] [options]")}
29
114
 
30
- Install DevRev Computer Skills into your project's .claude/skills/ directory.
115
+ ${pc.bold("Commands:")}
116
+ ${pc.cyan("add")} Install skills ${pc.dim("(default, interactive)")}
117
+ ${pc.cyan("remove")} Uninstall skills
118
+ ${pc.cyan("list")} List installed and available skills
31
119
 
32
- Options:
33
- --list, -l List available skills
34
- --skill, -s <name> Install specific skill(s) (repeatable)
35
- --dir, -d <path> Target project directory (default: cwd)
36
- --uninstall, -u Remove installed skills
37
- --help, -h Show this help
120
+ ${pc.bold("Options:")}
121
+ ${pc.dim("-s, --skill <name>")} Specific skill(s) ${pc.dim("(repeatable)")}
122
+ ${pc.dim("-g, --global")} Install to ~/.claude/skills/
123
+ ${pc.dim("-d, --dir <path>")} Target project directory
124
+ ${pc.dim("-y, --yes")} Skip prompts, install all
125
+ ${pc.dim("-h, --help")} Show this help
126
+ ${pc.dim("-v, --version")} Show version
127
+
128
+ ${pc.bold("Examples:")}
129
+ ${pc.dim("$")} npx @devrev-computer/skills
130
+ ${pc.dim("$")} npx @devrev-computer/skills add -g
131
+ ${pc.dim("$")} npx @devrev-computer/skills add -s skill-creator -s trace-diagnosis
132
+ ${pc.dim("$")} npx @devrev-computer/skills remove
133
+ ${pc.dim("$")} npx @devrev-computer/skills list
38
134
  `);
39
135
  }
40
136
 
41
- async function getSkillDescription(skillDir) {
42
- const candidates = [
43
- "SKILL.md",
44
- ...SKILLS.map((s) => `${s}.md`),
45
- ];
46
- for (const file of await readdir(skillDir)) {
47
- if (file.endsWith(".md") && (file === "SKILL.md" || file === join(skillDir).split("/").pop() + ".md")) {
48
- try {
49
- const content = await readFile(join(skillDir, file), "utf-8");
50
- const match = content.match(/description:\s*>?\s*\n?\s*(.+)/);
51
- if (match) return match[1].trim().slice(0, 80);
52
- } catch {}
137
+ // ── List command ───────────────────────────────────────────────
138
+
139
+ async function runList(cwd) {
140
+ const globalDir = getGlobalSkillsDir();
141
+ const projectDir = getProjectSkillsDir(cwd);
142
+ const globalInstalled = await getInstalledSkills(globalDir);
143
+ const projectInstalled = await getInstalledSkills(projectDir);
144
+
145
+ console.log();
146
+ console.log(pc.bold(" Available skills:\n"));
147
+
148
+ const categories = { sales: "Sales", platform: "Platform", product: "Product", meta: "Meta", eng: "Engineering" };
149
+
150
+ for (const [cat, label] of Object.entries(categories)) {
151
+ const skills = SKILLS.filter((s) => s.category === cat);
152
+ if (skills.length === 0) continue;
153
+
154
+ console.log(` ${pc.dim("──")} ${pc.bold(label)} ${pc.dim("─".repeat(50))}`);
155
+ for (const skill of skills) {
156
+ const inGlobal = globalInstalled.includes(skill.name);
157
+ const inProject = projectInstalled.includes(skill.name);
158
+ let badge = "";
159
+ if (inGlobal && inProject) badge = pc.green(" [global + project]");
160
+ else if (inGlobal) badge = pc.cyan(" [global]");
161
+ else if (inProject) badge = pc.blue(" [project]");
162
+
163
+ console.log(` ${pc.cyan(skill.name)}${badge}`);
164
+ console.log(` ${pc.dim(skill.desc)}`);
53
165
  }
166
+ console.log();
54
167
  }
55
- return "";
168
+
169
+ console.log(
170
+ ` ${pc.dim(`${SKILLS.length} available`)}${globalInstalled.length ? pc.dim(` · ${globalInstalled.length} global`) : ""}${projectInstalled.length ? pc.dim(` · ${projectInstalled.length} project`) : ""}`
171
+ );
172
+ console.log();
56
173
  }
57
174
 
58
- async function listSkills() {
59
- console.log("\nAvailable skills:\n");
60
- for (const skill of SKILLS) {
61
- const desc = await getSkillDescription(join(skillsSource, skill)).catch(() => "");
62
- console.log(` ${skill}${desc ? ` — ${desc}` : ""}`);
175
+ // ── Interactive add ────────────────────────────────────────────
176
+
177
+ async function runAdd(cwd, flags) {
178
+ showLogo();
179
+ p.intro(pc.inverse(" Install Skills "));
180
+
181
+ // ── Step 1: Scope ──
182
+
183
+ let targetDir;
184
+ const isInProject = hasProjectClaude(cwd);
185
+
186
+ if (flags.global) {
187
+ targetDir = homedir();
188
+ } else if (flags.dir) {
189
+ targetDir = flags.dir;
190
+ } else if (flags.yes) {
191
+ targetDir = isInProject ? cwd : homedir();
192
+ } else {
193
+ const scopeOptions = [];
194
+ if (isInProject) {
195
+ scopeOptions.push({
196
+ value: "project",
197
+ label: "Project",
198
+ hint: shortenPath(getProjectSkillsDir(cwd)),
199
+ });
200
+ }
201
+ scopeOptions.push({
202
+ value: "global",
203
+ label: "Global",
204
+ hint: shortenPath(getGlobalSkillsDir()),
205
+ });
206
+ if (isInProject) {
207
+ scopeOptions.push({
208
+ value: "both",
209
+ label: "Both",
210
+ hint: "Project + Global",
211
+ });
212
+ }
213
+
214
+ const scope = await p.select({
215
+ message: "Where do you want to install skills?",
216
+ options: scopeOptions,
217
+ });
218
+
219
+ if (p.isCancel(scope)) {
220
+ p.cancel("Installation cancelled.");
221
+ process.exit(0);
222
+ }
223
+
224
+ if (scope === "global") {
225
+ targetDir = homedir();
226
+ } else if (scope === "both") {
227
+ // Install to both — we'll handle this after skill selection
228
+ targetDir = "__both__";
229
+ } else {
230
+ targetDir = cwd;
231
+ }
63
232
  }
64
- console.log(`\n ${SKILLS.length} skills total\n`);
65
- }
66
233
 
67
- async function installSkills(targetDir, selectedSkills) {
68
- const skillsDir = join(targetDir, ".claude", "skills");
69
- await mkdir(skillsDir, { recursive: true });
234
+ // ── Step 2: Skill selection ──
70
235
 
71
- const toInstall = selectedSkills.length > 0 ? selectedSkills : SKILLS;
72
- const invalid = toInstall.filter((s) => !SKILLS.includes(s));
73
- if (invalid.length > 0) {
74
- console.error(`Unknown skill(s): ${invalid.join(", ")}`);
75
- console.error(`Run 'computer-skills --list' to see available skills.`);
76
- process.exit(1);
236
+ let selectedSkills;
237
+
238
+ if (flags.skill && flags.skill.length > 0) {
239
+ const invalid = flags.skill.filter((s) => !SKILL_NAMES.includes(s));
240
+ if (invalid.length > 0) {
241
+ p.cancel(`Unknown skill(s): ${invalid.join(", ")}`);
242
+ process.exit(1);
243
+ }
244
+ selectedSkills = flags.skill;
245
+ } else if (flags.yes) {
246
+ selectedSkills = SKILL_NAMES;
247
+ } else {
248
+ const alreadyInstalled = targetDir === "__both__"
249
+ ? await getInstalledSkills(getProjectSkillsDir(cwd))
250
+ : await getInstalledSkills(
251
+ targetDir === homedir()
252
+ ? getGlobalSkillsDir()
253
+ : getProjectSkillsDir(targetDir)
254
+ );
255
+
256
+ const selected = await p.multiselect({
257
+ message: "Which skills do you want to install?",
258
+ options: SKILLS.map((s) => ({
259
+ value: s.name,
260
+ label: s.name,
261
+ hint: alreadyInstalled.includes(s.name)
262
+ ? `${s.desc} ${pc.yellow("(installed, will update)")}`
263
+ : s.desc,
264
+ })),
265
+ required: true,
266
+ });
267
+
268
+ if (p.isCancel(selected)) {
269
+ p.cancel("Installation cancelled.");
270
+ process.exit(0);
271
+ }
272
+
273
+ selectedSkills = selected;
274
+ }
275
+
276
+ // ── Step 3: Confirmation ──
277
+
278
+ const targets =
279
+ targetDir === "__both__"
280
+ ? [getProjectSkillsDir(cwd), getGlobalSkillsDir()]
281
+ : [
282
+ targetDir === homedir()
283
+ ? getGlobalSkillsDir()
284
+ : getProjectSkillsDir(targetDir),
285
+ ];
286
+
287
+ if (!flags.yes) {
288
+ const summaryLines = [
289
+ `${pc.bold("Skills:")} ${selectedSkills.length === SKILL_NAMES.length ? "All (" + SKILL_NAMES.length + ")" : selectedSkills.join(", ")}`,
290
+ `${pc.bold("Target:")} ${targets.map(shortenPath).join(" + ")}`,
291
+ ];
292
+
293
+ p.note(summaryLines.join("\n"), "Installation Summary");
294
+
295
+ const confirmed = await p.confirm({
296
+ message: "Proceed with installation?",
297
+ });
298
+
299
+ if (p.isCancel(confirmed) || !confirmed) {
300
+ p.cancel("Installation cancelled.");
301
+ process.exit(0);
302
+ }
77
303
  }
78
304
 
79
- let installed = 0;
80
- let skipped = 0;
305
+ // ── Step 4: Install ──
81
306
 
82
- for (const skill of toInstall) {
83
- const src = join(skillsSource, skill);
84
- const dest = join(skillsDir, skill);
307
+ const s = p.spinner();
308
+ s.start(`Installing ${selectedSkills.length} skill(s)`);
85
309
 
86
- try {
87
- await stat(src);
88
- } catch {
89
- console.error(` skip ${skill} (not found in package)`);
90
- skipped++;
91
- continue;
310
+ let totalInstalled = 0;
311
+ for (const dest of targets) {
312
+ await mkdir(dest, { recursive: true });
313
+ for (const skill of selectedSkills) {
314
+ const src = join(skillsSource, skill);
315
+ try {
316
+ await stat(src);
317
+ await cp(src, join(dest, skill), { recursive: true, force: true });
318
+ totalInstalled++;
319
+ } catch {
320
+ // skip missing
321
+ }
92
322
  }
323
+ }
93
324
 
94
- await cp(src, dest, { recursive: true, force: true });
95
- console.log(` + ${skill}`);
96
- installed++;
325
+ s.stop(`Installed ${selectedSkills.length} skill(s)`);
326
+
327
+ // ── Step 5: Summary ──
328
+
329
+ const installed = selectedSkills;
330
+ console.log();
331
+ for (const skill of installed) {
332
+ const info = SKILLS.find((s) => s.name === skill);
333
+ console.log(` ${pc.green("+")} ${pc.bold(skill)} ${pc.dim("—")} ${pc.dim(info?.desc || "")}`);
97
334
  }
335
+ console.log();
98
336
 
99
- console.log(
100
- `\nInstalled ${installed} skill(s) into ${skillsDir}${skipped > 0 ? ` (${skipped} skipped)` : ""}`
337
+ for (const dest of targets) {
338
+ console.log(` ${pc.dim("Installed to")} ${pc.cyan(shortenPath(dest))}`);
339
+ }
340
+ console.log();
341
+
342
+ p.outro(
343
+ `${pc.green("Done!")} Skills are ready to use in Claude Code.`
101
344
  );
102
345
  }
103
346
 
104
- async function uninstallSkills(targetDir, selectedSkills) {
105
- const { rm } = await import("node:fs/promises");
106
- const skillsDir = join(targetDir, ".claude", "skills");
107
- const toRemove = selectedSkills.length > 0 ? selectedSkills : SKILLS;
347
+ // ── Interactive remove ─────────────────────────────────────────
348
+
349
+ async function runRemove(cwd, flags) {
350
+ showLogo();
351
+ p.intro(pc.inverse(" Remove Skills "));
352
+
353
+ // Find installed skills
354
+ const globalDir = getGlobalSkillsDir();
355
+ const projectDir = getProjectSkillsDir(cwd);
356
+ const globalInstalled = await getInstalledSkills(globalDir);
357
+ const projectInstalled = await getInstalledSkills(projectDir);
358
+
359
+ if (globalInstalled.length === 0 && projectInstalled.length === 0) {
360
+ p.log.warn("No skills are currently installed.");
361
+ p.outro("Nothing to remove.");
362
+ return;
363
+ }
364
+
365
+ // Build options with location badges
366
+ const allInstalled = new Map();
367
+ for (const s of projectInstalled) {
368
+ allInstalled.set(s, { project: true, global: globalInstalled.includes(s) });
369
+ }
370
+ for (const s of globalInstalled) {
371
+ if (!allInstalled.has(s)) {
372
+ allInstalled.set(s, { project: false, global: true });
373
+ }
374
+ }
375
+
376
+ let toRemove;
377
+ if (flags.skill && flags.skill.length > 0) {
378
+ toRemove = flags.skill;
379
+ } else if (flags.yes) {
380
+ toRemove = [...allInstalled.keys()];
381
+ } else {
382
+ const selected = await p.multiselect({
383
+ message: "Which skills do you want to remove?",
384
+ options: [...allInstalled.entries()].map(([name, loc]) => {
385
+ const where = [loc.project && "project", loc.global && "global"]
386
+ .filter(Boolean)
387
+ .join(" + ");
388
+ return {
389
+ value: name,
390
+ label: name,
391
+ hint: where,
392
+ };
393
+ }),
394
+ required: true,
395
+ });
396
+
397
+ if (p.isCancel(selected)) {
398
+ p.cancel("Removal cancelled.");
399
+ process.exit(0);
400
+ }
401
+ toRemove = selected;
402
+ }
403
+
404
+ const confirmed = flags.yes || await (async () => {
405
+ const c = await p.confirm({
406
+ message: `Remove ${toRemove.length} skill(s)?`,
407
+ });
408
+ return !p.isCancel(c) && c;
409
+ })();
410
+
411
+ if (!confirmed) {
412
+ p.cancel("Removal cancelled.");
413
+ process.exit(0);
414
+ }
415
+
416
+ const s = p.spinner();
417
+ s.start(`Removing ${toRemove.length} skill(s)`);
108
418
 
109
419
  let removed = 0;
110
420
  for (const skill of toRemove) {
111
- const dest = join(skillsDir, skill);
112
- try {
113
- await stat(dest);
114
- await rm(dest, { recursive: true, force: true });
115
- console.log(` - ${skill}`);
116
- removed++;
117
- } catch {
118
- // not installed, skip
421
+ for (const dir of [projectDir, globalDir]) {
422
+ const dest = join(dir, skill);
423
+ try {
424
+ await stat(dest);
425
+ await rm(dest, { recursive: true, force: true });
426
+ removed++;
427
+ } catch {
428
+ // not there
429
+ }
119
430
  }
120
431
  }
121
- console.log(`\nRemoved ${removed} skill(s) from ${skillsDir}`);
432
+
433
+ s.stop(`Removed ${toRemove.length} skill(s)`);
434
+
435
+ console.log();
436
+ for (const skill of toRemove) {
437
+ console.log(` ${pc.red("-")} ${skill}`);
438
+ }
439
+ console.log();
440
+
441
+ p.outro(pc.green("Done!"));
122
442
  }
123
443
 
444
+ // ── Main ───────────────────────────────────────────────────────
445
+
124
446
  async function main() {
125
- const { values } = parseArgs({
447
+ const { values, positionals } = parseArgs({
126
448
  options: {
127
- list: { type: "boolean", short: "l", default: false },
128
449
  skill: { type: "string", short: "s", multiple: true, default: [] },
129
- dir: { type: "string", short: "d", default: process.cwd() },
130
- uninstall: { type: "boolean", short: "u", default: false },
450
+ dir: { type: "string", short: "d" },
451
+ global: { type: "boolean", short: "g", default: false },
452
+ yes: { type: "boolean", short: "y", default: false },
131
453
  help: { type: "boolean", short: "h", default: false },
454
+ version: { type: "boolean", short: "v", default: false },
132
455
  },
456
+ allowPositionals: true,
133
457
  strict: true,
134
458
  });
135
459
 
136
- if (values.help) {
137
- usage();
460
+ if (values.version) {
461
+ console.log(getVersion());
138
462
  process.exit(0);
139
463
  }
140
464
 
141
- if (values.list) {
142
- await listSkills();
465
+ if (values.help) {
466
+ showLogo();
467
+ usage();
143
468
  process.exit(0);
144
469
  }
145
470
 
146
- console.log("\n@devrev/computer-skills\n");
471
+ const command = positionals[0] || "add";
472
+ const cwd = values.dir || process.cwd();
147
473
 
148
- if (values.uninstall) {
149
- await uninstallSkills(values.dir, values.skill);
150
- } else {
151
- await installSkills(values.dir, values.skill);
474
+ switch (command) {
475
+ case "add":
476
+ case "install":
477
+ case "i":
478
+ await runAdd(cwd, values);
479
+ break;
480
+ case "remove":
481
+ case "rm":
482
+ case "uninstall":
483
+ await runRemove(cwd, values);
484
+ break;
485
+ case "list":
486
+ case "ls":
487
+ showLogo();
488
+ await runList(cwd);
489
+ break;
490
+ default:
491
+ // No command = interactive add
492
+ await runAdd(cwd, values);
493
+ break;
152
494
  }
153
495
  }
154
496
 
155
497
  main().catch((err) => {
156
- console.error(err.message);
498
+ p.cancel(err.message);
157
499
  process.exit(1);
158
500
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devrev-computer/skills",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "AI-powered skills for Claude Code — sales intelligence, deal management, workflow automation, and agent debugging, built on DevRev.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,5 +29,9 @@
29
29
  "author": "DevRev",
30
30
  "engines": {
31
31
  "node": ">=18"
32
+ },
33
+ "dependencies": {
34
+ "@clack/prompts": "^1.2.0",
35
+ "picocolors": "^1.1.1"
32
36
  }
33
37
  }