pruff-skills 0.2.0 → 0.3.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/_chunks/libs/@clack/prompts.mjs +25 -3
- package/dist/cli.mjs +375 -27
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { __toESM } from "../../rolldown-runtime.mjs";
|
|
2
|
-
import { LD, SD, _D, dD, fD, require_picocolors, require_src } from "./core.mjs";
|
|
2
|
+
import { LD, RD, SD, _D, dD, fD, require_picocolors, require_src } from "./core.mjs";
|
|
3
3
|
import { stripVTControlCharacters } from "node:util";
|
|
4
4
|
import y from "node:process";
|
|
5
5
|
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
@@ -24,7 +24,29 @@ const V = ce(), u = (t, n) => V ? t : n, le = u("◆", "*"), L = u("■", "x"),
|
|
|
24
24
|
const j = v === 0 && $, E = v === f.length - 1 && g;
|
|
25
25
|
return j || E ? import_picocolors.default.dim("...") : i(p, v + l === n);
|
|
26
26
|
});
|
|
27
|
-
},
|
|
27
|
+
}, he = (t) => new RD({
|
|
28
|
+
validate: t.validate,
|
|
29
|
+
placeholder: t.placeholder,
|
|
30
|
+
defaultValue: t.defaultValue,
|
|
31
|
+
initialValue: t.initialValue,
|
|
32
|
+
render() {
|
|
33
|
+
const n = `${import_picocolors.default.gray(o)}
|
|
34
|
+
${b(this.state)} ${t.message}
|
|
35
|
+
`, r = t.placeholder ? import_picocolors.default.inverse(t.placeholder[0]) + import_picocolors.default.dim(t.placeholder.slice(1)) : import_picocolors.default.inverse(import_picocolors.default.hidden("_")), i = this.value ? this.valueWithCursor : r;
|
|
36
|
+
switch (this.state) {
|
|
37
|
+
case "error": return `${n.trim()}
|
|
38
|
+
${import_picocolors.default.yellow(o)} ${i}
|
|
39
|
+
${import_picocolors.default.yellow(d)} ${import_picocolors.default.yellow(this.error)}
|
|
40
|
+
`;
|
|
41
|
+
case "submit": return `${n}${import_picocolors.default.gray(o)} ${import_picocolors.default.dim(this.value || t.placeholder)}`;
|
|
42
|
+
case "cancel": return `${n}${import_picocolors.default.gray(o)} ${import_picocolors.default.strikethrough(import_picocolors.default.dim(this.value ?? ""))}${this.value?.trim() ? `
|
|
43
|
+
${import_picocolors.default.gray(o)}` : ""}`;
|
|
44
|
+
default: return `${n}${import_picocolors.default.cyan(o)} ${i}
|
|
45
|
+
${import_picocolors.default.cyan(d)}
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}).prompt(), ye = (t) => {
|
|
28
50
|
const n = t.active ?? "Yes", r = t.inactive ?? "No";
|
|
29
51
|
return new dD({
|
|
30
52
|
active: n,
|
|
@@ -329,4 +351,4 @@ ${J}${i.trimStart()}`), r = 3 + stripVTControlCharacters(i.trimStart()).length);
|
|
|
329
351
|
}
|
|
330
352
|
};
|
|
331
353
|
};
|
|
332
|
-
export { Ie, M, Me, Se, Y, be, fe, ve, xe, ye };
|
|
354
|
+
export { Ie, M, Me, Se, Y, be, fe, he, ve, xe, ye };
|
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { __toESM } from "./_chunks/rolldown-runtime.mjs";
|
|
3
3
|
import { pD, require_picocolors } from "./_chunks/libs/@clack/core.mjs";
|
|
4
|
-
import { Ie, M, Me, Se, Y, be, fe, ve, xe, ye } from "./_chunks/libs/@clack/prompts.mjs";
|
|
4
|
+
import { Ie, M, Me, Se, Y, be, fe, he, ve, xe, ye } from "./_chunks/libs/@clack/prompts.mjs";
|
|
5
5
|
import { esm_default } from "./_chunks/libs/simple-git.mjs";
|
|
6
6
|
import { xdgConfig } from "./_chunks/libs/xdg-basedir.mjs";
|
|
7
7
|
import { require_dist } from "./_chunks/libs/@vercel/detect-agent.mjs";
|
|
@@ -16,7 +16,7 @@ import { promisify } from "util";
|
|
|
16
16
|
import { execFile, execSync, spawn, spawnSync } from "child_process";
|
|
17
17
|
import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
|
|
18
18
|
import { parse } from "yaml";
|
|
19
|
-
import { createHash } from "crypto";
|
|
19
|
+
import { createHash, randomBytes } from "crypto";
|
|
20
20
|
import { createHash as createHash$1 } from "node:crypto";
|
|
21
21
|
import { gunzipSync, inflateRawSync } from "node:zlib";
|
|
22
22
|
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
@@ -555,7 +555,7 @@ function buildGitHubAuthError(url, repo, message) {
|
|
|
555
555
|
if (repo) return `Authentication failed for ${url}.\n - For private repos, ensure you have access\n - Retry with SSH: npx pruff-skills add ${repo.sshUrl}\n - Check access with: gh auth status -h github.com or ssh -T git@github.com`;
|
|
556
556
|
return `Authentication failed for ${url}.\n - For private repos, ensure you have access\n - For SSH: Check your keys with 'ssh -T git@github.com'\n - For HTTPS: Run 'gh auth login' or configure git credentials`;
|
|
557
557
|
}
|
|
558
|
-
async function cloneRepo(url, ref) {
|
|
558
|
+
async function cloneRepo$1(url, ref) {
|
|
559
559
|
const tempDir = await mkdtemp(join(tmpdir(), "skills-"));
|
|
560
560
|
const cloneOptions = ref ? [
|
|
561
561
|
"--depth",
|
|
@@ -3467,7 +3467,7 @@ async function runAdd(args, options = {}) {
|
|
|
3467
3467
|
spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
|
|
3468
3468
|
} else {
|
|
3469
3469
|
spinner.start("Cloning repository...");
|
|
3470
|
-
tempDir = await cloneRepo(parsed.url, parsed.ref);
|
|
3470
|
+
tempDir = await cloneRepo$1(parsed.url, parsed.ref);
|
|
3471
3471
|
spinner.stop("Repository cloned");
|
|
3472
3472
|
spinner.start("Discovering skills...");
|
|
3473
3473
|
skills = await discoverSkills(tempDir, parsed.subpath, {
|
|
@@ -3477,7 +3477,7 @@ async function runAdd(args, options = {}) {
|
|
|
3477
3477
|
}
|
|
3478
3478
|
} else {
|
|
3479
3479
|
spinner.start("Cloning repository...");
|
|
3480
|
-
tempDir = await cloneRepo(parsed.url, parsed.ref);
|
|
3480
|
+
tempDir = await cloneRepo$1(parsed.url, parsed.ref);
|
|
3481
3481
|
spinner.stop("Repository cloned");
|
|
3482
3482
|
spinner.start("Discovering skills...");
|
|
3483
3483
|
skills = await discoverSkills(tempDir, parsed.subpath, {
|
|
@@ -4020,7 +4020,7 @@ async function fetchAgentFile(source, name) {
|
|
|
4020
4020
|
return null;
|
|
4021
4021
|
}
|
|
4022
4022
|
try {
|
|
4023
|
-
const file = join(await cloneRepo(source), rel);
|
|
4023
|
+
const file = join(await cloneRepo$1(source), rel);
|
|
4024
4024
|
return existsSync(file) ? readFile(file, "utf8") : null;
|
|
4025
4025
|
} catch {
|
|
4026
4026
|
return null;
|
|
@@ -4134,7 +4134,7 @@ async function fetchFromCatalog(source, rel) {
|
|
|
4134
4134
|
return null;
|
|
4135
4135
|
}
|
|
4136
4136
|
try {
|
|
4137
|
-
const file = join(await cloneRepo(source), rel);
|
|
4137
|
+
const file = join(await cloneRepo$1(source), rel);
|
|
4138
4138
|
return existsSync(file) ? readFile(file, "utf8") : null;
|
|
4139
4139
|
} catch {
|
|
4140
4140
|
return null;
|
|
@@ -5466,7 +5466,7 @@ async function updateGlobalSkills(options = {}) {
|
|
|
5466
5466
|
}
|
|
5467
5467
|
continue;
|
|
5468
5468
|
}
|
|
5469
|
-
tempDir = await cloneRepo(sourceUrl, firstEntry.ref);
|
|
5469
|
+
tempDir = await cloneRepo$1(sourceUrl, firstEntry.ref);
|
|
5470
5470
|
const discoveredPaths = (await discoverSkills(tempDir)).map((skill) => {
|
|
5471
5471
|
return join(relative(tempDir, skill.path), "SKILL.md").split(sep).join("/");
|
|
5472
5472
|
});
|
|
@@ -5622,7 +5622,7 @@ async function updateProjectSkills(options = {}) {
|
|
|
5622
5622
|
let tempDir = null;
|
|
5623
5623
|
let deletedSkills = [];
|
|
5624
5624
|
try {
|
|
5625
|
-
tempDir = await cloneRepo(sourceUrl, ref);
|
|
5625
|
+
tempDir = await cloneRepo$1(sourceUrl, ref);
|
|
5626
5626
|
const discoveredPaths = (await discoverSkills(tempDir)).map((s) => {
|
|
5627
5627
|
return join(relative(tempDir, s.path), "SKILL.md").split(sep).join("/");
|
|
5628
5628
|
});
|
|
@@ -5841,14 +5841,14 @@ async function runUse(sourceArgs, options = {}, parseErrors = []) {
|
|
|
5841
5841
|
});
|
|
5842
5842
|
if (blobResult) skills = blobResult.skills;
|
|
5843
5843
|
else {
|
|
5844
|
-
cloneTempDir = await cloneRepo(parsed.url, parsed.ref);
|
|
5844
|
+
cloneTempDir = await cloneRepo$1(parsed.url, parsed.ref);
|
|
5845
5845
|
skills = await discoverSkills(cloneTempDir, parsed.subpath, {
|
|
5846
5846
|
includeInternal,
|
|
5847
5847
|
fullDepth: options.fullDepth
|
|
5848
5848
|
});
|
|
5849
5849
|
}
|
|
5850
5850
|
} else {
|
|
5851
|
-
cloneTempDir = await cloneRepo(parsed.url, parsed.ref);
|
|
5851
|
+
cloneTempDir = await cloneRepo$1(parsed.url, parsed.ref);
|
|
5852
5852
|
skills = await discoverSkills(cloneTempDir, parsed.subpath, {
|
|
5853
5853
|
includeInternal,
|
|
5854
5854
|
fullDepth: options.fullDepth
|
|
@@ -6071,6 +6071,291 @@ function fail(message) {
|
|
|
6071
6071
|
process.exit(1);
|
|
6072
6072
|
}
|
|
6073
6073
|
var UseCommandError = class extends Error {};
|
|
6074
|
+
const RESET = "\x1B[0m";
|
|
6075
|
+
const DIM = "\x1B[38;5;102m";
|
|
6076
|
+
const BLUES = [
|
|
6077
|
+
"\x1B[38;2;110;139;255m",
|
|
6078
|
+
"\x1B[38;2;91;118;249m",
|
|
6079
|
+
"\x1B[38;2;72;97;242m",
|
|
6080
|
+
"\x1B[38;2;53;77;236m",
|
|
6081
|
+
"\x1B[38;2;34;56;229m",
|
|
6082
|
+
"\x1B[38;2;15;35;223m"
|
|
6083
|
+
];
|
|
6084
|
+
const SWEEP = "\x1B[38;2;214;226;255m";
|
|
6085
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6086
|
+
function canAnimate() {
|
|
6087
|
+
return !!process.stdout.isTTY && !process.env.CI && !process.env.NO_COLOR && !process.env.PRUFF_SKILLS_NO_ANIM;
|
|
6088
|
+
}
|
|
6089
|
+
function columnColor(col, width) {
|
|
6090
|
+
return BLUES[Math.min(BLUES.length - 1, Math.floor(col / Math.max(1, width - 1) * BLUES.length))];
|
|
6091
|
+
}
|
|
6092
|
+
function renderSweepLine(chars, indent, pos, band) {
|
|
6093
|
+
let out = indent;
|
|
6094
|
+
let mode = "";
|
|
6095
|
+
for (let c = 0; c < chars.length; c++) {
|
|
6096
|
+
const color = c >= pos && c < pos + band ? SWEEP : columnColor(c, chars.length);
|
|
6097
|
+
if (color !== mode) {
|
|
6098
|
+
out += color;
|
|
6099
|
+
mode = color;
|
|
6100
|
+
}
|
|
6101
|
+
out += chars[c];
|
|
6102
|
+
}
|
|
6103
|
+
return out + RESET;
|
|
6104
|
+
}
|
|
6105
|
+
async function sweepLine(text, { slow = false, indent = " " } = {}) {
|
|
6106
|
+
const chars = Array.from(text);
|
|
6107
|
+
const width = chars.length;
|
|
6108
|
+
const band = 5;
|
|
6109
|
+
const step = slow ? 2 : 3;
|
|
6110
|
+
const ms = slow ? 42 : 24;
|
|
6111
|
+
if (!canAnimate()) {
|
|
6112
|
+
console.log(renderSweepLine(chars, indent, -1, 0));
|
|
6113
|
+
return;
|
|
6114
|
+
}
|
|
6115
|
+
process.stdout.write("\x1B[?25l");
|
|
6116
|
+
try {
|
|
6117
|
+
let first = true;
|
|
6118
|
+
for (let pos = -5; pos <= width; pos += step) {
|
|
6119
|
+
if (!first) process.stdout.write("\x1B[1A");
|
|
6120
|
+
process.stdout.write("\r" + renderSweepLine(chars, indent, pos, band) + "\n");
|
|
6121
|
+
first = false;
|
|
6122
|
+
await sleep(ms);
|
|
6123
|
+
}
|
|
6124
|
+
process.stdout.write("\x1B[1A\r" + renderSweepLine(chars, indent, -1, 0) + "\n");
|
|
6125
|
+
} finally {
|
|
6126
|
+
process.stdout.write("\x1B[?25h");
|
|
6127
|
+
}
|
|
6128
|
+
}
|
|
6129
|
+
const CLAUDE_CODE_PKG = "@anthropic-ai/claude-code";
|
|
6130
|
+
const KNOWLEDGE_REPO = "https://github.com/Pruff-Tech/pruff-knowledge";
|
|
6131
|
+
const KNOWLEDGE_DIR = "Pruff Knowledge";
|
|
6132
|
+
const DEFAULT_WORKSPACE = join(homedir(), "pruff");
|
|
6133
|
+
const MAIN_REPOS = [{
|
|
6134
|
+
name: "Pruff-server",
|
|
6135
|
+
url: "https://github.com/Pruff-Tech/Pruff-server"
|
|
6136
|
+
}, {
|
|
6137
|
+
name: "Pruff-Client",
|
|
6138
|
+
url: "https://github.com/Pruff-Tech/Pruff-Client"
|
|
6139
|
+
}];
|
|
6140
|
+
const CORE_SKILLS = [
|
|
6141
|
+
"commit",
|
|
6142
|
+
"create-pr",
|
|
6143
|
+
"linear",
|
|
6144
|
+
"start-issue",
|
|
6145
|
+
"handoff",
|
|
6146
|
+
"pruff-logs",
|
|
6147
|
+
"pruff-pr-review",
|
|
6148
|
+
"review-pr-comments",
|
|
6149
|
+
"react-doctor"
|
|
6150
|
+
];
|
|
6151
|
+
const CORE_AGENTS = [
|
|
6152
|
+
"codebase-navigator",
|
|
6153
|
+
"db-query-reviewer",
|
|
6154
|
+
"pruff-logs-investigator"
|
|
6155
|
+
];
|
|
6156
|
+
async function runOnboarding(args) {
|
|
6157
|
+
if (process.platform !== "darwin") {
|
|
6158
|
+
M.error(`${import_picocolors.default.bold("onboarding")} currently supports macOS only (detected ${import_picocolors.default.cyan(process.platform)}).`);
|
|
6159
|
+
return;
|
|
6160
|
+
}
|
|
6161
|
+
Ie(import_picocolors.default.bold("Pruff onboarding"));
|
|
6162
|
+
installClaudeCode();
|
|
6163
|
+
ensureObsidian();
|
|
6164
|
+
const workspace = await resolveWorkspace(args);
|
|
6165
|
+
mkdirSync(workspace, { recursive: true });
|
|
6166
|
+
M.success(`Workspace ${import_picocolors.default.cyan(workspace)}`);
|
|
6167
|
+
cloneKnowledge(workspace);
|
|
6168
|
+
for (const repo of MAIN_REPOS) cloneRepo(repo.name, repo.url, workspace);
|
|
6169
|
+
await installCatalog(workspace);
|
|
6170
|
+
await showGuidance(workspace);
|
|
6171
|
+
Se(`${import_picocolors.default.green("Done.")} Welcome to Pruff — happy building.`);
|
|
6172
|
+
}
|
|
6173
|
+
function has(cmd) {
|
|
6174
|
+
return spawnSync("command", ["-v", cmd], {
|
|
6175
|
+
shell: true,
|
|
6176
|
+
stdio: "ignore"
|
|
6177
|
+
}).status === 0;
|
|
6178
|
+
}
|
|
6179
|
+
function run(cmd, args, cwd) {
|
|
6180
|
+
return spawnSync(cmd, args, {
|
|
6181
|
+
stdio: "inherit",
|
|
6182
|
+
cwd
|
|
6183
|
+
}).status === 0;
|
|
6184
|
+
}
|
|
6185
|
+
function expandHome(input) {
|
|
6186
|
+
return input.startsWith("~") ? join(homedir(), input.slice(1)) : input;
|
|
6187
|
+
}
|
|
6188
|
+
function installClaudeCode() {
|
|
6189
|
+
if (has("claude")) {
|
|
6190
|
+
M.success("Claude Code already installed");
|
|
6191
|
+
return;
|
|
6192
|
+
}
|
|
6193
|
+
const s = Y();
|
|
6194
|
+
s.start("Installing Claude Code…");
|
|
6195
|
+
if (run("npm", [
|
|
6196
|
+
"install",
|
|
6197
|
+
"-g",
|
|
6198
|
+
CLAUDE_CODE_PKG
|
|
6199
|
+
])) s.stop("Claude Code installed");
|
|
6200
|
+
else {
|
|
6201
|
+
s.stop(import_picocolors.default.yellow("Claude Code install failed"));
|
|
6202
|
+
M.warn(`Install it manually: ${import_picocolors.default.cyan(`npm i -g ${CLAUDE_CODE_PKG}`)}`);
|
|
6203
|
+
}
|
|
6204
|
+
}
|
|
6205
|
+
function ensureObsidian() {
|
|
6206
|
+
if (existsSync("/Applications/Obsidian.app")) {
|
|
6207
|
+
M.success("Obsidian already installed");
|
|
6208
|
+
return;
|
|
6209
|
+
}
|
|
6210
|
+
if (!has("brew")) {
|
|
6211
|
+
M.warn(`Obsidian not found and Homebrew is unavailable.\n Install it from ${import_picocolors.default.cyan("https://obsidian.md")} and re-run.`);
|
|
6212
|
+
return;
|
|
6213
|
+
}
|
|
6214
|
+
const s = Y();
|
|
6215
|
+
s.start("Installing Obsidian (brew)…");
|
|
6216
|
+
if (run("brew", [
|
|
6217
|
+
"install",
|
|
6218
|
+
"--cask",
|
|
6219
|
+
"obsidian"
|
|
6220
|
+
])) s.stop("Obsidian installed");
|
|
6221
|
+
else {
|
|
6222
|
+
s.stop(import_picocolors.default.yellow("Obsidian install failed"));
|
|
6223
|
+
M.warn(`Install manually: ${import_picocolors.default.cyan("brew install --cask obsidian")}`);
|
|
6224
|
+
}
|
|
6225
|
+
}
|
|
6226
|
+
async function resolveWorkspace(args) {
|
|
6227
|
+
const fromArg = args.find((a) => !a.startsWith("-"));
|
|
6228
|
+
if (fromArg) return expandHome(fromArg);
|
|
6229
|
+
if (!process.stdout.isTTY) return DEFAULT_WORKSPACE;
|
|
6230
|
+
const answer = await he({
|
|
6231
|
+
message: "Where should the Pruff workspace live?",
|
|
6232
|
+
placeholder: DEFAULT_WORKSPACE,
|
|
6233
|
+
defaultValue: DEFAULT_WORKSPACE
|
|
6234
|
+
});
|
|
6235
|
+
if (pD(answer)) {
|
|
6236
|
+
xe("Onboarding cancelled.");
|
|
6237
|
+
process.exit(0);
|
|
6238
|
+
}
|
|
6239
|
+
return expandHome((answer || DEFAULT_WORKSPACE).trim());
|
|
6240
|
+
}
|
|
6241
|
+
function cloneKnowledge(workspace) {
|
|
6242
|
+
const vaultPath = join(workspace, KNOWLEDGE_DIR);
|
|
6243
|
+
const s = Y();
|
|
6244
|
+
if (existsSync(join(vaultPath, ".git"))) {
|
|
6245
|
+
s.start("Updating Pruff knowledge…");
|
|
6246
|
+
const ok = run("git", [
|
|
6247
|
+
"-C",
|
|
6248
|
+
vaultPath,
|
|
6249
|
+
"pull",
|
|
6250
|
+
"--ff-only"
|
|
6251
|
+
]);
|
|
6252
|
+
s.stop(ok ? "Pruff knowledge up to date" : import_picocolors.default.yellow("git pull failed (keeping local copy)"));
|
|
6253
|
+
registerVault(vaultPath);
|
|
6254
|
+
return;
|
|
6255
|
+
}
|
|
6256
|
+
s.start("Cloning Pruff knowledge…");
|
|
6257
|
+
if (run("git", [
|
|
6258
|
+
"clone",
|
|
6259
|
+
KNOWLEDGE_REPO,
|
|
6260
|
+
vaultPath
|
|
6261
|
+
])) {
|
|
6262
|
+
s.stop(`Cloned knowledge to ${import_picocolors.default.dim(vaultPath)}`);
|
|
6263
|
+
registerVault(vaultPath);
|
|
6264
|
+
return;
|
|
6265
|
+
}
|
|
6266
|
+
s.stop(import_picocolors.default.yellow("Knowledge clone failed"));
|
|
6267
|
+
M.warn(`Could not clone ${import_picocolors.default.cyan(KNOWLEDGE_REPO)}.\n It's private — authenticate first with ${import_picocolors.default.cyan("gh auth login")} (or an SSH key), then re-run.`);
|
|
6268
|
+
}
|
|
6269
|
+
function cloneRepo(name, url, workspace) {
|
|
6270
|
+
const target = join(workspace, name);
|
|
6271
|
+
if (existsSync(join(target, ".git"))) {
|
|
6272
|
+
M.success(`${name} already cloned`);
|
|
6273
|
+
return;
|
|
6274
|
+
}
|
|
6275
|
+
const s = Y();
|
|
6276
|
+
s.start(`Cloning ${name}…`);
|
|
6277
|
+
if (run("git", [
|
|
6278
|
+
"clone",
|
|
6279
|
+
url,
|
|
6280
|
+
target
|
|
6281
|
+
], workspace)) s.stop(`Cloned ${import_picocolors.default.cyan(name)}`);
|
|
6282
|
+
else {
|
|
6283
|
+
s.stop(import_picocolors.default.yellow(`${name} clone failed`));
|
|
6284
|
+
M.warn(`Could not clone ${name}. Authenticate with ${import_picocolors.default.cyan("gh auth login")} then run:\n ${import_picocolors.default.dim(`git clone ${url}`)} (inside ${workspace})`);
|
|
6285
|
+
}
|
|
6286
|
+
}
|
|
6287
|
+
async function installCatalog(workspace) {
|
|
6288
|
+
const prevCwd = process.cwd();
|
|
6289
|
+
M.step("Installing Pruff rules, agents and skills into the workspace…");
|
|
6290
|
+
try {
|
|
6291
|
+
process.chdir(workspace);
|
|
6292
|
+
await runRules(["add", "--all"]);
|
|
6293
|
+
await runAgents(["add", ...CORE_AGENTS]);
|
|
6294
|
+
await runAdd([PRUFF_CATALOG], {
|
|
6295
|
+
skill: CORE_SKILLS,
|
|
6296
|
+
agent: ["claude-code"],
|
|
6297
|
+
yes: true
|
|
6298
|
+
});
|
|
6299
|
+
} catch (error) {
|
|
6300
|
+
M.warn(`Catalog install hit an error: ${error instanceof Error ? error.message : String(error)}\n Re-run once authenticated: ${import_picocolors.default.cyan("npx pruff-skills onboarding")}`);
|
|
6301
|
+
} finally {
|
|
6302
|
+
process.chdir(prevCwd);
|
|
6303
|
+
}
|
|
6304
|
+
}
|
|
6305
|
+
async function showGuidance(workspace) {
|
|
6306
|
+
console.log();
|
|
6307
|
+
await sweepLine("W O R K S P A C E R E A D Y", { slow: true });
|
|
6308
|
+
console.log();
|
|
6309
|
+
const line = (s) => console.log(` ${s}`);
|
|
6310
|
+
line(import_picocolors.default.bold("Recommended structure") + import_picocolors.default.dim(" (keep everything under one parent)"));
|
|
6311
|
+
line("");
|
|
6312
|
+
line(`${import_picocolors.default.cyan(workspace)}/`);
|
|
6313
|
+
const tree = [
|
|
6314
|
+
[".claude/", "rules · agents · skills (apply to every repo below)"],
|
|
6315
|
+
[`${KNOWLEDGE_DIR}/`, "Obsidian vault — open it in Obsidian"],
|
|
6316
|
+
["Pruff-server/", "backend"],
|
|
6317
|
+
["Pruff-Client/", "frontend"],
|
|
6318
|
+
["sls-*/", "serverless services — clone as you need them"]
|
|
6319
|
+
];
|
|
6320
|
+
const labelWidth = Math.max(...tree.map(([label]) => label.length)) + 2;
|
|
6321
|
+
tree.forEach(([label, desc], i) => {
|
|
6322
|
+
const connector = i === tree.length - 1 ? "└──" : "├──";
|
|
6323
|
+
line(`${import_picocolors.default.dim(connector)} ${label.padEnd(labelWidth)}${import_picocolors.default.dim(desc)}`);
|
|
6324
|
+
});
|
|
6325
|
+
line("");
|
|
6326
|
+
line(import_picocolors.default.bold("Next steps"));
|
|
6327
|
+
line(`${import_picocolors.default.dim("1.")} Open ${import_picocolors.default.bold("Obsidian")} → the ${import_picocolors.default.cyan(KNOWLEDGE_DIR)} vault is registered and ready.`);
|
|
6328
|
+
line(`${import_picocolors.default.dim("2.")} Start Claude Code from ${import_picocolors.default.cyan(workspace)} so the shared ${import_picocolors.default.dim(".claude/")} applies everywhere.`);
|
|
6329
|
+
line(`${import_picocolors.default.dim("3.")} Backend: ${import_picocolors.default.dim("cd Pruff-server && npm i && npm run local")}`);
|
|
6330
|
+
line(`${import_picocolors.default.dim("4.")} Frontend: ${import_picocolors.default.dim("cd Pruff-Client && yarn && yarn start-local")}`);
|
|
6331
|
+
line(`${import_picocolors.default.dim("5.")} Clone any serverless service into this folder with ${import_picocolors.default.dim("git clone <repo>")}.`);
|
|
6332
|
+
console.log();
|
|
6333
|
+
}
|
|
6334
|
+
function registerVault(vaultPath) {
|
|
6335
|
+
const dir = join(homedir(), "Library", "Application Support", "obsidian");
|
|
6336
|
+
const configPath = join(dir, "obsidian.json");
|
|
6337
|
+
let config = { vaults: {} };
|
|
6338
|
+
if (existsSync(configPath)) try {
|
|
6339
|
+
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
6340
|
+
if (!config.vaults) config.vaults = {};
|
|
6341
|
+
} catch {
|
|
6342
|
+
M.warn(`Could not parse ${import_picocolors.default.dim(configPath)} — leaving it untouched.`);
|
|
6343
|
+
return;
|
|
6344
|
+
}
|
|
6345
|
+
else mkdirSync(dir, { recursive: true });
|
|
6346
|
+
if (Object.values(config.vaults).some((v) => v.path === vaultPath)) {
|
|
6347
|
+
M.success("Vault already registered in Obsidian");
|
|
6348
|
+
return;
|
|
6349
|
+
}
|
|
6350
|
+
const id = randomBytes(8).toString("hex");
|
|
6351
|
+
config.vaults[id] = {
|
|
6352
|
+
path: vaultPath,
|
|
6353
|
+
ts: Date.now(),
|
|
6354
|
+
open: true
|
|
6355
|
+
};
|
|
6356
|
+
writeFileSync(configPath, JSON.stringify(config), "utf-8");
|
|
6357
|
+
M.success("Registered vault in Obsidian");
|
|
6358
|
+
}
|
|
6074
6359
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6075
6360
|
function getVersion() {
|
|
6076
6361
|
try {
|
|
@@ -6081,9 +6366,7 @@ function getVersion() {
|
|
|
6081
6366
|
}
|
|
6082
6367
|
}
|
|
6083
6368
|
const VERSION = getVersion();
|
|
6084
|
-
const RESET = "\x1B[0m";
|
|
6085
6369
|
const BOLD = "\x1B[1m";
|
|
6086
|
-
const DIM = "\x1B[38;5;102m";
|
|
6087
6370
|
const TEXT = "\x1B[38;5;145m";
|
|
6088
6371
|
const BLUE = "\x1B[38;2;110;139;255m";
|
|
6089
6372
|
const LOGO_LINES = [
|
|
@@ -6094,16 +6377,6 @@ const LOGO_LINES = [
|
|
|
6094
6377
|
"██║ ██║ ██║╚██████╔╝██║ ██║ ",
|
|
6095
6378
|
"╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ "
|
|
6096
6379
|
];
|
|
6097
|
-
const BLUES = [
|
|
6098
|
-
"\x1B[38;2;110;139;255m",
|
|
6099
|
-
"\x1B[38;2;91;118;249m",
|
|
6100
|
-
"\x1B[38;2;72;97;242m",
|
|
6101
|
-
"\x1B[38;2;53;77;236m",
|
|
6102
|
-
"\x1B[38;2;34;56;229m",
|
|
6103
|
-
"\x1B[38;2;15;35;223m"
|
|
6104
|
-
];
|
|
6105
|
-
const SWEEP = "\x1B[38;2;214;226;255m";
|
|
6106
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6107
6380
|
function printStaticLogo() {
|
|
6108
6381
|
LOGO_LINES.forEach((line, i) => {
|
|
6109
6382
|
console.log(`${BLUES[i]}${line}${RESET}`);
|
|
@@ -6139,7 +6412,7 @@ function renderLogoFrame(pos, band) {
|
|
|
6139
6412
|
return out + RESET;
|
|
6140
6413
|
}).join("\n");
|
|
6141
6414
|
}
|
|
6142
|
-
async function showLogo() {
|
|
6415
|
+
async function showLogo(slow = false) {
|
|
6143
6416
|
console.log();
|
|
6144
6417
|
if (!(!!process.stdout.isTTY && !process.env.CI && !process.env.NO_COLOR && !process.env.PRUFF_SKILLS_NO_ANIM)) {
|
|
6145
6418
|
printStaticLogo();
|
|
@@ -6148,6 +6421,9 @@ async function showLogo() {
|
|
|
6148
6421
|
const rows = LOGO_LINES.length;
|
|
6149
6422
|
const width = Math.max(...LOGO_LINES.map((l) => Array.from(l).length));
|
|
6150
6423
|
const band = 7;
|
|
6424
|
+
const revealMs = slow ? 190 : 90;
|
|
6425
|
+
const step = slow ? 3 : 4;
|
|
6426
|
+
const sweepMs = slow ? 34 : 18;
|
|
6151
6427
|
process.stdout.write("\x1B[?25l");
|
|
6152
6428
|
try {
|
|
6153
6429
|
let first = true;
|
|
@@ -6155,15 +6431,75 @@ async function showLogo() {
|
|
|
6155
6431
|
if (!first) process.stdout.write(`\x1b[${rows}A`);
|
|
6156
6432
|
process.stdout.write("\r" + renderLogoReveal(cut, width) + "\n");
|
|
6157
6433
|
first = false;
|
|
6158
|
-
await sleep(
|
|
6434
|
+
await sleep(revealMs);
|
|
6159
6435
|
}
|
|
6160
|
-
for (let pos = -7; pos <= width; pos +=
|
|
6436
|
+
for (let pos = -7; pos <= width; pos += step) {
|
|
6161
6437
|
process.stdout.write(`\x1b[${rows}A`);
|
|
6162
6438
|
process.stdout.write("\r" + renderLogoFrame(pos, band) + "\n");
|
|
6163
|
-
await sleep(
|
|
6439
|
+
await sleep(sweepMs);
|
|
6164
6440
|
}
|
|
6165
6441
|
process.stdout.write(`\x1b[${rows}A\r`);
|
|
6166
6442
|
process.stdout.write(LOGO_LINES.map((line, i) => `${BLUES[i]}${line}${RESET}`).join("\n") + "\n");
|
|
6443
|
+
if (slow) await sleep(500);
|
|
6444
|
+
} finally {
|
|
6445
|
+
process.stdout.write("\x1B[?25h");
|
|
6446
|
+
}
|
|
6447
|
+
}
|
|
6448
|
+
const TAGLINE_BLOCKS = [[
|
|
6449
|
+
"██████╗ ██╗ ██╗██╗██╗ ██████╗ ██╗███╗ ██╗ ██████╗ ",
|
|
6450
|
+
"██╔══██╗██║ ██║██║██║ ██╔══██╗██║████╗ ██║██╔════╝ ",
|
|
6451
|
+
"██████╔╝██║ ██║██║██║ ██║ ██║██║██╔██╗ ██║██║ ███╗",
|
|
6452
|
+
"██╔══██╗██║ ██║██║██║ ██║ ██║██║██║╚██╗██║██║ ██║",
|
|
6453
|
+
"██████╔╝╚██████╔╝██║███████╗██████╔╝██║██║ ╚████║╚██████╔╝",
|
|
6454
|
+
"╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ "
|
|
6455
|
+
], [
|
|
6456
|
+
"███████╗██╗███╗ ██╗████████╗███████╗ ██████╗██╗ ██╗███████╗",
|
|
6457
|
+
"██╔════╝██║████╗ ██║╚══██╔══╝██╔════╝██╔════╝██║ ██║██╔════╝",
|
|
6458
|
+
"█████╗ ██║██╔██╗ ██║ ██║ █████╗ ██║ ███████║███████╗",
|
|
6459
|
+
"██╔══╝ ██║██║╚██╗██║ ██║ ██╔══╝ ██║ ██╔══██║╚════██║",
|
|
6460
|
+
"██║ ██║██║ ╚████║ ██║ ███████╗╚██████╗██║ ██║███████║",
|
|
6461
|
+
"╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝"
|
|
6462
|
+
]];
|
|
6463
|
+
const TAGLINE_INDENT = " ";
|
|
6464
|
+
function renderTaglineRow(line, row, pos, band) {
|
|
6465
|
+
const chars = Array.from(line);
|
|
6466
|
+
let out = TAGLINE_INDENT;
|
|
6467
|
+
let mode = "";
|
|
6468
|
+
for (let c = 0; c < chars.length; c++) {
|
|
6469
|
+
const color = c >= pos && c < pos + band ? SWEEP : BLUES[row];
|
|
6470
|
+
if (color !== mode) {
|
|
6471
|
+
out += color;
|
|
6472
|
+
mode = color;
|
|
6473
|
+
}
|
|
6474
|
+
out += chars[c];
|
|
6475
|
+
}
|
|
6476
|
+
return out + RESET;
|
|
6477
|
+
}
|
|
6478
|
+
function renderTaglineFrame(pos, band) {
|
|
6479
|
+
return TAGLINE_BLOCKS.map((block) => block.map((line, row) => renderTaglineRow(line, row, pos, band)).join("\n")).join("\n");
|
|
6480
|
+
}
|
|
6481
|
+
async function showTagline(slow = false) {
|
|
6482
|
+
console.log();
|
|
6483
|
+
const rows = TAGLINE_BLOCKS.reduce((n, b) => n + b.length, 0);
|
|
6484
|
+
const width = Math.max(...TAGLINE_BLOCKS.flat().map((l) => Array.from(l).length));
|
|
6485
|
+
const band = 7;
|
|
6486
|
+
const step = slow ? 3 : 4;
|
|
6487
|
+
const sweepMs = slow ? 34 : 18;
|
|
6488
|
+
if (!(!!process.stdout.isTTY && !process.env.CI && !process.env.NO_COLOR && !process.env.PRUFF_SKILLS_NO_ANIM)) {
|
|
6489
|
+
console.log(renderTaglineFrame(-1, 0));
|
|
6490
|
+
return;
|
|
6491
|
+
}
|
|
6492
|
+
process.stdout.write("\x1B[?25l");
|
|
6493
|
+
try {
|
|
6494
|
+
let first = true;
|
|
6495
|
+
for (let pos = -7; pos <= width; pos += step) {
|
|
6496
|
+
if (!first) process.stdout.write(`\x1b[${rows}A`);
|
|
6497
|
+
process.stdout.write("\r" + renderTaglineFrame(pos, band) + "\n");
|
|
6498
|
+
first = false;
|
|
6499
|
+
await sleep(sweepMs);
|
|
6500
|
+
}
|
|
6501
|
+
process.stdout.write(`\x1b[${rows}A\r` + renderTaglineFrame(-1, 0) + "\n");
|
|
6502
|
+
if (slow) await sleep(500);
|
|
6167
6503
|
} finally {
|
|
6168
6504
|
process.stdout.write("\x1B[?25h");
|
|
6169
6505
|
}
|
|
@@ -6212,6 +6548,10 @@ ${BOLD}Rules:${RESET}
|
|
|
6212
6548
|
rules add --scope <client|server|common>
|
|
6213
6549
|
Install a whole repo's rule set (client/server also pull in common)
|
|
6214
6550
|
|
|
6551
|
+
${BOLD}Onboarding:${RESET}
|
|
6552
|
+
onboarding Set up a new machine (macOS): install Claude Code,
|
|
6553
|
+
install Obsidian, and clone the Pruff knowledge vault
|
|
6554
|
+
|
|
6215
6555
|
${BOLD}Updates:${RESET}
|
|
6216
6556
|
update [skills...] Update skills to latest versions (alias: upgrade)
|
|
6217
6557
|
|
|
@@ -6417,6 +6757,14 @@ async function main() {
|
|
|
6417
6757
|
if (!inAgent) await showLogo();
|
|
6418
6758
|
await runRules(restArgs);
|
|
6419
6759
|
break;
|
|
6760
|
+
case "onboarding":
|
|
6761
|
+
case "onboard":
|
|
6762
|
+
if (!inAgent) {
|
|
6763
|
+
await showLogo(true);
|
|
6764
|
+
await showTagline(true);
|
|
6765
|
+
}
|
|
6766
|
+
await runOnboarding(restArgs);
|
|
6767
|
+
break;
|
|
6420
6768
|
case "remove":
|
|
6421
6769
|
case "rm":
|
|
6422
6770
|
case "r":
|