@shiwenbin1617/pstack 0.14.4
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/LICENSE +28 -0
- package/README.md +332 -0
- package/adapters/claude-code/overrides/setup-pstack/SKILL.md +48 -0
- package/adapters/claude-code.md +29 -0
- package/adapters/codex/agents/comment-sicko.toml +7 -0
- package/adapters/codex/agents/poteto-agent.toml +7 -0
- package/adapters/codex/overrides/setup-pstack/SKILL.md +40 -0
- package/adapters/codex.md +34 -0
- package/agents/comment-sicko.md +32 -0
- package/agents/poteto-agent.md +9 -0
- package/automations/benny/FOR_AGENTS.md +89 -0
- package/automations/benny/README.md +23 -0
- package/automations/benny/skills/reproduce-and-fix-issues/SKILL.md +310 -0
- package/automations/benny/skills/reproduce-and-fix-issues/references/control-adapter.md +169 -0
- package/automations/benny/skills/reproduce-and-fix-issues/references/feature-map.example.md +205 -0
- package/automations/benny/skills/reproduce-and-fix-issues/references/verify-existing-fix.md +93 -0
- package/automations/benny/skills/setup-benny/SKILL.md +266 -0
- package/automations/benny/skills/triage-issue-reports/SKILL.md +240 -0
- package/automations/benny/skills/triage-issue-reports/references/routing.example.md +61 -0
- package/automations/benny/templates/configuration.example.yaml +84 -0
- package/automations/benny/templates/reproduce-automation-prompt.md +33 -0
- package/automations/benny/templates/triage-automation-prompt.md +39 -0
- package/bin/pstack.mjs +331 -0
- package/package.json +48 -0
- package/scripts/build.mjs +218 -0
- package/scripts/host-adapters.mjs +128 -0
- package/scripts/lib.mjs +181 -0
- package/scripts/test.mjs +106 -0
- package/skills/architect/SKILL.md +83 -0
- package/skills/architect/references/design-red-flags.md +33 -0
- package/skills/architect/references/rationale-template.md +35 -0
- package/skills/architect/references/runner-prompt.md +20 -0
- package/skills/arena/SKILL.md +71 -0
- package/skills/automate-me/SKILL.md +109 -0
- package/skills/blast-radius/SKILL.md +50 -0
- package/skills/bro/SKILL.md +7 -0
- package/skills/create-verification-skill/SKILL.md +44 -0
- package/skills/create-verification-skill/references/feature-map-example/README.md +47 -0
- package/skills/create-verification-skill/references/feature-map-example/create-note.md +39 -0
- package/skills/create-verification-skill/references/feature-map-example/search.md +45 -0
- package/skills/figure-it-out/SKILL.md +55 -0
- package/skills/how/SKILL.md +134 -0
- package/skills/how/references/critic-prompt.md +59 -0
- package/skills/how/references/critique-rubric.md +58 -0
- package/skills/how/references/explainer-prompt.md +55 -0
- package/skills/how/references/explorer-prompt.md +52 -0
- package/skills/interrogate/SKILL.md +113 -0
- package/skills/interrogate/references/code-quality-review.md +47 -0
- package/skills/interrogate/references/lead-judgment.md +58 -0
- package/skills/interrogate/references/reviewer-prompt.md +72 -0
- package/skills/interrogate/references/rubric.md +77 -0
- package/skills/maintain-verification-skill/SKILL.md +39 -0
- package/skills/no-comments/SKILL.md +24 -0
- package/skills/poteto-mode/SKILL.md +140 -0
- package/skills/poteto-mode/playbooks/authoring-a-skill.md +12 -0
- package/skills/poteto-mode/playbooks/autonomous-run.md +13 -0
- package/skills/poteto-mode/playbooks/autopilot-full.md +13 -0
- package/skills/poteto-mode/playbooks/autopilot-stack.md +16 -0
- package/skills/poteto-mode/playbooks/babysit.md +27 -0
- package/skills/poteto-mode/playbooks/bug-fix.md +17 -0
- package/skills/poteto-mode/playbooks/eval.md +27 -0
- package/skills/poteto-mode/playbooks/feature.md +21 -0
- package/skills/poteto-mode/playbooks/hillclimb.md +21 -0
- package/skills/poteto-mode/playbooks/investigation.md +14 -0
- package/skills/poteto-mode/playbooks/multi-phase-plan.md +155 -0
- package/skills/poteto-mode/playbooks/opening-a-pr.md +29 -0
- package/skills/poteto-mode/playbooks/orchestrate.md +113 -0
- package/skills/poteto-mode/playbooks/pause-safely.md +10 -0
- package/skills/poteto-mode/playbooks/perf-issue.md +24 -0
- package/skills/poteto-mode/playbooks/prototype.md +14 -0
- package/skills/poteto-mode/playbooks/refactoring.md +16 -0
- package/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
- package/skills/poteto-mode/playbooks/session-pickup.md +13 -0
- package/skills/poteto-mode/playbooks/shipping.md +20 -0
- package/skills/poteto-mode/playbooks/trace-forensics.md +14 -0
- package/skills/poteto-mode/playbooks/visual-parity.md +11 -0
- package/skills/poteto-mode/playbooks/worktree-cleanup.md +14 -0
- package/skills/poteto-mode/references/bugbot-triage.md +142 -0
- package/skills/poteto-mode/scripts/bootstrap.ts +30 -0
- package/skills/poteto-mode/scripts/bun.lock +67 -0
- package/skills/poteto-mode/scripts/check-plan.mjs +186 -0
- package/skills/poteto-mode/scripts/orch/orch.test.ts +634 -0
- package/skills/poteto-mode/scripts/orch/orch.ts +578 -0
- package/skills/poteto-mode/scripts/orch/store.ts +1607 -0
- package/skills/poteto-mode/scripts/package.json +16 -0
- package/skills/poteto-mode/scripts/watch-pr/cli.test.ts +224 -0
- package/skills/poteto-mode/scripts/watch-pr/cli.ts +223 -0
- package/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +118 -0
- package/skills/poteto-mode/scripts/watch-pr/github.test.ts +306 -0
- package/skills/poteto-mode/scripts/watch-pr/github.ts +699 -0
- package/skills/poteto-mode/scripts/watch-pr/policy.test.ts +420 -0
- package/skills/poteto-mode/scripts/watch-pr/policy.ts +832 -0
- package/skills/poteto-mode/scripts/watch-pr/render.ts +169 -0
- package/skills/poteto-mode/scripts/watch-pr/tsconfig.json +13 -0
- package/skills/poteto-mode/scripts/watch-pr/types.compile.ts +93 -0
- package/skills/poteto-mode/scripts/watch-pr/types.ts +401 -0
- package/skills/poteto-mode/scripts/watch-pr/watch-pr +6 -0
- package/skills/poteto-mode/scripts/worktree-audit.sh +89 -0
- package/skills/principle-boundary-discipline/SKILL.md +34 -0
- package/skills/principle-build-the-lever/SKILL.md +23 -0
- package/skills/principle-encode-lessons-in-structure/SKILL.md +31 -0
- package/skills/principle-exhaust-the-design-space/SKILL.md +21 -0
- package/skills/principle-experience-first/SKILL.md +19 -0
- package/skills/principle-fix-root-causes/SKILL.md +23 -0
- package/skills/principle-foundational-thinking/SKILL.md +21 -0
- package/skills/principle-guard-the-context-window/SKILL.md +17 -0
- package/skills/principle-laziness-protocol/SKILL.md +18 -0
- package/skills/principle-make-operations-idempotent/SKILL.md +24 -0
- package/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +22 -0
- package/skills/principle-minimize-reader-load/SKILL.md +23 -0
- package/skills/principle-model-the-domain/SKILL.md +26 -0
- package/skills/principle-never-block-on-the-human/SKILL.md +23 -0
- package/skills/principle-outcome-oriented-execution/SKILL.md +22 -0
- package/skills/principle-prove-it-works/SKILL.md +33 -0
- package/skills/principle-redesign-from-first-principles/SKILL.md +16 -0
- package/skills/principle-separate-before-serializing-shared-state/SKILL.md +16 -0
- package/skills/principle-sequence-verifiable-units/SKILL.md +22 -0
- package/skills/principle-subtract-before-you-add/SKILL.md +22 -0
- package/skills/principle-type-system-discipline/SKILL.md +31 -0
- package/skills/recall/SKILL.md +35 -0
- package/skills/reflect/SKILL.md +77 -0
- package/skills/reflect/references/divergent-reviewer.md +43 -0
- package/skills/reflect/references/judgment-reviewer.md +42 -0
- package/skills/reflect/references/synthesizer.md +56 -0
- package/skills/reflect/references/tooling-reviewer.md +57 -0
- package/skills/setup-pstack/SKILL.md +106 -0
- package/skills/show-me-your-work/SKILL.md +82 -0
- package/skills/show-me-your-work/references/decision-log-template.tsv +1 -0
- package/skills/show-me-your-work/scripts/log.sh +40 -0
- package/skills/swarm/SKILL.md +46 -0
- package/skills/tdd/SKILL.md +44 -0
- package/skills/teach/SKILL.md +21 -0
- package/skills/technical-writing/SKILL.md +130 -0
- package/skills/typescript-best-practices/SKILL.md +28 -0
- package/skills/typescript-best-practices/references/patterns.md +292 -0
- package/skills/unslop/SKILL.md +80 -0
- package/skills/why/SKILL.md +229 -0
- package/skills/why/references/epistemics.md +144 -0
- package/skills/why/references/investigator-prompt.md +103 -0
- package/skills/why/references/source-playbook.md +17 -0
- package/skills/why/references/sources/code-archaeology.md +88 -0
- package/skills/why/references/sources/databricks.md +70 -0
- package/skills/why/references/sources/datadog.md +99 -0
- package/skills/why/references/sources/incident-postmortem.md +15 -0
- package/skills/why/references/sources/linear.md +48 -0
- package/skills/why/references/sources/notion.md +55 -0
- package/skills/why/references/sources/sentry.md +100 -0
- package/skills/why/references/sources/slack.md +54 -0
- package/skills/why/references/synthesizer-prompt.md +135 -0
package/bin/pstack.mjs
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface, emitKeypressEvents } from "node:readline";
|
|
3
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
REPO_ROOT, HOSTS, findSkills, installSkill, installAgents,
|
|
7
|
+
listInstalled, removeSkill, CORE_SKILLS, expandSkillDependencies,
|
|
8
|
+
} from "../scripts/lib.mjs";
|
|
9
|
+
|
|
10
|
+
const c = {
|
|
11
|
+
reset: "\x1b[0m", dim: "\x1b[2m", bold: "\x1b[1m",
|
|
12
|
+
cyan: "\x1b[36m", green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", grey: "\x1b[90m",
|
|
13
|
+
};
|
|
14
|
+
const supportsColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
15
|
+
const paint = (code, s) => (supportsColor ? code + s + c.reset : s);
|
|
16
|
+
|
|
17
|
+
const BANNER = `
|
|
18
|
+
██████╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
|
|
19
|
+
██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
|
|
20
|
+
██████╔╝███████╗ ██║ ███████║██║ █████╔╝
|
|
21
|
+
██╔═══╝ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
|
|
22
|
+
██║ ███████║ ██║ ██║ ██║╚██████╗██║ ██╗
|
|
23
|
+
╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const version = () => {
|
|
27
|
+
try { return JSON.parse(readFileSync(join(REPO_ROOT, "package.json"), "utf8")).version; }
|
|
28
|
+
catch { return "0.0.0"; }
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function help() {
|
|
32
|
+
console.log(paint(c.cyan, BANNER));
|
|
33
|
+
console.log(` ${paint(c.dim, "if you want to go fast, go deep first.")} ${paint(c.grey, "v" + version())}\n`);
|
|
34
|
+
console.log(` $ pstack add [skills...] ${paint(c.grey, "Install skills (interactive by default)")}`);
|
|
35
|
+
console.log(` $ pstack list ${paint(c.grey, "List installed pstack skills")}`);
|
|
36
|
+
console.log(` $ pstack remove [skills...] ${paint(c.grey, "Remove installed skills")}`);
|
|
37
|
+
console.log(` $ pstack update ${paint(c.grey, "Reinstall everything already installed")}`);
|
|
38
|
+
console.log(` $ pstack find [query] ${paint(c.grey, "Search the catalog")}`);
|
|
39
|
+
console.log(` $ pstack doctor ${paint(c.grey, "Show where skills are installed")}\n`);
|
|
40
|
+
console.log(` ${paint(c.bold, "Options")}`);
|
|
41
|
+
console.log(` --host <claude|codex|both> ${paint(c.grey, "Target agent (default: both, detected)")}`);
|
|
42
|
+
console.log(` --scope <user|project> ${paint(c.grey, "Install globally or into this repo (default: user)")}`);
|
|
43
|
+
console.log(` --copy ${paint(c.grey, "Install independent host-specific copies (default)")}`);
|
|
44
|
+
console.log(` --core ${paint(c.grey, "Core entry set plus required dependencies, no prompts")}`);
|
|
45
|
+
console.log(` --all ${paint(c.grey, "Every skill, no prompts")}`);
|
|
46
|
+
console.log(` -y, --yes ${paint(c.grey, "Skip confirmation")}`);
|
|
47
|
+
console.log(` --dry-run ${paint(c.grey, "Print what would happen")}\n`);
|
|
48
|
+
console.log(` ${paint(c.grey, "try: pstack add --core · npx @shiwenbin1617/pstack add --core")}\n`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseArgs(argv) {
|
|
52
|
+
const opts = { _: [], host: null, scope: "user", core: false, all: false, yes: false, dryRun: false };
|
|
53
|
+
for (let i = 0; i < argv.length; i++) {
|
|
54
|
+
const a = argv[i];
|
|
55
|
+
if (a === "--host") opts.host = argv[++i];
|
|
56
|
+
else if (a === "--scope") opts.scope = argv[++i];
|
|
57
|
+
else if (a === "--link") { console.error(paint(c.red, "--link was removed: Claude Code and Codex installs must stay independent.")); process.exit(1); }
|
|
58
|
+
else if (a === "--copy") {}
|
|
59
|
+
else if (a === "--core") opts.core = true;
|
|
60
|
+
else if (a === "--all") opts.all = true;
|
|
61
|
+
else if (a === "-y" || a === "--yes") opts.yes = true;
|
|
62
|
+
else if (a === "--dry-run") opts.dryRun = true;
|
|
63
|
+
else if (a === "-h" || a === "--help") opts.help = true;
|
|
64
|
+
else if (a === "-v" || a === "--version") opts.version = true;
|
|
65
|
+
else if (a.startsWith("-")) { console.error(paint(c.red, `unknown option: ${a}`)); process.exit(1); }
|
|
66
|
+
else opts._.push(a);
|
|
67
|
+
}
|
|
68
|
+
return opts;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Which hosts look present on this machine. */
|
|
72
|
+
function detectHosts() {
|
|
73
|
+
const found = [];
|
|
74
|
+
if (existsSync(join(process.env.HOME || "", ".claude"))) found.push("claude");
|
|
75
|
+
if (existsSync(join(process.env.HOME || "", ".codex")) || existsSync(join(process.env.HOME || "", ".agents"))) found.push("codex");
|
|
76
|
+
return found;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveHosts(opts) {
|
|
80
|
+
if (opts.host === "both") return ["claude", "codex"];
|
|
81
|
+
if (opts.host) {
|
|
82
|
+
if (!HOSTS[opts.host]) { console.error(paint(c.red, `unknown host: ${opts.host}`)); process.exit(1); }
|
|
83
|
+
return [opts.host];
|
|
84
|
+
}
|
|
85
|
+
const detected = detectHosts();
|
|
86
|
+
return detected.length ? detected : ["claude"];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const isTTY = () => process.stdin.isTTY && process.stdout.isTTY;
|
|
90
|
+
|
|
91
|
+
/** Arrow keys to move, space to toggle, a to toggle all, enter to confirm. */
|
|
92
|
+
function multiSelect(items, { preselected = new Set(), title }) {
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
let cursor = 0;
|
|
95
|
+
const chosen = new Set(preselected);
|
|
96
|
+
const pageSize = Math.min(items.length, Math.max(8, (process.stdout.rows || 24) - 8));
|
|
97
|
+
let top = 0;
|
|
98
|
+
emitKeypressEvents(process.stdin);
|
|
99
|
+
process.stdin.setRawMode(true);
|
|
100
|
+
process.stdin.resume();
|
|
101
|
+
|
|
102
|
+
let lastLines = 0;
|
|
103
|
+
const render = () => {
|
|
104
|
+
if (lastLines) process.stdout.write(`\x1b[${lastLines}A\x1b[0J`);
|
|
105
|
+
const lines = [];
|
|
106
|
+
lines.push(`${paint(c.bold, title)} ${paint(c.grey, "↑↓ move · space toggle · a all · enter confirm · esc cancel")}`);
|
|
107
|
+
if (cursor < top) top = cursor;
|
|
108
|
+
if (cursor >= top + pageSize) top = cursor - pageSize + 1;
|
|
109
|
+
for (let i = top; i < Math.min(items.length, top + pageSize); i++) {
|
|
110
|
+
const it = items[i];
|
|
111
|
+
const mark = chosen.has(it.value) ? paint(c.green, "◉") : paint(c.grey, "◯");
|
|
112
|
+
const pointer = i === cursor ? paint(c.cyan, "❯") : " ";
|
|
113
|
+
const name = i === cursor ? paint(c.bold, it.label) : it.label;
|
|
114
|
+
const hint = it.hint ? " " + paint(c.grey, it.hint) : "";
|
|
115
|
+
lines.push(`${pointer} ${mark} ${name}${hint}`);
|
|
116
|
+
}
|
|
117
|
+
lines.push(paint(c.grey, ` ${chosen.size} selected · showing ${Math.min(items.length, top + pageSize)}/${items.length}`));
|
|
118
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
119
|
+
lastLines = lines.length;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const done = (result) => {
|
|
123
|
+
process.stdin.setRawMode(false);
|
|
124
|
+
process.stdin.pause();
|
|
125
|
+
process.stdin.removeListener("keypress", onKey);
|
|
126
|
+
process.stdout.write("\n");
|
|
127
|
+
resolve(result);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const onKey = (_str, key) => {
|
|
131
|
+
if (key.name === "up" || key.name === "k") cursor = (cursor - 1 + items.length) % items.length;
|
|
132
|
+
else if (key.name === "down" || key.name === "j") cursor = (cursor + 1) % items.length;
|
|
133
|
+
else if (key.name === "space") {
|
|
134
|
+
const v = items[cursor].value;
|
|
135
|
+
chosen.has(v) ? chosen.delete(v) : chosen.add(v);
|
|
136
|
+
} else if (key.name === "a") {
|
|
137
|
+
if (chosen.size === items.length) chosen.clear();
|
|
138
|
+
else items.forEach((i) => chosen.add(i.value));
|
|
139
|
+
} else if (key.name === "return") return done([...chosen]);
|
|
140
|
+
else if (key.name === "escape" || (key.ctrl && key.name === "c")) return done(null);
|
|
141
|
+
render();
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
process.stdin.on("keypress", onKey);
|
|
145
|
+
render();
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function confirm(question) {
|
|
150
|
+
return new Promise((resolve) => {
|
|
151
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
152
|
+
rl.question(`${question} ${paint(c.grey, "(Y/n) ")}`, (a) => {
|
|
153
|
+
rl.close();
|
|
154
|
+
resolve(!a.trim() || /^y(es)?$/i.test(a.trim()));
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const truncate = (s, n) => (s.length <= n ? s : s.slice(0, n - 1) + "…");
|
|
160
|
+
|
|
161
|
+
/** Widest skill name in the catalog, so the description column never collides. */
|
|
162
|
+
const NAME_COL = Math.max(...findSkills().map((s) => s.name.length)) + 2;
|
|
163
|
+
|
|
164
|
+
async function cmdAdd(opts) {
|
|
165
|
+
const catalog = findSkills();
|
|
166
|
+
const hosts = resolveHosts(opts);
|
|
167
|
+
let names;
|
|
168
|
+
|
|
169
|
+
if (opts.all) names = catalog.map((s) => s.dirName);
|
|
170
|
+
else if (opts.core) names = CORE_SKILLS;
|
|
171
|
+
else if (opts._.length) names = opts._;
|
|
172
|
+
else if (isTTY()) {
|
|
173
|
+
const width = Math.max(20, (process.stdout.columns || 80) - NAME_COL - 10);
|
|
174
|
+
const items = catalog.map((s) => ({
|
|
175
|
+
value: s.dirName,
|
|
176
|
+
label: s.name.padEnd(NAME_COL),
|
|
177
|
+
hint: truncate(s.description.replace(/\s+/g, " "), width),
|
|
178
|
+
}));
|
|
179
|
+
const preselected = new Set(CORE_SKILLS.filter((n) => catalog.some((s) => s.dirName === n)));
|
|
180
|
+
console.log(paint(c.cyan, BANNER));
|
|
181
|
+
console.log(` installing to: ${paint(c.bold, hosts.map((h) => HOSTS[h].label).join(" + "))} ${paint(c.grey, "(" + opts.scope + " scope)")}\n`);
|
|
182
|
+
const picked = await multiSelect(items, { preselected, title: "Select skills to install" });
|
|
183
|
+
if (picked === null) { console.log(paint(c.grey, "cancelled.")); return; }
|
|
184
|
+
names = picked;
|
|
185
|
+
} else {
|
|
186
|
+
console.error(paint(c.red, "not a TTY. pass skill names, or --core / --all."));
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const unknown = names.filter((n) => !catalog.some((s) => s.dirName === n || s.name === n));
|
|
191
|
+
if (unknown.length) {
|
|
192
|
+
console.error(paint(c.red, `unknown skill(s): ${unknown.join(", ")}`));
|
|
193
|
+
console.error(paint(c.grey, "run `npx pstack find` to see the catalog."));
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
if (!names.length) { console.log(paint(c.grey, "nothing selected.")); return; }
|
|
197
|
+
|
|
198
|
+
const requested = catalog.filter((s) => names.includes(s.dirName) || names.includes(s.name));
|
|
199
|
+
const selected = expandSkillDependencies(requested, catalog);
|
|
200
|
+
const addedDependencies = selected.filter((skill) => !requested.includes(skill));
|
|
201
|
+
if (!opts.yes && !opts.dryRun && isTTY()) {
|
|
202
|
+
const ok = await confirm(`install ${paint(c.bold, String(selected.length))} skills to ${hosts.map((h) => HOSTS[h].label).join(" + ")}?`);
|
|
203
|
+
if (!ok) { console.log(paint(c.grey, "cancelled.")); return; }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (addedDependencies.length) console.log(`\n${paint(c.grey, `added ${addedDependencies.length} required skill dependencies`)}`);
|
|
207
|
+
for (const host of hosts) {
|
|
208
|
+
const dir = HOSTS[host].skillsDir(opts.scope);
|
|
209
|
+
console.log(`\n${paint(c.bold, HOSTS[host].label)} ${paint(c.grey, "→ " + dir)}`);
|
|
210
|
+
for (const s of selected) {
|
|
211
|
+
installSkill(s, { host, scope: opts.scope, dryRun: opts.dryRun });
|
|
212
|
+
console.log(` ${paint(c.green, "✓")} ${s.name} ${paint(c.grey, HOSTS[host].invoke(s.name))}`);
|
|
213
|
+
}
|
|
214
|
+
const agentsDir = installAgents({ host, scope: opts.scope, dryRun: opts.dryRun });
|
|
215
|
+
if (agentsDir) console.log(` ${paint(c.green, "✓")} agents ${paint(c.grey, "→ " + agentsDir)}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (opts.dryRun) { console.log(`\n${paint(c.yellow, "dry run — nothing written.")}`); return; }
|
|
219
|
+
console.log(`\n${paint(c.bold, "next:")}`);
|
|
220
|
+
for (const host of hosts) {
|
|
221
|
+
console.log(` ${paint(c.bold, HOSTS[host].label + ":")} ${paint(c.cyan, HOSTS[host].invoke("setup-pstack"))} ${paint(c.grey, "then " + HOSTS[host].invoke("poteto-mode"))}`);
|
|
222
|
+
}
|
|
223
|
+
console.log("");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function cmdRemove(opts) {
|
|
227
|
+
const hosts = resolveHosts(opts);
|
|
228
|
+
let names = opts._;
|
|
229
|
+
if (!names.length) {
|
|
230
|
+
const installedAnywhere = [...new Set(hosts.flatMap((h) => listInstalled({ host: h, scope: opts.scope }).map((s) => s.name)))];
|
|
231
|
+
if (!installedAnywhere.length) { console.log(paint(c.grey, "nothing installed.")); return; }
|
|
232
|
+
if (!isTTY()) { console.error(paint(c.red, "not a TTY. pass skill names.")); process.exit(1); }
|
|
233
|
+
const picked = await multiSelect(
|
|
234
|
+
installedAnywhere.sort().map((n) => ({ value: n, label: n })),
|
|
235
|
+
{ title: "Select skills to remove" },
|
|
236
|
+
);
|
|
237
|
+
if (picked === null || !picked.length) { console.log(paint(c.grey, "cancelled.")); return; }
|
|
238
|
+
names = picked;
|
|
239
|
+
}
|
|
240
|
+
for (const host of hosts) {
|
|
241
|
+
console.log(`\n${paint(c.bold, HOSTS[host].label)}`);
|
|
242
|
+
for (const n of names) {
|
|
243
|
+
const removed = removeSkill(n, { host, scope: opts.scope, dryRun: opts.dryRun });
|
|
244
|
+
console.log(removed ? ` ${paint(c.green, "✓")} removed ${n}` : ` ${paint(c.grey, "· not installed: " + n)}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
console.log("");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function cmdList(opts) {
|
|
251
|
+
for (const host of resolveHosts(opts)) {
|
|
252
|
+
const installed = listInstalled({ host, scope: opts.scope });
|
|
253
|
+
console.log(`\n${paint(c.bold, HOSTS[host].label)} ${paint(c.grey, HOSTS[host].skillsDir(opts.scope))}`);
|
|
254
|
+
if (!installed.length) console.log(paint(c.grey, " (none)"));
|
|
255
|
+
else {
|
|
256
|
+
const pad = Math.max(...installed.map((s) => s.name.length)) + 2;
|
|
257
|
+
installed.forEach((s) => {
|
|
258
|
+
const tag = s.linkedTo ? paint(c.grey, "⇢ " + s.linkedTo) : paint(c.grey, "(copy)");
|
|
259
|
+
console.log(` ${paint(c.green, "✓")} ${s.name.padEnd(pad)}${paint(c.grey, HOSTS[host].invoke(s.name).padEnd(pad))}${tag}`);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
console.log("");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function cmdUpdate(opts) {
|
|
267
|
+
const catalog = findSkills();
|
|
268
|
+
for (const host of resolveHosts(opts)) {
|
|
269
|
+
const installed = listInstalled({ host, scope: opts.scope }).map((s) => s.name);
|
|
270
|
+
console.log(`\n${paint(c.bold, HOSTS[host].label)}`);
|
|
271
|
+
if (!installed.length) { console.log(paint(c.grey, " (none installed)")); continue; }
|
|
272
|
+
const requested = catalog.filter((skill) => installed.includes(skill.dirName));
|
|
273
|
+
const selected = expandSkillDependencies(requested, catalog);
|
|
274
|
+
const addedDependencies = selected.filter((skill) => !installed.includes(skill.dirName));
|
|
275
|
+
if (addedDependencies.length) {
|
|
276
|
+
console.log(` ${paint(c.grey, `adding ${addedDependencies.length} required skill dependencies`)}`);
|
|
277
|
+
}
|
|
278
|
+
for (const s of selected) {
|
|
279
|
+
installSkill(s, { host, scope: opts.scope, dryRun: opts.dryRun });
|
|
280
|
+
console.log(` ${paint(c.green, "✓")} ${s.name}`);
|
|
281
|
+
}
|
|
282
|
+
installAgents({ host, scope: opts.scope, dryRun: opts.dryRun });
|
|
283
|
+
}
|
|
284
|
+
console.log("");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function cmdFind(opts) {
|
|
288
|
+
const q = opts._.join(" ").toLowerCase();
|
|
289
|
+
const catalog = findSkills().filter((s) => !q || s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q));
|
|
290
|
+
if (!catalog.length) { console.log(paint(c.grey, `no skills match "${q}"`)); return; }
|
|
291
|
+
const width = Math.max(30, (process.stdout.columns || 100) - NAME_COL - 4);
|
|
292
|
+
console.log("");
|
|
293
|
+
for (const s of catalog) {
|
|
294
|
+
console.log(` ${paint(c.cyan, s.name.padEnd(NAME_COL))}${paint(c.grey, truncate(s.description.replace(/\s+/g, " "), width))}`);
|
|
295
|
+
}
|
|
296
|
+
console.log(`\n ${paint(c.grey, catalog.length + " skills")}\n`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function cmdDoctor(opts) {
|
|
300
|
+
console.log(paint(c.cyan, BANNER));
|
|
301
|
+
const detected = detectHosts();
|
|
302
|
+
console.log(` ${paint(c.bold, "catalog")} ${findSkills().length} skills at ${paint(c.grey, REPO_ROOT)}\n`);
|
|
303
|
+
for (const host of ["claude", "codex"]) {
|
|
304
|
+
const present = detected.includes(host);
|
|
305
|
+
console.log(` ${present ? paint(c.green, "✓") : paint(c.grey, "·")} ${paint(c.bold, HOSTS[host].label)} ${present ? "" : paint(c.grey, "(not detected)")}`);
|
|
306
|
+
for (const scope of ["user", "project"]) {
|
|
307
|
+
const dir = HOSTS[host].skillsDir(scope);
|
|
308
|
+
const n = listInstalled({ host, scope }).length;
|
|
309
|
+
console.log(` ${scope.padEnd(8)} ${n ? paint(c.green, n + " installed") : paint(c.grey, "0 installed")} ${paint(c.grey, dir)}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
console.log("");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
316
|
+
if (opts.version) { console.log(version()); process.exit(0); }
|
|
317
|
+
const cmd = opts._.shift();
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
if (opts.help || !cmd) help();
|
|
321
|
+
else if (cmd === "add" || cmd === "install") await cmdAdd(opts);
|
|
322
|
+
else if (cmd === "remove" || cmd === "rm" || cmd === "uninstall") await cmdRemove(opts);
|
|
323
|
+
else if (cmd === "list" || cmd === "ls") cmdList(opts);
|
|
324
|
+
else if (cmd === "update") await cmdUpdate(opts);
|
|
325
|
+
else if (cmd === "find" || cmd === "search") cmdFind(opts);
|
|
326
|
+
else if (cmd === "doctor") cmdDoctor(opts);
|
|
327
|
+
else { console.error(paint(c.red, `unknown command: ${cmd}`)); help(); process.exit(1); }
|
|
328
|
+
} catch (err) {
|
|
329
|
+
console.error(paint(c.red, `\nerror: ${err.message}`));
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shiwenbin1617/pstack",
|
|
3
|
+
"version": "0.14.4",
|
|
4
|
+
"description": "if you want to go fast, go deep first. rigorous agent workflows for Claude Code and Codex. ported from poteto's pstack.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"pstack": "bin/pstack.mjs"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"scripts/",
|
|
15
|
+
"skills/",
|
|
16
|
+
"agents/",
|
|
17
|
+
"adapters/",
|
|
18
|
+
"automations/",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"pstack",
|
|
24
|
+
"poteto-mode",
|
|
25
|
+
"claude-code",
|
|
26
|
+
"codex",
|
|
27
|
+
"agent-skills",
|
|
28
|
+
"skills",
|
|
29
|
+
"workflow"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"author": "shiwenbin",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/shiwenbin1617/pstack.git"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/shiwenbin1617/pstack#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/shiwenbin1617/pstack/issues"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "node scripts/build.mjs",
|
|
46
|
+
"test": "node scripts/test.mjs"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Build the per-host trees from the single source in `skills/`, and validate them.
|
|
4
|
+
*
|
|
5
|
+
* node scripts/build.mjs → dist/claude-code/ and dist/codex/
|
|
6
|
+
* node scripts/build.mjs --check → validate only, write nothing
|
|
7
|
+
*
|
|
8
|
+
* The two trees share method content but compile host-specific metadata, paths,
|
|
9
|
+
* invocations, agent definitions, and runtime vocabulary.
|
|
10
|
+
*/
|
|
11
|
+
import { mkdirSync, rmSync, cpSync, writeFileSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { REPO_ROOT, SKILLS_DIR, AGENTS_DIR, CODEX_ADAPTER_DIR, CLAUDE_ADAPTER_DIR, findSkills } from "./lib.mjs";
|
|
14
|
+
import { adaptCopiedSkillTree, adaptMarkdownForHost } from "./host-adapters.mjs";
|
|
15
|
+
|
|
16
|
+
const check = process.argv.includes("--check");
|
|
17
|
+
const DIST = join(REPO_ROOT, "dist");
|
|
18
|
+
const skills = findSkills();
|
|
19
|
+
const CODEX_AGENTS_CONTENT = `# pstack
|
|
20
|
+
|
|
21
|
+
These skills are installed under \`.agents/skills/\`. Invoke one with \`$<name>\`, for example \`$poteto-mode\`.
|
|
22
|
+
|
|
23
|
+
Invoke \`$poteto-mode\` for a non-trivial task that needs the pstack workflow. Invoke it again for a later
|
|
24
|
+
task because mode activation is explicit and scoped to one task. Run \`$setup-pstack\` only to override models.
|
|
25
|
+
|
|
26
|
+
Custom subagents are standalone TOML files under \`.codex/agents/\` or \`~/.codex/agents/\`.
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
/* ---------- validate ---------- */
|
|
30
|
+
|
|
31
|
+
const problems = [];
|
|
32
|
+
for (const s of skills) {
|
|
33
|
+
if (!s.description) problems.push(`${s.relPath}: SKILL.md has no \`description\` frontmatter; it will never trigger`);
|
|
34
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(s.name)) problems.push(`${s.relPath}: name "${s.name}" is not a kebab-case slug`);
|
|
35
|
+
if (s.name !== s.dirName) problems.push(`${s.relPath}: frontmatter name "${s.name}" does not match its directory "${s.dirName}"`);
|
|
36
|
+
if (s.description.length > 1024) problems.push(`${s.relPath}: description is ${s.description.length} chars; Codex trims long ones first`);
|
|
37
|
+
}
|
|
38
|
+
const dupes = skills.map((s) => s.dirName).filter((n, i, a) => a.indexOf(n) !== i);
|
|
39
|
+
if (dupes.length) problems.push(`duplicate skill directory names, which collide when flattened: ${[...new Set(dupes)].join(", ")}`);
|
|
40
|
+
|
|
41
|
+
// Every relative markdown link inside a skill must resolve.
|
|
42
|
+
const brokenLinks = [];
|
|
43
|
+
for (const s of skills) {
|
|
44
|
+
const walk = (dir) => {
|
|
45
|
+
for (const e of readdirSync(dir)) {
|
|
46
|
+
const p = join(dir, e);
|
|
47
|
+
if (statSync(p).isDirectory()) { walk(p); continue; }
|
|
48
|
+
if (!p.endsWith(".md")) continue;
|
|
49
|
+
const body = readFileSync(p, "utf8");
|
|
50
|
+
for (const m of body.matchAll(/\]\((?!https?:|#|mailto:)([^)]+)\)/g)) {
|
|
51
|
+
const target = m[1].split("#")[0];
|
|
52
|
+
// Skip template placeholders like [PR #123](url) and [x]({path}).
|
|
53
|
+
if (!target || target.startsWith("{") || !/[./]/.test(target)) continue;
|
|
54
|
+
try { statSync(join(dir, target)); }
|
|
55
|
+
catch { brokenLinks.push(`${s.name}: ${p.replace(REPO_ROOT + "/", "")} → ${target}`); }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
walk(s.path);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const codexLeaks = [];
|
|
63
|
+
const claudeLeaks = [];
|
|
64
|
+
const runtimeLeaks = [];
|
|
65
|
+
const skillNames = skills.map((skill) => skill.name);
|
|
66
|
+
const CODEX_LEAK_PATTERNS = [
|
|
67
|
+
[/\.claude\//, "Claude path"],
|
|
68
|
+
[/Claude Code/, "Claude host name"],
|
|
69
|
+
[/Task tool/, "Claude/Cursor Task tool"],
|
|
70
|
+
[/subagent_type:/, "Claude subagent type"],
|
|
71
|
+
[/run_in_background/, "Claude background argument"],
|
|
72
|
+
[/AskQuestion|AskUserQuestion/, "host-specific question tool"],
|
|
73
|
+
];
|
|
74
|
+
const CLAUDE_LEAK_PATTERNS = [
|
|
75
|
+
[/\.codex\//, "Codex path"],
|
|
76
|
+
[/\.agents\/skills/, "Codex skill path"],
|
|
77
|
+
[/\$[a-z][a-z0-9-]+/, "Codex skill invocation"],
|
|
78
|
+
];
|
|
79
|
+
const RUNTIME_LEAK_PATTERNS = [
|
|
80
|
+
[/\.cursor\/projects/, "Cursor transcript path"],
|
|
81
|
+
[/\bgrok-[a-z0-9.-]+/i, "hard-coded model slug"],
|
|
82
|
+
[/Bun\.spawnSync\([\s\S]{0,200}["']install["']/, "implicit package installation"],
|
|
83
|
+
];
|
|
84
|
+
for (const [pattern, why] of CODEX_LEAK_PATTERNS) {
|
|
85
|
+
const match = pattern.exec(CODEX_AGENTS_CONTENT);
|
|
86
|
+
if (match) codexLeaks.push(`generated Codex AGENTS.md: ${why} (${match[0]})`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const s of skills) {
|
|
90
|
+
const override = join(CODEX_ADAPTER_DIR, "overrides", s.dirName);
|
|
91
|
+
const nativeCodex = Boolean(statOrNull(override));
|
|
92
|
+
const source = nativeCodex ? override : s.path;
|
|
93
|
+
const walk = (dir) => {
|
|
94
|
+
for (const e of readdirSync(dir)) {
|
|
95
|
+
const p = join(dir, e);
|
|
96
|
+
if (statSync(p).isDirectory()) { walk(p); continue; }
|
|
97
|
+
if (!p.endsWith(".md")) continue;
|
|
98
|
+
const raw = readFileSync(p, "utf8");
|
|
99
|
+
const body = nativeCodex ? raw : adaptMarkdownForHost(raw, { host: "codex", skillNames });
|
|
100
|
+
for (const [pat, why] of CODEX_LEAK_PATTERNS) {
|
|
101
|
+
const m = pat.exec(body);
|
|
102
|
+
if (m) codexLeaks.push(`${p.replace(REPO_ROOT + "/", "")}: ${why} (${m[0]})`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
walk(source);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const runtimeDir = join(SKILLS_DIR, "poteto-mode", "scripts");
|
|
110
|
+
const scanRuntime = (dir) => {
|
|
111
|
+
for (const entry of readdirSync(dir)) {
|
|
112
|
+
const path = join(dir, entry);
|
|
113
|
+
if (statSync(path).isDirectory()) {
|
|
114
|
+
scanRuntime(path);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const body = readFileSync(path, "utf8");
|
|
118
|
+
for (const [pattern, why] of RUNTIME_LEAK_PATTERNS) {
|
|
119
|
+
const match = pattern.exec(body);
|
|
120
|
+
if (match) runtimeLeaks.push(`${path.replace(REPO_ROOT + "/", "")}: ${why} (${match[0]})`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
scanRuntime(runtimeDir);
|
|
125
|
+
|
|
126
|
+
for (const s of skills) {
|
|
127
|
+
const override = join(CLAUDE_ADAPTER_DIR, "overrides", s.dirName);
|
|
128
|
+
const nativeClaude = Boolean(statOrNull(override));
|
|
129
|
+
const source = nativeClaude ? override : s.path;
|
|
130
|
+
const walk = (dir) => {
|
|
131
|
+
for (const e of readdirSync(dir)) {
|
|
132
|
+
const p = join(dir, e);
|
|
133
|
+
if (statSync(p).isDirectory()) { walk(p); continue; }
|
|
134
|
+
if (!p.endsWith(".md")) continue;
|
|
135
|
+
const raw = readFileSync(p, "utf8");
|
|
136
|
+
const body = nativeClaude ? raw : adaptMarkdownForHost(raw, { host: "claude", skillNames });
|
|
137
|
+
for (const [pat, why] of CLAUDE_LEAK_PATTERNS) {
|
|
138
|
+
const m = pat.exec(body);
|
|
139
|
+
if (m) claudeLeaks.push(`${p.replace(REPO_ROOT + "/", "")}: ${why} (${m[0]})`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
walk(source);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function statOrNull(path) {
|
|
147
|
+
try { return statSync(path); } catch { return null; }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const report = (label, list) => {
|
|
151
|
+
if (!list.length) return false;
|
|
152
|
+
console.error(`\n${label}`);
|
|
153
|
+
list.forEach((p) => console.error(` - ${p}`));
|
|
154
|
+
return true;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
let failed = false;
|
|
158
|
+
failed = report("problems:", problems) || failed;
|
|
159
|
+
failed = report("broken relative links:", brokenLinks) || failed;
|
|
160
|
+
failed = report("Claude-specific leaks in generated Codex skills:", codexLeaks) || failed;
|
|
161
|
+
failed = report("Codex-specific leaks in generated Claude Code skills:", claudeLeaks) || failed;
|
|
162
|
+
failed = report("Host/model leaks in shared runtime scripts:", runtimeLeaks) || failed;
|
|
163
|
+
|
|
164
|
+
console.log(`checked ${skills.length} skills`);
|
|
165
|
+
if (failed) process.exit(1);
|
|
166
|
+
if (check) { console.log("ok"); process.exit(0); }
|
|
167
|
+
|
|
168
|
+
/* ---------- emit ---------- */
|
|
169
|
+
|
|
170
|
+
rmSync(DIST, { recursive: true, force: true });
|
|
171
|
+
|
|
172
|
+
// Claude Code: a host-native skills and agents tree.
|
|
173
|
+
const cc = join(DIST, "claude-code");
|
|
174
|
+
mkdirSync(cc, { recursive: true });
|
|
175
|
+
cpSync(SKILLS_DIR, join(cc, "skills"), { recursive: true });
|
|
176
|
+
cpSync(AGENTS_DIR, join(cc, "agents"), { recursive: true });
|
|
177
|
+
for (const s of skills) {
|
|
178
|
+
const dest = join(cc, "skills", s.dirName);
|
|
179
|
+
const override = join(CLAUDE_ADAPTER_DIR, "overrides", s.dirName);
|
|
180
|
+
const nativeClaude = Boolean(statOrNull(override));
|
|
181
|
+
if (nativeClaude) {
|
|
182
|
+
rmSync(dest, { recursive: true, force: true });
|
|
183
|
+
cpSync(override, dest, { recursive: true });
|
|
184
|
+
}
|
|
185
|
+
adaptCopiedSkillTree(dest, {
|
|
186
|
+
host: "claude",
|
|
187
|
+
skillNames,
|
|
188
|
+
allowImplicitInvocation: true,
|
|
189
|
+
nativeCodex: nativeClaude,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Codex: the same content, flattened into .agents/skills.
|
|
194
|
+
const cx = join(DIST, "codex", ".agents", "skills");
|
|
195
|
+
mkdirSync(cx, { recursive: true });
|
|
196
|
+
for (const s of skills) cpSync(s.path, join(cx, s.dirName), { recursive: true });
|
|
197
|
+
for (const s of skills) {
|
|
198
|
+
const dest = join(cx, s.dirName);
|
|
199
|
+
const override = join(CODEX_ADAPTER_DIR, "overrides", s.dirName);
|
|
200
|
+
if (statOrNull(override)) {
|
|
201
|
+
rmSync(dest, { recursive: true, force: true });
|
|
202
|
+
cpSync(override, dest, { recursive: true });
|
|
203
|
+
}
|
|
204
|
+
adaptCopiedSkillTree(dest, {
|
|
205
|
+
host: "codex",
|
|
206
|
+
skillNames,
|
|
207
|
+
allowImplicitInvocation: !readFileSync(join(s.path, "SKILL.md"), "utf8").includes("disable-model-invocation: true"),
|
|
208
|
+
nativeCodex: Boolean(statOrNull(override)),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const codexAgents = join(DIST, "codex", ".codex", "agents");
|
|
213
|
+
mkdirSync(codexAgents, { recursive: true });
|
|
214
|
+
cpSync(join(CODEX_ADAPTER_DIR, "agents"), codexAgents, { recursive: true });
|
|
215
|
+
|
|
216
|
+
writeFileSync(join(DIST, "codex", "AGENTS.md"), CODEX_AGENTS_CONTENT);
|
|
217
|
+
|
|
218
|
+
console.log(`built independent dist/claude-code and dist/codex host trees`);
|