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.
- package/bin/promptlings.js +1 -1
- package/lib/run.js +14 -2
- package/lib/tools.js +173 -74
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/bin/promptlings.js
CHANGED
|
@@ -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
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
return
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
const packages = ["Python.Python.3.12", "Git.Git", "GitHub.cli",
|
|
32
|
-
|
|
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
|
-
|
|
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, ["
|
|
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
|
-
|
|
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 (
|
|
94
|
-
if (
|
|
95
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
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,
|
|
211
|
-
export function installTools(options, temporary
|
|
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 =
|
|
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
|
-
|
|
240
|
-
if (options.playwright)
|
|
241
|
-
if (options.hyperframes)
|
|
242
|
-
if (options.demo)
|
|
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
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "promptlings",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
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.
|
|
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