@4pm/cli 1.8.4-b → 1.9.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/index.js +92 -36
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33965,6 +33965,14 @@ var issueProvisioningTokenRequestSchema = external_exports.object({
|
|
|
33965
33965
|
/** TTL seconds (0/absent ⇒ a default short TTL applied server-side). */
|
|
33966
33966
|
ttlSec: external_exports.number().int().min(0).max(2592e3).optional()
|
|
33967
33967
|
});
|
|
33968
|
+
var provisionRequestSchema = external_exports.object({
|
|
33969
|
+
mode: external_exports.enum(["sync", "force"]).optional().default("sync")
|
|
33970
|
+
});
|
|
33971
|
+
var provisionForUserRequestSchema = external_exports.object({
|
|
33972
|
+
userId: external_exports.string().uuid(),
|
|
33973
|
+
projectId: external_exports.string().uuid(),
|
|
33974
|
+
mode: external_exports.enum(["sync", "force"]).optional().default("sync")
|
|
33975
|
+
});
|
|
33968
33976
|
var listWorkersQuerySchema = baseRequestSchema.extend({
|
|
33969
33977
|
status: external_exports.enum(["online", "offline"]).optional(),
|
|
33970
33978
|
/** Only workers whose live CPU/memory/disk usage % ≥ the given threshold (0–100). */
|
|
@@ -34240,7 +34248,13 @@ var repoSpecSchema = external_exports.object({
|
|
|
34240
34248
|
/** Clone url — `https` or `ssh` (required; ADR-0172). */
|
|
34241
34249
|
url: external_exports.string().max(500).regex(GIT_URL_RE, "must be an https or ssh git url"),
|
|
34242
34250
|
/** Sub-repo subfolder under the target root (empty ⇒ derived from role/name). */
|
|
34243
|
-
subdir: external_exports.string().max(120).optional().default("")
|
|
34251
|
+
subdir: external_exports.string().max(120).optional().default(""),
|
|
34252
|
+
/**
|
|
34253
|
+
* Primary branch to clone / check out (ADR-0292). Empty ⇒ the repo's default branch. The
|
|
34254
|
+
* worker clones with `-b <branch>` and the on-demand "update repos" action checks it out +
|
|
34255
|
+
* fast-forward pulls it.
|
|
34256
|
+
*/
|
|
34257
|
+
branch: external_exports.string().max(200).optional().default("")
|
|
34244
34258
|
});
|
|
34245
34259
|
function repoName(url) {
|
|
34246
34260
|
const last = (url || "").trim().replace(/\/+$/, "").split(/[/:]/).pop() ?? "";
|
|
@@ -34368,7 +34382,7 @@ var activityHeartbeatSchema = external_exports.object({
|
|
|
34368
34382
|
});
|
|
34369
34383
|
|
|
34370
34384
|
// src/core/scaffold.ts
|
|
34371
|
-
import { cp, mkdir, writeFile } from "fs/promises";
|
|
34385
|
+
import { cp, mkdir, readdir, rename, rm, writeFile } from "fs/promises";
|
|
34372
34386
|
import { existsSync as existsSync4 } from "fs";
|
|
34373
34387
|
import { execFile as execFile2 } from "child_process";
|
|
34374
34388
|
import { dirname as dirname3, join as join6, resolve } from "path";
|
|
@@ -34457,16 +34471,47 @@ function subDirName(repo) {
|
|
|
34457
34471
|
const raw = (repo.subdir || repo.role || repoName(repo.url ?? "") || "repo").trim();
|
|
34458
34472
|
return raw.replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
|
|
34459
34473
|
}
|
|
34474
|
+
function cloneArgs(url, dest, branch) {
|
|
34475
|
+
const b = (branch ?? "").trim();
|
|
34476
|
+
return b ? ["clone", "-b", b, url, dest] : ["clone", url, dest];
|
|
34477
|
+
}
|
|
34478
|
+
async function updateRepo(dir2, label, branch, emit) {
|
|
34479
|
+
const b = (branch ?? "").trim();
|
|
34480
|
+
emit("git", `Updating ${label} (fetch + ${b ? `checkout ${b} + ` : ""}fast-forward pull)\u2026`);
|
|
34481
|
+
await run2("git", ["fetch", "origin", "--prune"], { cwd: dir2, timeout: 12e4 });
|
|
34482
|
+
if (b) await run2("git", ["checkout", b], { cwd: dir2, timeout: 6e4 });
|
|
34483
|
+
await run2("git", ["pull", "--ff-only"], { cwd: dir2, timeout: 12e4 });
|
|
34484
|
+
}
|
|
34460
34485
|
async function provisionRepos(target, repos, emit, opts = {}) {
|
|
34486
|
+
const mode = opts.mode ?? "clone";
|
|
34487
|
+
const subDirsToKeep = new Set(repos.filter((r) => !r.primary && r.url).map((r) => subDirName(r)));
|
|
34461
34488
|
for (const repo of repos) {
|
|
34462
34489
|
if (repo.primary) {
|
|
34463
34490
|
if (opts.clonePrimary && repo.url) {
|
|
34464
34491
|
if (existsSync4(join6(target, ".git"))) {
|
|
34465
|
-
|
|
34492
|
+
if (mode === "sync") {
|
|
34493
|
+
await updateRepo(target, "primary repo", repo.branch, emit);
|
|
34494
|
+
} else if (mode === "force") {
|
|
34495
|
+
emit("git", `Re-cloning ${repo.url} (force)\u2026`);
|
|
34496
|
+
const tmp = `${target}.4pm-reclone`;
|
|
34497
|
+
await rm(tmp, { recursive: true, force: true });
|
|
34498
|
+
await run2("git", cloneArgs(repo.url, tmp, repo.branch), { timeout: 12e4 });
|
|
34499
|
+
for (const entry of await readdir(target)) {
|
|
34500
|
+
if (subDirsToKeep.has(entry)) continue;
|
|
34501
|
+
await rm(join6(target, entry), { recursive: true, force: true });
|
|
34502
|
+
}
|
|
34503
|
+
for (const entry of await readdir(tmp)) {
|
|
34504
|
+
if (subDirsToKeep.has(entry)) continue;
|
|
34505
|
+
await rename(join6(tmp, entry), join6(target, entry));
|
|
34506
|
+
}
|
|
34507
|
+
await rm(tmp, { recursive: true, force: true });
|
|
34508
|
+
} else {
|
|
34509
|
+
emit("git", `Primary repo already present \u2014 skipping clone.`);
|
|
34510
|
+
}
|
|
34466
34511
|
continue;
|
|
34467
34512
|
}
|
|
34468
34513
|
emit("git", `Cloning ${repo.url}\u2026`);
|
|
34469
|
-
await run2("git",
|
|
34514
|
+
await run2("git", cloneArgs(repo.url, target, repo.branch), { timeout: 12e4 });
|
|
34470
34515
|
continue;
|
|
34471
34516
|
}
|
|
34472
34517
|
emit("git", "Initializing the primary repo\u2026");
|
|
@@ -34481,11 +34526,19 @@ async function provisionRepos(target, repos, emit, opts = {}) {
|
|
|
34481
34526
|
const dir2 = join6(target, subDirName(repo));
|
|
34482
34527
|
if (repo.url) {
|
|
34483
34528
|
if (existsSync4(join6(dir2, ".git"))) {
|
|
34484
|
-
|
|
34529
|
+
if (mode === "sync") {
|
|
34530
|
+
await updateRepo(dir2, `sub-repo ${subDirName(repo)}`, repo.branch, emit);
|
|
34531
|
+
} else if (mode === "force") {
|
|
34532
|
+
emit("git", `Re-cloning sub-repo ${repo.url} (force)\u2026`);
|
|
34533
|
+
await rm(dir2, { recursive: true, force: true });
|
|
34534
|
+
await run2("git", cloneArgs(repo.url, dir2, repo.branch), { timeout: 12e4 });
|
|
34535
|
+
} else {
|
|
34536
|
+
emit("git", `Sub-repo ${subDirName(repo)} already present \u2014 skipping clone.`);
|
|
34537
|
+
}
|
|
34485
34538
|
continue;
|
|
34486
34539
|
}
|
|
34487
34540
|
emit("git", `Cloning sub-repo ${repo.url}\u2026`);
|
|
34488
|
-
await run2("git",
|
|
34541
|
+
await run2("git", cloneArgs(repo.url, dir2, repo.branch), { timeout: 12e4 });
|
|
34489
34542
|
} else {
|
|
34490
34543
|
emit("git", `Initializing sub-repo ${subDirName(repo)}\u2026`);
|
|
34491
34544
|
await mkdir(dir2, { recursive: true });
|
|
@@ -34590,11 +34643,14 @@ async function addProject(payload, profileDir2, onProgress) {
|
|
|
34590
34643
|
const target = join6(root, payload.projectName);
|
|
34591
34644
|
await mkdir(target, { recursive: true });
|
|
34592
34645
|
const repos = payload.repos ?? [];
|
|
34593
|
-
|
|
34646
|
+
const mode = payload.mode ?? "clone";
|
|
34647
|
+
if (repos.length > 0) await provisionRepos(target, repos, emit, { clonePrimary: true, mode });
|
|
34594
34648
|
onProgress?.({ projectId: payload.projectId, step: "done", message: "Project added.", done: true });
|
|
34595
34649
|
return { ok: true, path: target };
|
|
34596
34650
|
} catch (err) {
|
|
34597
|
-
|
|
34651
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
34652
|
+
onProgress?.({ projectId: payload.projectId, step: "error", message: `Failed: ${message}`, done: true });
|
|
34653
|
+
return { ok: false, error: message, step: lastStep };
|
|
34598
34654
|
}
|
|
34599
34655
|
}
|
|
34600
34656
|
|
|
@@ -35082,7 +35138,7 @@ function decryptPayload(key, payloadBase64, nonceBase64) {
|
|
|
35082
35138
|
import { spawn as spawn2 } from "child_process";
|
|
35083
35139
|
|
|
35084
35140
|
// src/common/logger/logger.ts
|
|
35085
|
-
import { appendFile, mkdir as mkdir2, readdir, unlink } from "fs/promises";
|
|
35141
|
+
import { appendFile, mkdir as mkdir2, readdir as readdir2, unlink } from "fs/promises";
|
|
35086
35142
|
import { readdirSync as readdirSync2, readFileSync as readFileSync6, statSync } from "fs";
|
|
35087
35143
|
import { join as join7 } from "path";
|
|
35088
35144
|
var LEVEL_ORDER = { error: 0, warn: 1, info: 2, debug: 3 };
|
|
@@ -35142,7 +35198,7 @@ var CliLogger = class {
|
|
|
35142
35198
|
async cleanupOldLogs() {
|
|
35143
35199
|
const dir2 = this.dir;
|
|
35144
35200
|
if (!dir2) return;
|
|
35145
|
-
const files = await
|
|
35201
|
+
const files = await readdir2(dir2).catch(() => []);
|
|
35146
35202
|
const cutoff = Date.now() - this.retentionDays * DAY_MS;
|
|
35147
35203
|
for (const file of files) {
|
|
35148
35204
|
const match = /^cli-(\d{4}-\d{2}-\d{2})\.jsonl$/.exec(file);
|
|
@@ -38123,7 +38179,7 @@ function rejectByReview(ctx, commandId, origin, prompt, reasons) {
|
|
|
38123
38179
|
}
|
|
38124
38180
|
|
|
38125
38181
|
// src/core/fs-browse.ts
|
|
38126
|
-
import { readdir as
|
|
38182
|
+
import { readdir as readdir3 } from "fs/promises";
|
|
38127
38183
|
import { resolve as resolve2, sep as sep2 } from "path";
|
|
38128
38184
|
async function listDir(path, root) {
|
|
38129
38185
|
if (!root) return { path: "", entries: [] };
|
|
@@ -38131,7 +38187,7 @@ async function listDir(path, root) {
|
|
|
38131
38187
|
const requested = resolve2(base, path && path.trim() ? path : ".");
|
|
38132
38188
|
const target = requested === base || requested.startsWith(base + sep2) ? requested : base;
|
|
38133
38189
|
try {
|
|
38134
|
-
const dirents = await
|
|
38190
|
+
const dirents = await readdir3(target, { withFileTypes: true });
|
|
38135
38191
|
const entries2 = dirents.map((d) => ({
|
|
38136
38192
|
name: d.name,
|
|
38137
38193
|
type: d.isDirectory() ? "dir" : "file"
|
|
@@ -38199,7 +38255,7 @@ async function writeWorkerFile(root, relPath, content) {
|
|
|
38199
38255
|
}
|
|
38200
38256
|
|
|
38201
38257
|
// src/core/fs-mutate.ts
|
|
38202
|
-
import { mkdir as mkdir4, writeFile as writeFile4, rename, rm, stat } from "fs/promises";
|
|
38258
|
+
import { mkdir as mkdir4, writeFile as writeFile4, rename as rename2, rm as rm2, stat } from "fs/promises";
|
|
38203
38259
|
import { basename as basename3, dirname as dirname5, resolve as resolve5, sep as sep5 } from "path";
|
|
38204
38260
|
var MAX_BYTES3 = 2 * 1024 * 1024;
|
|
38205
38261
|
function resolveInRoot3(root, rel) {
|
|
@@ -38254,14 +38310,14 @@ async function mutateFs(root, req) {
|
|
|
38254
38310
|
if (to !== resolve5(root) && !to.startsWith(resolve5(root) + sep5)) return escaped(req.to ?? "");
|
|
38255
38311
|
if (await exists(to)) return { ok: false, path: to, error: "destination already exists" };
|
|
38256
38312
|
await mkdir4(dirname5(to), { recursive: true });
|
|
38257
|
-
await
|
|
38313
|
+
await rename2(from, to);
|
|
38258
38314
|
return { ok: true, path: to };
|
|
38259
38315
|
}
|
|
38260
38316
|
case "delete": {
|
|
38261
38317
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
38262
38318
|
if (!target) return escaped(req.path ?? "");
|
|
38263
38319
|
if (target === resolve5(root)) return { ok: false, path: target, error: "cannot delete the project root" };
|
|
38264
|
-
await
|
|
38320
|
+
await rm2(target, { recursive: true, force: true });
|
|
38265
38321
|
return { ok: true, path: target };
|
|
38266
38322
|
}
|
|
38267
38323
|
default:
|
|
@@ -38486,7 +38542,7 @@ ${stderr}`;
|
|
|
38486
38542
|
|
|
38487
38543
|
// src/core/git-ssh-key.ts
|
|
38488
38544
|
import { execFile as execFile8 } from "child_process";
|
|
38489
|
-
import { readFile as readFile6, rm as
|
|
38545
|
+
import { readFile as readFile6, rm as rm3, mkdir as mkdir6 } from "fs/promises";
|
|
38490
38546
|
import { join as join18 } from "path";
|
|
38491
38547
|
import { promisify as promisify8 } from "util";
|
|
38492
38548
|
var run8 = promisify8(execFile8);
|
|
@@ -38520,8 +38576,8 @@ async function configureGit(key) {
|
|
|
38520
38576
|
async function generate(profileDir2) {
|
|
38521
38577
|
const { dir: dir2, key, pub } = keyPaths(profileDir2);
|
|
38522
38578
|
await mkdir6(dir2, { recursive: true });
|
|
38523
|
-
await
|
|
38524
|
-
await
|
|
38579
|
+
await rm3(key, { force: true });
|
|
38580
|
+
await rm3(pub, { force: true });
|
|
38525
38581
|
await run8("ssh-keygen", ["-t", "ed25519", "-f", key, "-N", "", "-C", "4pm-deploy-key"], {
|
|
38526
38582
|
timeout: 15e3
|
|
38527
38583
|
});
|
|
@@ -38530,8 +38586,8 @@ async function generate(profileDir2) {
|
|
|
38530
38586
|
}
|
|
38531
38587
|
async function remove(profileDir2) {
|
|
38532
38588
|
const { key, pub } = keyPaths(profileDir2);
|
|
38533
|
-
await
|
|
38534
|
-
await
|
|
38589
|
+
await rm3(key, { force: true });
|
|
38590
|
+
await rm3(pub, { force: true });
|
|
38535
38591
|
await run8("git", ["config", "--global", "--unset", "core.sshCommand"], { timeout: 5e3 }).catch(
|
|
38536
38592
|
() => void 0
|
|
38537
38593
|
);
|
|
@@ -38553,7 +38609,7 @@ async function manageSshKey(op, profileDir2) {
|
|
|
38553
38609
|
}
|
|
38554
38610
|
|
|
38555
38611
|
// src/core/git-history.ts
|
|
38556
|
-
import { readdir as
|
|
38612
|
+
import { readdir as readdir4 } from "fs/promises";
|
|
38557
38613
|
import { execFile as execFile9 } from "child_process";
|
|
38558
38614
|
import { resolve as resolve8, sep as sep8 } from "path";
|
|
38559
38615
|
import { promisify as promisify9 } from "util";
|
|
@@ -38594,13 +38650,13 @@ async function gitRepos(root) {
|
|
|
38594
38650
|
};
|
|
38595
38651
|
await add(base);
|
|
38596
38652
|
try {
|
|
38597
|
-
const lvl1 = await
|
|
38653
|
+
const lvl1 = await readdir4(base, { withFileTypes: true });
|
|
38598
38654
|
for (const d of lvl1) {
|
|
38599
38655
|
if (!d.isDirectory() || d.name === ".git" || d.name === "node_modules") continue;
|
|
38600
38656
|
const p1 = resolve8(base, d.name);
|
|
38601
38657
|
await add(p1);
|
|
38602
38658
|
try {
|
|
38603
|
-
const lvl2 = await
|
|
38659
|
+
const lvl2 = await readdir4(p1, { withFileTypes: true });
|
|
38604
38660
|
for (const d2 of lvl2) {
|
|
38605
38661
|
if (!d2.isDirectory() || d2.name === ".git" || d2.name === "node_modules") continue;
|
|
38606
38662
|
await add(resolve8(p1, d2.name));
|
|
@@ -39130,7 +39186,7 @@ function handleToolsChannels(ctx, message, payload) {
|
|
|
39130
39186
|
}
|
|
39131
39187
|
|
|
39132
39188
|
// src/core/agents.ts
|
|
39133
|
-
import { readFile as readFile7, writeFile as writeFile6, mkdir as mkdir7, readdir as
|
|
39189
|
+
import { readFile as readFile7, writeFile as writeFile6, mkdir as mkdir7, readdir as readdir5, rm as rm4 } from "fs/promises";
|
|
39134
39190
|
import { join as join21 } from "path";
|
|
39135
39191
|
var AGENTS_REL = ".claude/agents";
|
|
39136
39192
|
var SKILLS_REL = ".claude/skills";
|
|
@@ -39155,7 +39211,7 @@ async function listAgents(root) {
|
|
|
39155
39211
|
const subagents = [];
|
|
39156
39212
|
const skills = [];
|
|
39157
39213
|
try {
|
|
39158
|
-
for (const e of await
|
|
39214
|
+
for (const e of await readdir5(join21(root, AGENTS_REL), { withFileTypes: true })) {
|
|
39159
39215
|
if (!e.isFile() || !e.name.endsWith(".md") || MEMORY_DIRS.has(e.name)) continue;
|
|
39160
39216
|
const fm = frontmatter(await readText2(join21(root, AGENTS_REL, e.name)));
|
|
39161
39217
|
subagents.push({ name: e.name.replace(/\.md$/, ""), description: fm.description });
|
|
@@ -39163,7 +39219,7 @@ async function listAgents(root) {
|
|
|
39163
39219
|
} catch {
|
|
39164
39220
|
}
|
|
39165
39221
|
try {
|
|
39166
|
-
for (const e of await
|
|
39222
|
+
for (const e of await readdir5(join21(root, SKILLS_REL), { withFileTypes: true })) {
|
|
39167
39223
|
if (!e.isDirectory()) continue;
|
|
39168
39224
|
const fm = frontmatter(await readText2(join21(root, SKILLS_REL, e.name, "SKILL.md")));
|
|
39169
39225
|
skills.push({ name: e.name, description: fm.description });
|
|
@@ -39193,8 +39249,8 @@ async function writeAgent(root, req) {
|
|
|
39193
39249
|
if (!n) return { ok: false, error: "invalid name" };
|
|
39194
39250
|
try {
|
|
39195
39251
|
if (req.action === "delete") {
|
|
39196
|
-
if (req.kind === "subagent") await
|
|
39197
|
-
else await
|
|
39252
|
+
if (req.kind === "subagent") await rm4(itemPath(root, "subagent", n), { force: true });
|
|
39253
|
+
else await rm4(join21(root, SKILLS_REL, n), { recursive: true, force: true });
|
|
39198
39254
|
return { ok: true };
|
|
39199
39255
|
}
|
|
39200
39256
|
if (req.content === void 0) return { ok: false, error: "content is required" };
|
|
@@ -39315,7 +39371,7 @@ async function writeAgentTools(root, scope, permissions) {
|
|
|
39315
39371
|
|
|
39316
39372
|
// src/core/packages.ts
|
|
39317
39373
|
import { createHash as createHash4 } from "crypto";
|
|
39318
|
-
import { readFile as readFile10, writeFile as writeFile9, mkdir as mkdir9, readdir as
|
|
39374
|
+
import { readFile as readFile10, writeFile as writeFile9, mkdir as mkdir9, readdir as readdir6, rm as rm5, stat as stat3 } from "fs/promises";
|
|
39319
39375
|
import { dirname as dirname9, join as join24, relative } from "path";
|
|
39320
39376
|
var AGENTS_REL2 = ".claude/agents";
|
|
39321
39377
|
var SKILLS_REL2 = ".claude/skills";
|
|
@@ -39335,7 +39391,7 @@ async function listFilesRel(dir2) {
|
|
|
39335
39391
|
async function walk2(d) {
|
|
39336
39392
|
let entries2;
|
|
39337
39393
|
try {
|
|
39338
|
-
entries2 = await
|
|
39394
|
+
entries2 = await readdir6(d, { withFileTypes: true });
|
|
39339
39395
|
} catch {
|
|
39340
39396
|
return;
|
|
39341
39397
|
}
|
|
@@ -39488,11 +39544,11 @@ async function removePackage(root, slug) {
|
|
|
39488
39544
|
const entry = manifest[n];
|
|
39489
39545
|
if (!entry) return { ok: false, error: "not installed" };
|
|
39490
39546
|
if (entry.kind === "subagent") {
|
|
39491
|
-
await
|
|
39492
|
-
await
|
|
39493
|
-
await
|
|
39547
|
+
await rm5(join24(root, AGENTS_REL2, `${n}.md`), { force: true });
|
|
39548
|
+
await rm5(join24(root, AGENTS_REL2, "short-memory", `${n}.md`), { force: true });
|
|
39549
|
+
await rm5(join24(root, AGENTS_REL2, "long-memory", `${n}.md`), { force: true });
|
|
39494
39550
|
} else {
|
|
39495
|
-
await
|
|
39551
|
+
await rm5(join24(root, SKILLS_REL2, n), { recursive: true, force: true });
|
|
39496
39552
|
}
|
|
39497
39553
|
delete manifest[n];
|
|
39498
39554
|
await writeManifest(root, manifest);
|
|
@@ -39503,7 +39559,7 @@ async function removePackage(root, slug) {
|
|
|
39503
39559
|
}
|
|
39504
39560
|
|
|
39505
39561
|
// src/core/graph.ts
|
|
39506
|
-
import { readFile as readFile11, readdir as
|
|
39562
|
+
import { readFile as readFile11, readdir as readdir7 } from "fs/promises";
|
|
39507
39563
|
import { join as join25, relative as relative2, dirname as dirname10, resolve as resolve9, extname as extname2, basename as basename6 } from "path";
|
|
39508
39564
|
var MAX_FILES = 2e3;
|
|
39509
39565
|
var MAX_NODES = 3e3;
|
|
@@ -39516,7 +39572,7 @@ async function walk(root, accept) {
|
|
|
39516
39572
|
const dir2 = stack.pop();
|
|
39517
39573
|
let entries2;
|
|
39518
39574
|
try {
|
|
39519
|
-
entries2 = await
|
|
39575
|
+
entries2 = await readdir7(dir2, { withFileTypes: true });
|
|
39520
39576
|
} catch {
|
|
39521
39577
|
continue;
|
|
39522
39578
|
}
|