promptlings 0.1.0-alpha.1 → 0.1.0-alpha.2

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.
@@ -59,7 +59,7 @@ async function main() {
59
59
  const runAddMcp = (args) => runPackage("add-mcp", args);
60
60
  const temporary = mkdtempSync(join(tmpdir(), "promptlings-"));
61
61
  try {
62
- if (steps.tools) (await import("../lib/tools.js")).installTools(options, temporary, runSkills);
62
+ if (steps.tools) await (await import("../lib/tools.js")).installTools(options, temporary);
63
63
  if (steps.publish) publish(options, temporary);
64
64
  if (steps.memory) await (await import("../lib/memory.js")).installMemory(options, { runSkills, runAddMcp });
65
65
  } finally {
package/lib/run.js CHANGED
@@ -1,4 +1,4 @@
1
- import { spawnSync } from "node:child_process";
1
+ import { spawn as spawnAsync, spawnSync } from "node:child_process";
2
2
  import { existsSync, lstatSync, readFileSync } from "node:fs";
3
3
  import { delimiter, dirname, extname, join } from "node:path";
4
4
  import { createRequire } from "node:module";
@@ -37,7 +37,7 @@ function spawn(command, args, options = {}) {
37
37
  cwd: options.cwd,
38
38
  env: options.env || process.env,
39
39
  encoding: "utf8",
40
- stdio: options.capture ? ["inherit", "pipe", "inherit"] : "inherit",
40
+ stdio: options.capture ? ["inherit", "pipe", options.silent ? "pipe" : "inherit"] : "inherit",
41
41
  maxBuffer: 64 * 1024 * 1024,
42
42
  };
43
43
  if (windows && /\.(cmd|bat)$/i.test(resolved)) {
@@ -56,6 +56,18 @@ export function run(command, args = [], options = {}) {
56
56
  return result;
57
57
  }
58
58
 
59
+ // Runs an executable concurrently with others, collecting stdout and stderr together so the caller can print them in order.
60
+ export function runBuffered(executable, args = [], options = {}) {
61
+ return new Promise((resolve) => {
62
+ let output = "";
63
+ const child = spawnAsync(executable, args, { cwd: options.cwd, env: options.env || process.env, stdio: ["ignore", "pipe", "pipe"] });
64
+ child.stdout.on("data", (chunk) => { output += chunk; });
65
+ child.stderr.on("data", (chunk) => { output += chunk; });
66
+ child.on("error", (error) => resolve({ status: null, output: `${output}${error.message}\n` }));
67
+ child.on("close", (status) => resolve({ status, output }));
68
+ });
69
+ }
70
+
59
71
  export const capture = (command, args = [], options = {}) =>
60
72
  run(command, args, { ...options, capture: true }).stdout.trim();
61
73
 
package/lib/tools.js CHANGED
@@ -1,42 +1,74 @@
1
- import { accessSync, appendFileSync, constants, existsSync, readFileSync } from "node:fs";
2
- import { homedir } from "node:os";
1
+ import { spawnSync } from "node:child_process";
2
+ import { accessSync, appendFileSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { homedir, tmpdir } from "node:os";
3
4
  import { dirname, join } from "node:path";
4
5
  import { pins } from "./pins.js";
5
- import { capture, fail, powershell, prependPath, run, succeeds, which, windows } from "./run.js";
6
+ import { capture, fail, packageBin, powershell, prependPath, run, runBuffered, succeeds, which, windows } from "./run.js";
6
7
 
7
8
  // WinGet reports an already-current package as "no applicable update". Node may surface it unsigned.
8
9
  const wingetCurrent = [0, -1978335189, 2316632107];
9
10
 
11
+ // First string value printed by `reg query`, with %VARIABLE% references expanded from the environment.
12
+ export function regString(output, env = process.env) {
13
+ const value = /^\s+.+?\s{4}REG_(?:EXPAND_)?SZ\s{4}(.*)$/m.exec(output ?? "")?.[1]?.trim();
14
+ return value ? value.replace(/%([^%]+)%/g, (whole, name) => env[name] ?? whole) : null;
15
+ }
16
+
17
+ const regQuery = (key, args) => {
18
+ const result = spawnSync("reg", ["query", key, ...args], { encoding: "utf8", windowsHide: true });
19
+ return result.status === 0 ? regString(result.stdout) : null;
20
+ };
21
+
10
22
  function refreshWindowsPath() {
11
- const persisted = powershell(
12
- '[Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Environment]::GetEnvironmentVariable("Path","User")',
13
- { capture: true },
14
- ).stdout.trim();
15
- prependPath(...persisted.split(";"));
23
+ const machine = regQuery("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", ["/v", "Path"]);
24
+ const user = regQuery("HKCU\\Environment", ["/v", "Path"]);
25
+ prependPath(...[machine, user].filter(Boolean).join(";").split(";"));
16
26
  }
17
27
 
18
28
  // Document rendering needs either Microsoft Word or LibreOffice.
19
29
  function windowsWordPath() {
20
- const script = `foreach ($hive in "HKLM:", "HKCU:") {
21
- $path = (Get-ItemProperty -LiteralPath "$hive\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Winword.exe" -ErrorAction SilentlyContinue).'(default)'
22
- if ($path -and (Test-Path -LiteralPath $path)) { $path; break }
23
- }`;
24
- return powershell(script, { capture: true }).stdout.trim() || null;
30
+ for (const hive of ["HKLM", "HKCU"]) {
31
+ const path = regQuery(`${hive}\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Winword.exe`, ["/ve"]);
32
+ if (path && existsSync(path)) return path;
33
+ }
34
+ return null;
25
35
  }
26
36
 
27
37
  const windowsOffice = () => join(process.env.ProgramFiles || "C:\\Program Files", "LibreOffice", "program");
28
38
 
29
- function windowsPackages(options) {
30
- if (!which("winget")) fail("Install Microsoft's App Installer (WinGet): https://aka.ms/getwinget . Then rerun.");
31
- const packages = ["Python.Python.3.12", "Git.Git", "GitHub.cli", "astral-sh.uv"];
32
- if (options.hyperframes || options.demo) packages.push("Gyan.FFmpeg");
39
+ // Selects the WinGet packages whose commands are not already usable. Probes are functions so unneeded ones never run.
40
+ export function windowsPackageIds(options, present) {
41
+ const packages = [["Python.Python.3.12", present.python], ["Git.Git", present.git], ["GitHub.cli", present.gh],
42
+ ["astral-sh.uv", present.uv]];
43
+ if (options.hyperframes || options.demo) packages.push(["Gyan.FFmpeg", present.ffmpeg]);
33
44
  if (options.demo) {
34
- packages.push("oschwartz10612.Poppler");
35
- if (!which("code")) packages.push("Microsoft.VisualStudioCode");
36
- if (!windowsWordPath() && !which("soffice.com") && !existsSync(join(windowsOffice(), "soffice.com"))) {
37
- packages.push("TheDocumentFoundation.LibreOffice");
38
- }
45
+ packages.push(["oschwartz10612.Poppler", present.poppler], ["Microsoft.VisualStudioCode", present.code],
46
+ ["TheDocumentFoundation.LibreOffice", present.office]);
39
47
  }
48
+ return packages.filter(([, found]) => !found()).map(([id]) => id);
49
+ }
50
+
51
+ // First word of each listed line: package names from `brew list --versions`.
52
+ export const listedNames = (output) => new Set(output.split(/\r?\n/).map((line) => line.trim().split(/\s+/)[0]).filter(Boolean));
53
+
54
+ // Packages that `dpkg-query -W -f '${Package} ${db:Status-Status}\n'` reports as installed.
55
+ export const dpkgInstalled = (output) => new Set(output.split(/\r?\n/).map((line) => line.trim().split(/\s+/))
56
+ .filter(([name, status]) => name && status === "installed").map(([name]) => name));
57
+
58
+ function windowsPackages(options) {
59
+ refreshWindowsPath();
60
+ const packages = windowsPackageIds(options, {
61
+ python: () => Boolean(findPython()),
62
+ git: () => which("git"),
63
+ gh: () => which("gh"),
64
+ uv: () => which("uv"),
65
+ ffmpeg: () => which("ffmpeg") && which("ffprobe"),
66
+ poppler: () => which("pdftoppm"),
67
+ code: () => which("code"),
68
+ office: () => windowsWordPath() || which("soffice.com") || existsSync(join(windowsOffice(), "soffice.com")),
69
+ });
70
+ if (!packages.length) { console.log("Native packages are already present."); return; }
71
+ if (!which("winget")) fail("Install Microsoft's App Installer (WinGet): https://aka.ms/getwinget . Then rerun.");
40
72
  for (const id of packages) {
41
73
  run("winget", ["install", "--id", id, "--exact", "--source", "winget", "--accept-package-agreements",
42
74
  "--accept-source-agreements", "--disable-interactivity"], { allow: wingetCurrent });
@@ -60,7 +92,11 @@ function macPackages(options, temporary) {
60
92
  const packages = ["python@3.12", "git", "gh", "uv"];
61
93
  if (options.hyperframes || options.demo) packages.push("ffmpeg");
62
94
  if (options.demo) packages.push("poppler");
63
- run(brew, ["install", ...packages]);
95
+ const listed = listedNames(run(brew, ["list", "--versions", ...packages],
96
+ { capture: true, silent: true, quiet: true, allow: [0, 1] }).stdout);
97
+ const missing = packages.filter((name) => !listed.has(name));
98
+ if (missing.length) run(brew, ["install", ...missing]);
99
+ else console.log("Homebrew packages are already present.");
64
100
  const casks = [];
65
101
  if (options.demo) {
66
102
  if (!which("code") && !existsSync("/Applications/Visual Studio Code.app")) casks.push("visual-studio-code");
@@ -78,24 +114,31 @@ function linuxPackages(options, temporary) {
78
114
  if (parseInt(release.VERSION_ID, 10) < (release.ID === "ubuntu" ? 24 : 12)) fail("Upgrade to Ubuntu 24.04+ or Debian 12+.");
79
115
  if (process.arch !== "x64") fail("The default Chrome-based Playwright recipe requires Linux x86_64.");
80
116
  if (process.getuid() === 0) fail("Run as your normal user. The installer uses sudo for system packages.");
81
- run("sudo", ["apt-get", "update"]);
82
117
  const packages = ["ca-certificates", "curl", "git", "gh", "python3", "python3-venv"];
83
118
  if (options.hyperframes || options.demo) packages.push("ffmpeg");
84
119
  if (options.demo) packages.push("poppler-utils");
85
120
  if (options.demo && !which("soffice")) packages.push("libreoffice-writer", "libreoffice-impress");
86
- run("sudo", ["apt-get", "install", "-y", ...packages]);
121
+ const chrome = "google-chrome-stable";
122
+ const installed = dpkgInstalled(run("dpkg-query", ["-W", "-f", "${Package} ${db:Status-Status}\\n", ...packages, chrome],
123
+ { capture: true, silent: true, quiet: true, allow: [0, 1] }).stdout);
124
+ const missing = packages.filter((name) => !installed.has(name));
125
+ const needCode = options.demo && !which("code");
126
+ const needChrome = (options.playwright || options.hyperframes) && !installed.has(chrome);
127
+ if (missing.length || needCode || needChrome) run("sudo", ["apt-get", "update"]);
128
+ if (missing.length) run("sudo", ["apt-get", "install", "-y", ...missing]);
129
+ else console.log("APT packages are already present.");
87
130
  // curl honors the environment's proxy settings.
88
131
  const deb = (url, name) => {
89
132
  const path = join(temporary, name);
90
133
  run("curl", ["-fsSL", url, "-o", path]);
91
134
  run("sudo", ["apt-get", "install", "-y", path]);
92
135
  };
93
- if (options.demo && !which("code")) deb("https://update.code.visualstudio.com/latest/linux-deb-x64/stable", "code.deb");
94
- if (options.playwright || options.hyperframes) {
95
- deb("https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb", "chrome.deb");
136
+ if (needCode) deb("https://update.code.visualstudio.com/latest/linux-deb-x64/stable", "code.deb");
137
+ if (needChrome) deb("https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb", "chrome.deb");
138
+ if (!which("uv") && !existsSync(join(homedir(), ".local", "bin", "uv"))) {
139
+ run("curl", ["-fsSL", "https://astral.sh/uv/install.sh", "-o", join(temporary, "uv.sh")]);
140
+ run("sh", [join(temporary, "uv.sh")], { env: { ...process.env, UV_NO_MODIFY_PATH: "1" } });
96
141
  }
97
- run("curl", ["-fsSL", "https://astral.sh/uv/install.sh", "-o", join(temporary, "uv.sh")]);
98
- run("sh", [join(temporary, "uv.sh")], { env: { ...process.env, UV_NO_MODIFY_PATH: "1" } });
99
142
  }
100
143
 
101
144
  export function systemPackages(options, temporary) {
@@ -120,8 +163,33 @@ export function uvToolPath() {
120
163
  prependPath(capture("uv", ["tool", "dir", "--bin"]));
121
164
  }
122
165
 
123
- function npmGlobal(packages) {
124
- if (!packages.length) return;
166
+ const splitSpec = (spec) => {
167
+ const at = spec.lastIndexOf("@");
168
+ return at > 0 ? [spec.slice(0, at), spec.slice(at + 1)] : [spec, ""];
169
+ };
170
+
171
+ function installedManifest(root, name) {
172
+ const manifest = join(root, name, "package.json");
173
+ if (!existsSync(manifest)) return null;
174
+ try { return JSON.parse(readFileSync(manifest, "utf8")); } catch { return null; }
175
+ }
176
+
177
+ // Pinned specs not installed at exactly their pinned version under the global root.
178
+ export function npmMissing(root, specs) {
179
+ return specs.filter((spec) => {
180
+ const [name, version] = splitSpec(spec);
181
+ const actual = installedManifest(root, name);
182
+ return !actual || actual.name !== name || (version && actual.version !== version);
183
+ });
184
+ }
185
+
186
+ let npmRoot;
187
+ const globalRoot = () => (npmRoot ??= capture("npm", ["root", "--global"], { quiet: true }).trim());
188
+
189
+ function npmGlobal(specs) {
190
+ if (!specs.length) return;
191
+ const packages = npmMissing(globalRoot(), specs);
192
+ if (!packages.length) { console.log("Global npm packages are already at their pinned versions."); return; }
125
193
  const prefix = capture("npm", ["prefix", "--global"], { quiet: true });
126
194
  const modules = windows ? prefix : join(prefix, "lib");
127
195
  let writable = true;
@@ -138,35 +206,76 @@ function npmGlobal(packages) {
138
206
  }
139
207
 
140
208
  export function skillsAdd(runSkills, sources) {
141
- for (const source of sources) {
209
+ for (const source of pendingSkillSources(sources)) {
142
210
  runSkills(["add", source, "--global", "--skill", "*", "--yes", "--copy"], { env: { ...process.env, DISABLE_TELEMETRY: "1" } });
143
211
  }
144
212
  }
145
213
 
146
- function playwrightSmoke(temporary) {
147
- run("playwright-cli", ["install-browser", "chrome"]);
148
- const session = `-s=promptlings-install-${process.pid}`;
149
- try {
150
- run("playwright-cli", [session, "open", "about:blank"], { cwd: temporary });
151
- const title = capture("playwright-cli",
152
- [session, "eval", "() => { document.title = 'promptlings'; return document.title; }", "--raw"], { cwd: temporary });
153
- if (title.replace(/^"|"$/g, "") !== "promptlings") fail(`Playwright CLI browser smoke check failed: ${title}`);
154
- } finally {
155
- try { run("playwright-cli", [session, "close"], { cwd: temporary }); } catch { /* keep the original failure */ }
214
+ export const skillLockPath = (env = process.env) =>
215
+ env.XDG_STATE_HOME ? join(env.XDG_STATE_HOME, "skills", ".skill-lock.json") : join(homedir(), ".agents", ".skill-lock.json");
216
+
217
+ const readJson = (path) => { try { return JSON.parse(readFileSync(path, "utf8")); } catch { return null; } };
218
+
219
+ // A pinned source counts as installed when the lock holds skills from the same repository, commit and path,
220
+ // and each of those skills is still present in the global skills directory.
221
+ export function skillSourceInstalled(lock, source, present) {
222
+ const match = /^https:\/\/github\.com\/([^/]+\/[^/]+)\/tree\/([^/]+)\/(.+?)\/?$/.exec(source);
223
+ if (!match) return false;
224
+ const [, repo, ref, path] = match;
225
+ const entries = Object.entries(lock?.skills ?? {}).filter(([, entry]) =>
226
+ entry.sourceType === "github" && entry.ref === ref && `${entry.skillPath ?? ""}`.startsWith(`${path}/`)
227
+ && (entry.source === repo || entry.sourceUrl === `https://github.com/${repo}.git`));
228
+ return entries.length > 0 && entries.every(([name]) => present(name));
229
+ }
230
+
231
+ export function pendingSkillSources(sources, lock = readJson(skillLockPath()),
232
+ present = (name) => existsSync(join(homedir(), ".agents", "skills", name, "SKILL.md"))) {
233
+ const pending = sources.filter((source) => !skillSourceInstalled(lock, source, present));
234
+ for (const source of sources) if (!pending.includes(source)) console.log(`= skills already installed: ${source}`);
235
+ return pending;
236
+ }
237
+
238
+ // Folds per-run skill lock entries into the shared lock, keeping each skill's original install time.
239
+ export function mergeSkillLocks(shared, runs) {
240
+ const merged = shared && typeof shared.version === "number" && shared.skills ? shared : null;
241
+ const result = merged ?? { version: runs.find(Boolean)?.version ?? 3, skills: {}, dismissed: {} };
242
+ for (const lock of runs) {
243
+ for (const [name, entry] of Object.entries(lock?.skills ?? {})) {
244
+ result.skills[name] = { ...entry, installedAt: result.skills[name]?.installedAt ?? entry.installedAt };
245
+ }
156
246
  }
247
+ return result;
157
248
  }
158
249
 
159
- function hyperframesCheck() {
160
- run("ffmpeg", ["-version"]);
161
- run("ffprobe", ["-version"]);
162
- run("hyperframes", ["browser", "ensure"]);
163
- const report = JSON.parse(capture("hyperframes", ["doctor", "--json"]));
164
- const failed = ["FFmpeg", "FFprobe", "Chrome"].filter((name) => !report.checks.some((check) => check.name === name && check.ok));
165
- if (failed.length) fail(`HyperFrames prerequisites failed: ${failed.join(", ")}`);
166
- run("hyperframes", ["--version"]);
250
+ // The skills CLI rewrites one global lock file per skill, so concurrent runs each get a private lock directory
251
+ // and the entries are merged afterwards. Installed skill directories are distinct per skill.
252
+ export async function skillsAddConcurrent(sources, runner = (args, options) => runBuffered(process.execPath, [packageBin("skills"), ...args], options)) {
253
+ if (!sources.length) return;
254
+ const states = mkdtempSync(join(tmpdir(), "promptlings-skills-"));
255
+ try {
256
+ console.log(`+ skills add (${sources.length} sources in parallel)`);
257
+ const results = await Promise.all(sources.map((source, index) => {
258
+ const state = join(states, String(index));
259
+ mkdirSync(state);
260
+ return runner(["add", source, "--global", "--skill", "*", "--yes", "--copy"],
261
+ { env: { ...process.env, DISABLE_TELEMETRY: "1", CI: "true", XDG_STATE_HOME: state } })
262
+ .then((result) => ({ ...result, source, lock: join(state, "skills", ".skill-lock.json") }));
263
+ }));
264
+ for (const { source, output } of results) console.log(`+ skills add ${source}\n${output.trimEnd()}`);
265
+ const lockPath = skillLockPath();
266
+ const runs = results.map(({ lock }) => readJson(lock));
267
+ if (runs.some((lock) => lock && Object.keys(lock.skills ?? {}).length)) {
268
+ mkdirSync(dirname(lockPath), { recursive: true });
269
+ writeFileSync(lockPath, JSON.stringify(mergeSkillLocks(readJson(lockPath), runs), null, 2), "utf8");
270
+ }
271
+ const failed = results.filter(({ status }) => status !== 0);
272
+ if (failed.length) fail(`External skill installation failed: ${failed.map(({ source, status }) => `${source} (${status})`).join(", ")}`);
273
+ } finally {
274
+ rmSync(states, { recursive: true, force: true });
275
+ }
167
276
  }
168
277
 
169
- function demoCheck() {
278
+ function demoSetup() {
170
279
  if (process.platform === "darwin") {
171
280
  const code = "/Applications/Visual Studio Code.app/Contents/Resources/app/bin";
172
281
  if (!which("code") && existsSync(join(code, "code"))) prependPath(code);
@@ -180,24 +289,16 @@ function demoCheck() {
180
289
  [Environment]::SetEnvironmentVariable("Path", ((@($path, '${windowsOffice().replace(/'/g, "''")}') | Where-Object { $_ }) -join ";"), "User")`);
181
290
  prependPath(windowsOffice());
182
291
  }
183
- run("code", ["--version"]);
184
- run("code", ["--install-extension", pins.demoTimeExtension]);
185
- const office = windows ? "soffice.com" : "soffice";
186
- if (which(office)) run(office, ["--version"]);
187
- else if (windows && windowsWordPath()) console.log("Document rendering: Microsoft Word.");
188
- else if (process.platform === "darwin" && existsSync("/Applications/Microsoft Word.app")) console.log("Document rendering: Microsoft Word.");
189
- else fail("Document rendering needs Microsoft Word or LibreOffice.");
190
- run("pdftoppm", ["-v"]);
191
- run("ffmpeg", ["-version"]);
192
- run("diff2html", ["--version"]);
292
+ const [extension, version] = pins.demoTimeExtension.split("@");
293
+ if (existsSync(join(homedir(), ".vscode", "extensions", `${extension.toLowerCase()}-${version}`, "package.json"))) {
294
+ console.log(`= VS Code extension already installed: ${pins.demoTimeExtension}`);
295
+ } else run("code", ["--install-extension", pins.demoTimeExtension]);
193
296
  }
194
297
 
195
298
  // Confirms each pinned experience package is installed at its exact version under the global root.
196
299
  export function verifyExperience(root, packages) {
197
300
  for (const spec of packages) {
198
- const at = spec.lastIndexOf("@");
199
- const name = at > 0 ? spec.slice(0, at) : spec;
200
- const version = at > 0 ? spec.slice(at + 1) : "";
301
+ const [name, version] = splitSpec(spec);
201
302
  const manifest = join(root, name, "package.json");
202
303
  if (!existsSync(manifest)) fail(`Experience toolkit package missing: ${name}`);
203
304
  const actual = JSON.parse(readFileSync(manifest, "utf8"));
@@ -207,14 +308,12 @@ export function verifyExperience(root, packages) {
207
308
  }
208
309
  }
209
310
 
210
- // System packages, global command-line tools, external skills and their smoke checks.
211
- export function installTools(options, temporary, runSkills) {
311
+ // System packages, global command-line tools, browsers and external skills.
312
+ export async function installTools(options, temporary) {
212
313
  if (options.systemPackages) systemPackages(options, temporary);
213
314
  prependPath(join(homedir(), ".local", "bin"));
214
315
  for (const command of ["uv", "git", "gh"]) if (!which(command)) fail(`Missing prerequisite: ${command}.`);
215
316
  if (!findPython()) fail("Python 3.11+ is required. Install it normally or rerun without --no-system-packages.");
216
- run("git", ["--version"]);
217
- run("gh", ["--version"]);
218
317
  npmGlobal([
219
318
  options.playwright && pins.playwright,
220
319
  options.hyperframes && pins.hyperframes,
@@ -222,7 +321,7 @@ export function installTools(options, temporary, runSkills) {
222
321
  ...(options.experience ? pins.experiencePackages : []),
223
322
  ].filter(Boolean));
224
323
  if (options.experience) {
225
- const root = capture("npm", ["root", "--global"], { quiet: true }).trim();
324
+ const root = globalRoot();
226
325
  verifyExperience(root, pins.experiencePackages);
227
326
  console.log(`Experience toolkit: ${root}`);
228
327
  }
@@ -236,8 +335,8 @@ export function installTools(options, temporary, runSkills) {
236
335
  ] : []),
237
336
  ...(options.experience ? [pins.frontendDesignSkill, pins.playwrightSkills, pins.hyperframesSkills] : []),
238
337
  ].filter(Boolean));
239
- if (skillSources.size) skillsAdd(runSkills, [...skillSources]);
240
- if (options.playwright) playwrightSmoke(temporary);
241
- if (options.hyperframes) hyperframesCheck();
242
- if (options.demo) demoCheck();
338
+ await skillsAddConcurrent(pendingSkillSources([...skillSources]));
339
+ if (options.playwright) run("playwright-cli", ["install-browser", "chrome"]);
340
+ if (options.hyperframes) run("hyperframes", ["browser", "ensure"]);
341
+ if (options.demo) demoSetup();
243
342
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "promptlings",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "promptlings",
9
- "version": "0.1.0-alpha.1",
9
+ "version": "0.1.0-alpha.2",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "add-mcp": "2.6.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptlings",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.2",
4
4
  "description": "Install the promptlings agents, skills, Basic Memory and supporting toolchain on any OS.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {