@4pm/cli 1.19.6 → 1.19.9
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/index.js
CHANGED
|
@@ -37191,7 +37191,6 @@ var toolRuleSchema = external_exports.string().min(1).max(500);
|
|
|
37191
37191
|
var agentToolsPermissionsSchema = external_exports.object({
|
|
37192
37192
|
defaultMode: external_exports.enum(["default", "acceptEdits", "plan", "bypassPermissions"]),
|
|
37193
37193
|
allow: external_exports.array(toolRuleSchema).max(500),
|
|
37194
|
-
ask: external_exports.array(toolRuleSchema).max(500),
|
|
37195
37194
|
deny: external_exports.array(toolRuleSchema).max(500)
|
|
37196
37195
|
});
|
|
37197
37196
|
var agentToolsReadQuerySchema = external_exports.object({
|
|
@@ -37611,6 +37610,11 @@ async function attachSubmodules(root, submodules, emit, createMissingBranch = fa
|
|
|
37611
37610
|
}
|
|
37612
37611
|
continue;
|
|
37613
37612
|
}
|
|
37613
|
+
const abs = join6(root, dir2);
|
|
37614
|
+
if (existsSync4(abs)) {
|
|
37615
|
+
emit("submodule", `Removing placeholder ${dir2} before attaching the submodule\u2026`);
|
|
37616
|
+
await rm(abs, { recursive: true, force: true });
|
|
37617
|
+
}
|
|
37614
37618
|
emit("submodule", `Adding submodule ${sub.url} \u2192 ${dir2}\u2026`);
|
|
37615
37619
|
const b2 = (sub.branch ?? "").trim();
|
|
37616
37620
|
if (b2 && createMissingBranch && !await ensureRemoteBranch(sub.url, b2, emit)) {
|
|
@@ -37653,8 +37657,13 @@ async function attachSubmodules(root, submodules, emit, createMissingBranch = fa
|
|
|
37653
37657
|
}
|
|
37654
37658
|
async function commitAndPushSubmodules(root, submodules, emit) {
|
|
37655
37659
|
const dirs = submodules.map((s3) => (s3.subdir ?? "").trim()).filter(Boolean);
|
|
37660
|
+
const addPaths = [
|
|
37661
|
+
...existsSync4(join6(root, ".gitmodules")) ? [".gitmodules"] : [],
|
|
37662
|
+
...dirs.filter((d) => existsSync4(join6(root, d)))
|
|
37663
|
+
];
|
|
37664
|
+
if (addPaths.length === 0) return;
|
|
37656
37665
|
emit("submodule", "Committing .gitmodules\u2026");
|
|
37657
|
-
await run2("git", ["add",
|
|
37666
|
+
await run2("git", ["add", ...addPaths], { cwd: root, timeout: 6e4 });
|
|
37658
37667
|
const staged = (await run2("git", ["diff", "--cached", "--name-only"], { cwd: root, timeout: 3e4 })).stdout.trim();
|
|
37659
37668
|
if (!staged) return;
|
|
37660
37669
|
const commitArgs = ["commit", "-m", "chore: add git submodules (4PM)"];
|
|
@@ -37671,6 +37680,50 @@ async function commitAndPushSubmodules(root, submodules, emit) {
|
|
|
37671
37680
|
emit("submodule-push-failed", `Submodules committed locally but the push failed: ${msg}`);
|
|
37672
37681
|
}
|
|
37673
37682
|
}
|
|
37683
|
+
function providerSlug(url) {
|
|
37684
|
+
const provider = /github\.com/i.test(url) ? "gh" : /gitlab\.com/i.test(url) ? "glab" : null;
|
|
37685
|
+
const m2 = /(?:github|gitlab)\.com[/:]+([^/]+\/.+?)(?:\.git)?\/?$/i.exec(url);
|
|
37686
|
+
return { provider, slug: m2?.[1] ?? "" };
|
|
37687
|
+
}
|
|
37688
|
+
async function commitAndOpenPr(root, repo, emit) {
|
|
37689
|
+
emit("commit", "Committing the scaffolded project\u2026");
|
|
37690
|
+
await run2("git", ["add", "-A"], { cwd: root, timeout: 6e4 });
|
|
37691
|
+
const staged = (await run2("git", ["diff", "--cached", "--name-only"], { cwd: root, timeout: 3e4 })).stdout.trim();
|
|
37692
|
+
if (!staged) {
|
|
37693
|
+
emit("commit", "Nothing to commit \u2014 skipping the pull request.");
|
|
37694
|
+
return;
|
|
37695
|
+
}
|
|
37696
|
+
const commitArgs = ["commit", "-m", "chore: scaffold project (4PM)"];
|
|
37697
|
+
try {
|
|
37698
|
+
await run2("git", commitArgs, { cwd: root, timeout: 6e4 });
|
|
37699
|
+
} catch {
|
|
37700
|
+
await run2("git", ["-c", "user.name=4PM", "-c", "user.email=noreply@4pm.app", ...commitArgs], { cwd: root, timeout: 6e4 });
|
|
37701
|
+
}
|
|
37702
|
+
const branch = (repo.branch ?? "").trim();
|
|
37703
|
+
emit("push", "Pushing the scaffold branch\u2026");
|
|
37704
|
+
try {
|
|
37705
|
+
await run2("git", ["push", "-u", "origin", ...branch ? [branch] : ["HEAD"]], { cwd: root, timeout: 12e4 });
|
|
37706
|
+
} catch (err) {
|
|
37707
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
37708
|
+
emit("push-failed", `Scaffold committed locally but the push failed: ${msg} \u2014 push it from the Git tab when credentials are available.`);
|
|
37709
|
+
return;
|
|
37710
|
+
}
|
|
37711
|
+
const { provider, slug } = providerSlug(repo.url ?? "");
|
|
37712
|
+
if (!provider) {
|
|
37713
|
+
emit("pr", "Unknown git host \u2014 skipping the pull request.");
|
|
37714
|
+
return;
|
|
37715
|
+
}
|
|
37716
|
+
emit("pr", `Opening a pull request via ${provider}\u2026`);
|
|
37717
|
+
try {
|
|
37718
|
+
const args = provider === "glab" ? ["mr", "create", "--fill", ...slug ? ["-R", slug] : []] : ["pr", "create", "--fill", ...slug ? ["-R", slug] : []];
|
|
37719
|
+
const { stdout } = await run2(provider, args, { cwd: root, timeout: 12e4 });
|
|
37720
|
+
const link2 = stdout.trim().split(/\s+/).find((s3) => /^https?:\/\//.test(s3)) ?? stdout.trim().split("\n").pop() ?? "";
|
|
37721
|
+
emit("pr", `Pull request opened${link2 ? `: ${link2}` : "."}`);
|
|
37722
|
+
} catch (err) {
|
|
37723
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
37724
|
+
emit("pr-failed", `Could not open a pull request (${provider}): ${msg}`);
|
|
37725
|
+
}
|
|
37726
|
+
}
|
|
37674
37727
|
function cloneArgs(url, dest, branch) {
|
|
37675
37728
|
const b2 = (branch ?? "").trim();
|
|
37676
37729
|
return b2 ? ["clone", "-b", b2, url, dest] : ["clone", url, dest];
|
|
@@ -37777,18 +37830,20 @@ async function scaffoldProject(payload, profileDir2, onProgress) {
|
|
|
37777
37830
|
if (!repo) throw new Error("A project must declare one repo (ADR-0314).");
|
|
37778
37831
|
emit("git", "Cloning repository\u2026");
|
|
37779
37832
|
await provisionRepo(target, repo, emit, { createMissingBranch: true });
|
|
37780
|
-
await scaffoldRepo(target, payload.projectName, payload.spec, emit);
|
|
37833
|
+
await scaffoldRepo(target, payload.projectName, payload.spec, emit, { resetClaude: true });
|
|
37781
37834
|
await attachSubmodules(target, submodulesOf(reposOf(payload.spec)), emit, true);
|
|
37782
37835
|
emit("version", "Writing .4pm/.4pm.json\u2026");
|
|
37783
37836
|
await writeTemplateMarker(target);
|
|
37837
|
+
await commitAndOpenPr(target, repo, emit);
|
|
37784
37838
|
onProgress?.({ projectId: payload.projectId, step: "done", message: "Scaffold complete.", done: true });
|
|
37785
37839
|
return { ok: true, path: target };
|
|
37786
37840
|
} catch (err) {
|
|
37787
37841
|
return { ok: false, error: err instanceof Error ? err.message : String(err), step: lastStep };
|
|
37788
37842
|
}
|
|
37789
37843
|
}
|
|
37790
|
-
async function scaffoldRepo(dir2, label, spec, emit) {
|
|
37844
|
+
async function scaffoldRepo(dir2, label, spec, emit, opts = {}) {
|
|
37791
37845
|
await mkdir(dir2, { recursive: true });
|
|
37846
|
+
if (opts.resetClaude) await resetTemplateManagedClaude(dir2, label, emit);
|
|
37792
37847
|
emit("copy", `Copying the sample template into ${label}\u2026`);
|
|
37793
37848
|
await cp(sampleDir(), dir2, { recursive: true, force: false, errorOnExist: false });
|
|
37794
37849
|
if (spec) {
|
|
@@ -37797,6 +37852,13 @@ async function scaffoldRepo(dir2, label, spec, emit) {
|
|
|
37797
37852
|
await aiInit(dir2, spec, emit);
|
|
37798
37853
|
}
|
|
37799
37854
|
}
|
|
37855
|
+
async function resetTemplateManagedClaude(dir2, label, emit) {
|
|
37856
|
+
const claude = join6(dir2, ".claude");
|
|
37857
|
+
if (!existsSync4(claude)) return;
|
|
37858
|
+
emit("copy", `Resetting template .claude config in ${label}\u2026`);
|
|
37859
|
+
await rm(join6(claude, "settings.json"), { force: true });
|
|
37860
|
+
await rm(join6(claude, "skills"), { recursive: true, force: true });
|
|
37861
|
+
}
|
|
37800
37862
|
async function writeTemplateMarker(target) {
|
|
37801
37863
|
const dir2 = join6(target, ".4pm");
|
|
37802
37864
|
await mkdir(dir2, { recursive: true });
|
|
@@ -37844,15 +37906,20 @@ ${specJson}`
|
|
|
37844
37906
|
${specJson}` + (guideInstructions ? `
|
|
37845
37907
|
Also incorporate these additional instructions/content:
|
|
37846
37908
|
${guideInstructions}
|
|
37847
|
-
` : "")
|
|
37909
|
+
` : ""),
|
|
37910
|
+
guideInstructions
|
|
37848
37911
|
);
|
|
37849
37912
|
}
|
|
37850
|
-
async function generateFile(path, prompt) {
|
|
37913
|
+
async function generateFile(path, prompt, fallback) {
|
|
37851
37914
|
try {
|
|
37852
37915
|
const text = await aiGenerate(prompt);
|
|
37853
|
-
if (text.trim())
|
|
37916
|
+
if (text.trim()) {
|
|
37917
|
+
await writeFile(path, text.trim() + "\n", "utf8");
|
|
37918
|
+
return;
|
|
37919
|
+
}
|
|
37854
37920
|
} catch {
|
|
37855
37921
|
}
|
|
37922
|
+
if (fallback && fallback.trim()) await writeFile(path, fallback.trim() + "\n", "utf8");
|
|
37856
37923
|
}
|
|
37857
37924
|
async function addProject(payload, profileDir2, onProgress) {
|
|
37858
37925
|
let lastStep = "start";
|
|
@@ -42350,7 +42417,8 @@ async function gitRepos(root) {
|
|
|
42350
42417
|
if (!top || !(top === base || top.startsWith(base + sep8))) return { repos: [] };
|
|
42351
42418
|
const repos = [];
|
|
42352
42419
|
const rootRemote = (await git(base, ["remote", "get-url", "origin"])).trim();
|
|
42353
|
-
|
|
42420
|
+
const rootBranch = (await git(base, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
42421
|
+
repos.push({ subdir: "", name: "", remote: rootRemote || null, branch: rootBranch || null });
|
|
42354
42422
|
const subOut = await git(base, ["submodule", "status"]);
|
|
42355
42423
|
for (const line of subOut.split("\n")) {
|
|
42356
42424
|
const m2 = line.trim().match(/^[-+U ]?[0-9a-f]{7,40}\s+(\S+)/);
|
|
@@ -42359,7 +42427,8 @@ async function gitRepos(root) {
|
|
|
42359
42427
|
const abs = resolve8(base, subPath);
|
|
42360
42428
|
if (!(abs === base || abs.startsWith(base + sep8))) continue;
|
|
42361
42429
|
const remote = (await git(abs, ["remote", "get-url", "origin"])).trim();
|
|
42362
|
-
|
|
42430
|
+
const branch = (await git(abs, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
42431
|
+
repos.push({ subdir: subPath, name: subPath, remote: remote || null, branch: branch || null });
|
|
42363
42432
|
}
|
|
42364
42433
|
return { repos };
|
|
42365
42434
|
}
|
|
@@ -43029,7 +43098,6 @@ import { dirname as dirname8, join as join26 } from "path";
|
|
|
43029
43098
|
var SHARED_REL = ".claude/settings.json";
|
|
43030
43099
|
var LOCAL_REL = ".claude/settings.local.json";
|
|
43031
43100
|
var SECRETS_DENY = "Read(./project.secrets.json)";
|
|
43032
|
-
var MODES = ["default", "acceptEdits", "plan", "bypassPermissions"];
|
|
43033
43101
|
function fileFor(root, scope) {
|
|
43034
43102
|
return join26(root, scope === "shared" ? SHARED_REL : LOCAL_REL);
|
|
43035
43103
|
}
|
|
@@ -43043,9 +43111,8 @@ async function readSettingsObject(path) {
|
|
|
43043
43111
|
}
|
|
43044
43112
|
function normalizePermissions(raw) {
|
|
43045
43113
|
const p2 = raw && typeof raw === "object" ? raw : {};
|
|
43046
|
-
const mode = MODES.includes(p2.defaultMode) ? p2.defaultMode : "default";
|
|
43047
43114
|
const list = (v2) => Array.isArray(v2) ? v2.filter((x) => typeof x === "string") : [];
|
|
43048
|
-
return { defaultMode:
|
|
43115
|
+
return { defaultMode: "bypassPermissions", allow: list(p2.allow), deny: list(p2.deny) };
|
|
43049
43116
|
}
|
|
43050
43117
|
async function readAgentTools(root, scope) {
|
|
43051
43118
|
const obj = await readSettingsObject(fileFor(root, scope));
|