pm4ai 0.0.73

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.

Potentially problematic release.


This version of pm4ai might be problematic. Click here for more details.

Files changed (51) hide show
  1. package/README.md +34 -0
  2. package/dist/audit-oQQfgtxr.mjs +287 -0
  3. package/dist/cleanup-M-ALxTqh.mjs +35 -0
  4. package/dist/cli.d.mts +1 -0
  5. package/dist/cli.mjs +77 -0
  6. package/dist/dashboard-DVRNZGun.mjs +39 -0
  7. package/dist/discover-d8ENQC1K.mjs +172 -0
  8. package/dist/fix-BcMN_cuG.mjs +260 -0
  9. package/dist/fix-DvtItv_V.mjs +2 -0
  10. package/dist/guide-BS7-RqpH.d.mts +4 -0
  11. package/dist/guide-CifUmtQN.mjs +59 -0
  12. package/dist/guide.d.mts +2 -0
  13. package/dist/guide.mjs +2 -0
  14. package/dist/ignores-BBl55eUM.mjs +37 -0
  15. package/dist/index.d.mts +83 -0
  16. package/dist/index.mjs +11 -0
  17. package/dist/init-C-073mRX.mjs +120 -0
  18. package/dist/list-QdJPgkEO.mjs +31 -0
  19. package/dist/package-NpIViQjo.mjs +4 -0
  20. package/dist/schemas-Dsbtf6P2.mjs +51 -0
  21. package/dist/schemas.d.mts +48 -0
  22. package/dist/schemas.mjs +2 -0
  23. package/dist/setup-BPuE4oWT.mjs +164 -0
  24. package/dist/status-ByiuW1iF.mjs +2 -0
  25. package/dist/status-CzCNkG58.mjs +1775 -0
  26. package/dist/sync-DN1rgN3P.mjs +732 -0
  27. package/dist/templates/cli/package.json +30 -0
  28. package/dist/templates/cli/src/cli.ts +16 -0
  29. package/dist/templates/cli/src/index.ts +2 -0
  30. package/dist/templates/cli/src/tui.tsx +57 -0
  31. package/dist/templates/cli/tsdown.config.ts +9 -0
  32. package/dist/templates/docs/content/docs/index.mdx +6 -0
  33. package/dist/templates/docs/package.json +20 -0
  34. package/dist/templates/docs/source.config.ts +16 -0
  35. package/dist/templates/docs/src/app/(home)/page.tsx +11 -0
  36. package/dist/templates/lib/package.json +22 -0
  37. package/dist/templates/lib/src/index.ts +2 -0
  38. package/dist/templates/lib/tsdown.config.ts +9 -0
  39. package/dist/templates/root-package.txt +38 -0
  40. package/dist/templates/web/package.json +17 -0
  41. package/dist/templates/web/src/app/page.tsx +6 -0
  42. package/dist/templates/web/src/app/providers.tsx +15 -0
  43. package/dist/utils-CpkOMuQN.mjs +221 -0
  44. package/dist/watch-D4OSFClu.mjs +566 -0
  45. package/dist/watch-emitter-uTmZ3oQd.mjs +177 -0
  46. package/dist/watch-state-DIMHiLr5.d.mts +118 -0
  47. package/dist/watch-state-wF-NfC-k.mjs +274 -0
  48. package/dist/watch-state.d.mts +2 -0
  49. package/dist/watch-state.mjs +2 -0
  50. package/dist/watch-types-BzSNCGb2.mjs +22 -0
  51. package/package.json +65 -0
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # pm4ai
2
+
3
+ Agent-first anti-slop project management for TypeScript monorepos.
4
+
5
+ One source of truth. Zero manual maintenance.
6
+
7
+ ## Quick Start
8
+
9
+ ```sh
10
+ bunx pm4ai@latest status # check current project
11
+ bunx pm4ai@latest fix # sync + maintain current project
12
+ bunx pm4ai@latest status --all # check all projects
13
+ bunx pm4ai@latest fix --all # sync + maintain all projects
14
+ bunx pm4ai@latest init app # scaffold a new project
15
+ bunx pm4ai@latest setup # menubar + daily auto-run
16
+ ```
17
+
18
+ ## How It Works
19
+
20
+ pm4ai is context-aware. Inside a project, it operates on that project only. Use `--all` to scan everything.
21
+
22
+ **`pm4ai status`** checks the current project — git status, config drift, dep audit, CI status, Vercel deployments, background lint check — and reports only issues.
23
+
24
+ **`pm4ai fix`** requires clean git state (no uncommitted changes, up to date with remote). Syncs dotfiles, generates CLAUDE.md from rules, copies readonly/ui, runs maintenance (`sh up.sh`). Shows file change summary after completion.
25
+
26
+ **`pm4ai init`** scaffolds a new project with turbo, lintmax, sherif, simple-git-hooks, CI workflow — everything correct from day one.
27
+
28
+ **`pm4ai setup`** installs a SwiftBar menubar plugin (hourly refresh) and a launchd agent (daily auto-fix).
29
+
30
+ ## For Agents
31
+
32
+ Full docs: `curl https://pm4ai.vercel.app/llms-full.txt`
33
+
34
+ Start prompts for Claude Code sessions: [PM session](prompts/pm.md) · [Project session](prompts/project.md)
@@ -0,0 +1,287 @@
1
+ import { O as FORBIDDEN_PM_PREFIXES, P as REQUIRED_ROOT_DEVDEPS, R as TURBO_FLAG, c as gitCleanRe, d as isSkippedPath, k as LINTMAX_PKG, n as collectWorkspacePackages, r as debug, t as buildPkgDepMap } from "./utils-CpkOMuQN.mjs";
2
+ import { a as npmVersionSchema, n as ghReleaseSchema, o as safeParse } from "./schemas-Dsbtf6P2.mjs";
3
+ import { $ } from "bun";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { dirname, join } from "node:path";
6
+ //#region src/types.ts
7
+ const DEP_FIELDS = ["dependencies", "devDependencies"];
8
+ const ALL_DEP_FIELDS = [
9
+ "dependencies",
10
+ "devDependencies",
11
+ "peerDependencies"
12
+ ];
13
+ //#endregion
14
+ //#region src/audit.ts
15
+ const latestNpmVersionCache = /* @__PURE__ */ new Map();
16
+ const fetchNpmVersion = async (pkg) => {
17
+ const url = `https://registry.npmjs.org/${pkg}/latest`;
18
+ const res = await fetch(url).catch(() => void 0);
19
+ if (!res?.ok) {
20
+ debug("fetch failed:", url);
21
+ return;
22
+ }
23
+ return safeParse(npmVersionSchema, await res.json())?.version;
24
+ };
25
+ const getLatestNpmVersion = async (pkg) => {
26
+ const cached = latestNpmVersionCache.get(pkg);
27
+ if (cached) return cached;
28
+ const p = fetchNpmVersion(pkg);
29
+ latestNpmVersionCache.set(pkg, p);
30
+ const v = await p;
31
+ if (!v) latestNpmVersionCache.delete(pkg);
32
+ return v;
33
+ };
34
+ let latestBunVersionCache;
35
+ const fetchBunVersion = async () => {
36
+ const url = "https://api.github.com/repos/oven-sh/bun/releases/latest";
37
+ const res = await fetch(url).catch(() => void 0);
38
+ if (!res?.ok) {
39
+ debug("fetch failed:", url);
40
+ return;
41
+ }
42
+ return safeParse(ghReleaseSchema, await res.json())?.tag_name.replace("bun-v", "");
43
+ };
44
+ const getLatestBunVersion = async () => {
45
+ if (latestBunVersionCache) return latestBunVersionCache;
46
+ latestBunVersionCache = fetchBunVersion();
47
+ const v = await latestBunVersionCache;
48
+ if (!v) latestBunVersionCache = void 0;
49
+ return v;
50
+ };
51
+ const isPublishedPkg = (pkg) => !pkg.private && Boolean(pkg.name) && Boolean(pkg.exports ?? pkg.main ?? pkg.bin);
52
+ const getDepsFromPkg = (pkg) => {
53
+ const result = /* @__PURE__ */ new Map();
54
+ for (const field of DEP_FIELDS) {
55
+ const deps = pkg[field];
56
+ if (deps) for (const [n, v] of Object.entries(deps)) result.set(n, v);
57
+ }
58
+ return result;
59
+ };
60
+ const checkPackageConventions = (pkgs, projectPath) => {
61
+ const issues = [];
62
+ const filtered = pkgs.filter((p) => !isSkippedPath(p.path) && p.path !== join(projectPath, "package.json"));
63
+ for (const { path: pkgPath, pkg } of filtered) {
64
+ const shortPath = pkgPath.replace(`${projectPath}/`, "");
65
+ if (isPublishedPkg(pkg)) {
66
+ if (pkg.type !== "module") issues.push({
67
+ detail: `${shortPath} should have "type": "module"`,
68
+ type: "drift"
69
+ });
70
+ if (!pkg.files) issues.push({
71
+ detail: `${shortPath} missing "files" field`,
72
+ type: "drift"
73
+ });
74
+ if (!pkg.license) issues.push({
75
+ detail: `${shortPath} missing "license" field`,
76
+ type: "drift"
77
+ });
78
+ if (!pkg.repository) issues.push({
79
+ detail: `${shortPath} missing "repository" field`,
80
+ type: "drift"
81
+ });
82
+ }
83
+ const devDeps = pkg.devDependencies;
84
+ if ((devDeps ? Object.entries(devDeps).filter(([, v]) => !v.startsWith("workspace:")) : []).length > 0) issues.push({
85
+ detail: `${shortPath} devDependencies should be hoisted to root`,
86
+ type: "drift"
87
+ });
88
+ }
89
+ return issues;
90
+ };
91
+ const checkDuplicates = (pkgs, projectPath) => {
92
+ const issues = [];
93
+ const pkgDepsByName = buildPkgDepMap(pkgs);
94
+ const filtered = pkgs.filter((p) => !isSkippedPath(p.path));
95
+ for (const { path: pkgPath, pkg } of filtered) {
96
+ const shortPath = pkgPath.replace(`${projectPath}/`, "");
97
+ const allDeps = [...getDepsFromPkg(pkg)];
98
+ const wsDeps = allDeps.filter(([, v]) => v.startsWith("workspace:")).map(([n]) => n);
99
+ const ownDeps = new Set(allDeps.filter(([, v]) => !v.startsWith("workspace:")).map(([n]) => n));
100
+ const providedByWs = /* @__PURE__ */ new Set();
101
+ for (const ws of wsDeps) for (const d of pkgDepsByName.get(ws) ?? []) providedByWs.add(d);
102
+ const isRoot = pkgPath === join(projectPath, "package.json");
103
+ const isApp = shortPath.startsWith("apps/");
104
+ const duplicated = [...ownDeps].filter((d) => providedByWs.has(d) && !(isRoot && REQUIRED_ROOT_DEVDEPS.includes(d)) && !isApp);
105
+ for (const dep of duplicated) issues.push({
106
+ detail: `${dep} in ${shortPath} already provided by workspace dep`,
107
+ type: "duplicate"
108
+ });
109
+ }
110
+ return issues;
111
+ };
112
+ const usesForbidden = (cmd) => FORBIDDEN_PM_PREFIXES.some((p) => cmd.startsWith(p) || cmd.includes(` && ${p}`) || cmd.includes(` || ${p}`));
113
+ const turboRe = /\bturbo\b/u;
114
+ const checkScripts = (pkgs, projectPath) => {
115
+ const issues = [];
116
+ const rootPkgPath = join(projectPath, "package.json");
117
+ const filtered = pkgs.filter((p) => !isSkippedPath(p.path));
118
+ for (const { path: pkgPath, pkg } of filtered) {
119
+ const shortPath = pkgPath.replace(`${projectPath}/`, "");
120
+ const isRoot = pkgPath === rootPkgPath;
121
+ const scripts = Object.entries(pkg.scripts ?? {});
122
+ for (const [script, cmd] of scripts) {
123
+ if (usesForbidden(cmd)) issues.push({
124
+ detail: `"${script}" uses non-bun pm in ${shortPath}`,
125
+ type: "forbidden"
126
+ });
127
+ if (isRoot && turboRe.test(cmd) && !cmd.includes("--output-logs=errors-only") && !script.startsWith("dev")) issues.push({
128
+ detail: `"${script}" missing ${TURBO_FLAG}`,
129
+ type: "drift"
130
+ });
131
+ if (isRoot && turboRe.test(cmd) && cmd.includes("--output-logs=errors-only") && !script.startsWith("dev") && !cmd.includes("WARNING|Could not resolve workspaces|missing field|Turborepo will still function")) issues.push({
132
+ detail: `"${script}" missing turbo workspace warning filter`,
133
+ type: "drift"
134
+ });
135
+ }
136
+ if (!isRoot && scripts.some(([s]) => s === "clean")) issues.push({
137
+ detail: `${shortPath} has redundant "clean" script, use root clean.sh`,
138
+ type: "drift"
139
+ });
140
+ }
141
+ return issues;
142
+ };
143
+ const checkRootScripts = (rootPkg) => {
144
+ const issues = [];
145
+ const scripts = rootPkg.scripts ?? {};
146
+ if (!scripts.build?.includes("turbo")) issues.push({
147
+ detail: "root \"build\" should use turbo",
148
+ type: "drift"
149
+ });
150
+ if (scripts.check && !scripts.check.includes("lintmax") && !scripts.check.includes("cli.js check")) issues.push({
151
+ detail: "root \"check\" should include \"lintmax check\"",
152
+ type: "drift"
153
+ });
154
+ if (scripts.fix && !scripts.fix.endsWith(`lintmax fix`) && !scripts.fix.endsWith("cli.js fix") && !scripts.fix.endsWith("cli.mjs fix")) issues.push({
155
+ detail: "root \"fix\" should end with \"lintmax fix\"",
156
+ type: "drift"
157
+ });
158
+ return issues;
159
+ };
160
+ const checkRootWorkspacesAndDevDeps = (rootPkg) => {
161
+ const issues = [];
162
+ if (!rootPkg.workspaces || rootPkg.workspaces.length === 0) issues.push({
163
+ detail: "root missing \"workspaces\" field",
164
+ type: "missing"
165
+ });
166
+ const allDeps = {
167
+ ...rootPkg.dependencies,
168
+ ...rootPkg.devDependencies
169
+ };
170
+ for (const dep of REQUIRED_ROOT_DEVDEPS) if (!allDeps[dep]) issues.push({
171
+ detail: `root missing "${dep}" in devDependencies`,
172
+ type: "missing"
173
+ });
174
+ return issues;
175
+ };
176
+ const checkTrustedDeps = (rootPkg, requiredTrusted) => {
177
+ const issues = [];
178
+ const trusted = rootPkg.trustedDependencies ?? [];
179
+ for (const dep of requiredTrusted ?? []) if (!trusted.includes(dep)) issues.push({
180
+ detail: `root missing "${dep}" in trustedDependencies`,
181
+ type: "missing"
182
+ });
183
+ return issues;
184
+ };
185
+ const checkPublishedPkgConventions = (pkgs, projectPath) => {
186
+ const issues = [];
187
+ const published = pkgs.filter((p) => isPublishedPkg(p.pkg));
188
+ for (const { path: pkgPath, pkg } of published) {
189
+ const shortPath = pkgPath.replace(`${projectPath}/`, "");
190
+ if (!pkg.scripts?.postpublish) issues.push({
191
+ detail: `${shortPath} published but missing "postpublish" cleanup`,
192
+ type: "drift"
193
+ });
194
+ if (pkg.scripts?.build && pkg.scripts.build !== "tsdown") issues.push({
195
+ detail: `${shortPath} build must be exactly "tsdown"`,
196
+ type: "drift"
197
+ });
198
+ if (pkg.scripts?.build && pkg.scripts.prepublishOnly !== "bun run build") issues.push({
199
+ detail: `${shortPath} missing "prepublishOnly": "bun run build" — stale dist will ship`,
200
+ type: "drift"
201
+ });
202
+ if (!existsSync(join(dirname(pkgPath), "tsdown.config.ts"))) issues.push({
203
+ detail: `${shortPath} missing tsdown.config.ts`,
204
+ type: "missing"
205
+ });
206
+ }
207
+ return issues;
208
+ };
209
+ const checkAppPackages = (pkgs, projectPath) => {
210
+ const issues = [];
211
+ for (const { path: pkgPath, pkg } of pkgs) {
212
+ const rel = pkgPath.replace(`${projectPath}/`, "");
213
+ if (rel.startsWith("apps/") && !pkg.private) issues.push({
214
+ detail: `${rel} should be private`,
215
+ type: "drift"
216
+ });
217
+ }
218
+ return issues;
219
+ };
220
+ const checkSubPkgScripts = (pkgs, projectPath) => {
221
+ const issues = [];
222
+ const rootPkgPath = join(projectPath, "package.json");
223
+ const filtered = pkgs.filter((p) => !isSkippedPath(p.path) && p.path !== rootPkgPath);
224
+ for (const { path: pkgPath, pkg } of filtered) {
225
+ const shortPath = pkgPath.replace(`${projectPath}/`, "");
226
+ for (const [script, cmd] of Object.entries(pkg.scripts ?? {})) if (gitCleanRe.test(cmd)) issues.push({
227
+ detail: `"${script}" uses git clean in ${shortPath}, use rm -rf`,
228
+ type: "forbidden"
229
+ });
230
+ }
231
+ return issues;
232
+ };
233
+ const audit = async (projectPath) => {
234
+ const issues = [];
235
+ const pkgs = (await collectWorkspacePackages(projectPath)).map((e) => ({
236
+ path: e.path,
237
+ pkg: e.pkg
238
+ }));
239
+ const rootPkg = pkgs[0]?.pkg;
240
+ const bunVersion = rootPkg?.packageManager?.replace("bun@", "");
241
+ if (bunVersion) {
242
+ const latest = await getLatestBunVersion();
243
+ if (latest && bunVersion !== latest) issues.push({
244
+ detail: `${bunVersion} behind latest ${latest}`,
245
+ type: "bun"
246
+ });
247
+ }
248
+ const lintmaxVersion = getDepsFromPkg(rootPkg ?? {}).get(LINTMAX_PKG);
249
+ if (lintmaxVersion && !lintmaxVersion.startsWith("workspace:")) {
250
+ const lintmaxLatest = await getLatestNpmVersion(LINTMAX_PKG);
251
+ if (lintmaxLatest) {
252
+ const resolved = (await $`bun why ${LINTMAX_PKG}`.cwd(projectPath).quiet().nothrow()).stdout.toString().trim();
253
+ if (resolved && !resolved.includes(lintmaxLatest)) issues.push({
254
+ detail: `resolved version behind latest ${lintmaxLatest}`,
255
+ type: LINTMAX_PKG
256
+ });
257
+ }
258
+ }
259
+ if (rootPkg) {
260
+ issues.push(...checkRootScripts(rootPkg));
261
+ issues.push(...checkRootWorkspacesAndDevDeps(rootPkg));
262
+ const selfPkgPath = join(import.meta.dirname, "..", "..", "..", "package.json");
263
+ const selfPkg = existsSync(selfPkgPath) ? JSON.parse(readFileSync(selfPkgPath, "utf8")) : {};
264
+ issues.push(...checkTrustedDeps(rootPkg, selfPkg.trustedDependencies ?? []));
265
+ }
266
+ issues.push(...checkPackageConventions(pkgs, projectPath));
267
+ issues.push(...checkDuplicates(pkgs, projectPath));
268
+ issues.push(...checkScripts(pkgs, projectPath));
269
+ issues.push(...checkPublishedPkgConventions(pkgs, projectPath));
270
+ issues.push(...checkAppPackages(pkgs, projectPath));
271
+ issues.push(...checkSubPkgScripts(pkgs, projectPath));
272
+ const publishedPkgs = pkgs.filter((p) => isPublishedPkg(p.pkg));
273
+ await Promise.all(publishedPkgs.map(async (p) => {
274
+ const r = await $`bun pm view ${p.pkg.name} versions --json`.quiet().nothrow();
275
+ if (r.exitCode !== 0) return;
276
+ try {
277
+ const parsed = JSON.parse(r.stdout.toString());
278
+ if (Array.isArray(parsed) && parsed.length > 1) issues.push({
279
+ detail: `${p.pkg.name} has ${parsed.length} versions published, run cleanup`,
280
+ type: "drift"
281
+ });
282
+ } catch {}
283
+ }));
284
+ return issues;
285
+ };
286
+ //#endregion
287
+ export { DEP_FIELDS as i, isPublishedPkg as n, ALL_DEP_FIELDS as r, audit as t };
@@ -0,0 +1,35 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { spawnSync } from "node:child_process";
4
+ //#region src/cleanup.ts
5
+ const cleanup = () => {
6
+ const pkgPath = join(process.cwd(), "package.json");
7
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
8
+ if (!(pkg.name && pkg.version)) {
9
+ console.error("package.json missing name or version");
10
+ process.exitCode = 1;
11
+ return;
12
+ }
13
+ const result = spawnSync("npm", [
14
+ "view",
15
+ pkg.name,
16
+ "versions",
17
+ "--json"
18
+ ], { encoding: "utf8" });
19
+ if (result.status !== 0) {
20
+ console.log(`${pkg.name}: first publish, nothing to clean`);
21
+ return;
22
+ }
23
+ const versions = JSON.parse(result.stdout);
24
+ const old = (Array.isArray(versions) ? versions : [versions]).filter((v) => v !== pkg.version);
25
+ if (old.length === 0) {
26
+ console.log(`${pkg.name}: no old versions`);
27
+ return;
28
+ }
29
+ for (const v of old) if (spawnSync("npm", ["unpublish", `${pkg.name}@${v}`], {
30
+ encoding: "utf8",
31
+ stdio: "inherit"
32
+ }).status === 0) console.log(`${pkg.name}@${v} unpublished`);
33
+ };
34
+ //#endregion
35
+ export { cleanup };
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env bun
2
+ import { _ as setVerbose } from "./utils-CpkOMuQN.mjs";
3
+ import { t as guide } from "./guide-CifUmtQN.mjs";
4
+ import { t as version } from "./package-NpIViQjo.mjs";
5
+ import { $ } from "bun";
6
+ //#region src/preflight.ts
7
+ const REQUIRED = ["rg", "git"];
8
+ const OPTIONAL = ["gh"];
9
+ const check = async (name) => {
10
+ return (await $`which ${name}`.quiet().nothrow()).exitCode === 0;
11
+ };
12
+ const preflight = async () => {
13
+ const [requiredResults, optionalResults] = await Promise.all([Promise.all(REQUIRED.map(async (name) => ({
14
+ found: await check(name),
15
+ name
16
+ }))), Promise.all(OPTIONAL.map(async (name) => ({
17
+ found: await check(name),
18
+ name
19
+ })))]);
20
+ const missingRequired = requiredResults.filter((r) => !r.found);
21
+ const missingOptional = optionalResults.filter((r) => !r.found);
22
+ if (missingRequired.length > 0) {
23
+ console.log("missing required:");
24
+ for (const r of missingRequired) console.log(` ${r.name}`);
25
+ }
26
+ if (missingOptional.length > 0) {
27
+ console.log("missing optional (some checks skipped):");
28
+ for (const r of missingOptional) console.log(` ${r.name}`);
29
+ }
30
+ return missingRequired.length === 0;
31
+ };
32
+ //#endregion
33
+ //#region src/cli.ts
34
+ const args = process.argv.slice(2);
35
+ const flags = new Set(args.filter((a) => a.startsWith("-")));
36
+ const positional = args.filter((a) => !a.startsWith("-"));
37
+ const command = positional[0];
38
+ const excludes = [...flags].filter((f) => f.startsWith("--exclude=")).flatMap((f) => f.slice(10).split(",")).map((s) => s.trim()).filter(Boolean);
39
+ if (flags.has("--verbose")) setVerbose(true);
40
+ if (flags.has("--version") || flags.has("-v")) console.log(version);
41
+ else if (!command) console.log(guide);
42
+ else if (command === "init") {
43
+ const name = positional[1];
44
+ if (name) {
45
+ const { init } = await import("./init-C-073mRX.mjs");
46
+ await init(name);
47
+ } else console.log("usage: pm4ai init <name>");
48
+ } else if (command === "setup") {
49
+ const { setup } = await import("./setup-BPuE4oWT.mjs");
50
+ await setup();
51
+ } else {
52
+ if (!await preflight()) throw new Error("missing required tools");
53
+ if (command === "status") {
54
+ const { status } = await import("./status-ByiuW1iF.mjs");
55
+ await status(flags.has("--swiftbar"), flags.has("--all"), excludes);
56
+ } else if (command === "fix") {
57
+ const { fix } = await import("./fix-DvtItv_V.mjs");
58
+ await fix(flags.has("--all"), excludes);
59
+ } else if (command === "watch") {
60
+ const { watch } = await import("./watch-D4OSFClu.mjs");
61
+ await watch(flags.has("--json"));
62
+ } else if (command === "dashboard") {
63
+ const { dashboard } = await import("./dashboard-DVRNZGun.mjs");
64
+ await dashboard();
65
+ } else if (command === "ignores") {
66
+ const { ignores } = await import("./ignores-BBl55eUM.mjs");
67
+ await ignores(flags.has("--all"));
68
+ } else if (command === "cleanup") {
69
+ const { cleanup } = await import("./cleanup-M-ALxTqh.mjs");
70
+ cleanup();
71
+ } else if (command === "list") {
72
+ const { list } = await import("./list-QdJPgkEO.mjs");
73
+ await list(excludes);
74
+ } else console.log(guide);
75
+ }
76
+ //#endregion
77
+ export {};
@@ -0,0 +1,39 @@
1
+ import { t as discover } from "./discover-d8ENQC1K.mjs";
2
+ import { existsSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { spawn } from "node:child_process";
5
+ import { randomUUID } from "node:crypto";
6
+ //#region src/dashboard.ts
7
+ const dashboard = async () => {
8
+ const { self } = await discover();
9
+ const dashboardDir = join(self.path, "apps", "web");
10
+ if (!existsSync(dashboardDir)) {
11
+ console.log("dashboard app not found at", dashboardDir);
12
+ console.log("run from pm4ai monorepo or ensure apps/web exists");
13
+ return;
14
+ }
15
+ const token = randomUUID();
16
+ const tokenFile = join(dashboardDir, ".auth-token");
17
+ writeFileSync(tokenFile, token);
18
+ const url = `http://localhost:4200/auth/${token}`;
19
+ console.log(`dashboard: ${url}`);
20
+ console.log("(token is one-time use — copy URL if port-forwarding)");
21
+ const proc = spawn("bun", ["run", "dev"], {
22
+ cwd: dashboardDir,
23
+ stdio: "inherit"
24
+ });
25
+ setTimeout(() => {
26
+ if (process.platform === "darwin") spawn("open", [url], { stdio: "ignore" }).unref();
27
+ }, 2e3);
28
+ await new Promise((resolve, reject) => {
29
+ proc.on("close", (code) => {
30
+ try {
31
+ unlinkSync(tokenFile);
32
+ } catch {}
33
+ if (code === 0) resolve();
34
+ else reject(/* @__PURE__ */ new Error(`dashboard exited with code ${code}`));
35
+ });
36
+ });
37
+ };
38
+ //#endregion
39
+ export { dashboard };
@@ -0,0 +1,172 @@
1
+ import { M as PKG_NAME, f as projectName, r as debug, x as CONFIG_DIR } from "./utils-CpkOMuQN.mjs";
2
+ import { $, file } from "bun";
3
+ import { existsSync, mkdirSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { homedir } from "node:os";
6
+ //#region src/discover.ts
7
+ const hasDirInside = (dir, sub) => existsSync(join(dir, sub));
8
+ const hasLintmaxDep = async (dir) => {
9
+ const pkgFile = file(join(dir, "package.json"));
10
+ if (!await pkgFile.exists()) return false;
11
+ try {
12
+ const pkg = await pkgFile.json();
13
+ return Boolean(pkg.dependencies?.["lintmax"] ?? pkg.devDependencies?.["lintmax"] ?? pkg.peerDependencies?.["lintmax"]);
14
+ } catch {
15
+ return false;
16
+ }
17
+ };
18
+ const isCnsyncRepo = async (dir) => {
19
+ if (!hasDirInside(dir, "readonly/ui")) return false;
20
+ return (await $`git remote get-url origin`.cwd(dir).quiet().nothrow()).stdout.toString().trim().includes(`1qh/cnsync`);
21
+ };
22
+ const cloneIfMissing = async (repo, dest) => {
23
+ if (existsSync(dest)) return dest;
24
+ debug("cloning", repo, "to", dest);
25
+ mkdirSync(dirname(dest), { recursive: true });
26
+ await $`git clone https://github.com/${"1qh"}/${repo}.git ${dest}`.quiet().nothrow();
27
+ return dest;
28
+ };
29
+ const rgExcludes = [
30
+ "-g",
31
+ "!**/node_modules/**",
32
+ "-g",
33
+ "!**/.cache/**",
34
+ "-g",
35
+ "!**/.Trash/**",
36
+ "-g",
37
+ "!**/Library/**",
38
+ "-g",
39
+ "!**/Applications/**",
40
+ "-g",
41
+ "!**/.local/**",
42
+ "-g",
43
+ "!**/.npm/**",
44
+ "-g",
45
+ "!**/.bun/**",
46
+ "-g",
47
+ "!**/.docker/**",
48
+ "-g",
49
+ "!**/iCloud*/**",
50
+ "-g",
51
+ "!**/.git/**"
52
+ ];
53
+ const discover = async (searchRoot, excludes = []) => {
54
+ const home = searchRoot ?? homedir();
55
+ const stdout = (await $`rg --files ${home} -g turbo.json -g turbo.jsonc ${rgExcludes}`.quiet().nothrow()).stdout.toString().trim();
56
+ if (!stdout) debug("rg not found or returned empty");
57
+ const found = stdout.split("\n").filter(Boolean);
58
+ const allDirs = [...new Set(found.map((f) => dirname(f)))].toSorted();
59
+ const turboRoots = allDirs.filter((dir) => !allDirs.some((other) => other !== dir && dir.startsWith(`${other}/`)));
60
+ const projects = (await Promise.all(turboRoots.map(async (dir) => {
61
+ if (!await hasLintmaxDep(dir)) return null;
62
+ const name = projectName(dir);
63
+ const isSelf = name === "pm4ai-monorepo" || name === PKG_NAME;
64
+ if (!isSelf && (excludes.includes(name) || excludes.includes(dir))) return null;
65
+ return {
66
+ isCnsync: await isCnsyncRepo(dir),
67
+ isSelf,
68
+ name,
69
+ path: dir
70
+ };
71
+ }))).filter((p) => p !== null);
72
+ let self = projects.find((p) => p.isSelf);
73
+ let cnsync = projects.find((p) => p.isCnsync);
74
+ const reposDir = join(home, CONFIG_DIR, "repos");
75
+ if (!self) {
76
+ const dest = join(reposDir, PKG_NAME);
77
+ await cloneIfMissing(PKG_NAME, dest);
78
+ self = {
79
+ isCnsync: false,
80
+ isSelf: true,
81
+ name: PKG_NAME,
82
+ path: dest
83
+ };
84
+ }
85
+ if (!cnsync) {
86
+ const dest = join(reposDir, "cnsync");
87
+ await cloneIfMissing("cnsync", dest);
88
+ cnsync = {
89
+ isCnsync: true,
90
+ isSelf: false,
91
+ name: "cnsync",
92
+ path: dest
93
+ };
94
+ }
95
+ const consumers = projects.filter((p) => !(p.isSelf || p.isCnsync));
96
+ return {
97
+ cnsync,
98
+ consumers,
99
+ self
100
+ };
101
+ };
102
+ const discoverSources = async (searchRoot) => {
103
+ const home = searchRoot ?? homedir();
104
+ const reposDir = join(home, CONFIG_DIR, "repos");
105
+ const selfDir = join(reposDir, PKG_NAME);
106
+ const cnsyncDir = join(reposDir, "cnsync");
107
+ let self;
108
+ let cnsync;
109
+ if (existsSync(selfDir)) self = {
110
+ isCnsync: false,
111
+ isSelf: true,
112
+ name: PKG_NAME,
113
+ path: selfDir
114
+ };
115
+ if (existsSync(cnsyncDir)) cnsync = {
116
+ isCnsync: true,
117
+ isSelf: false,
118
+ name: "cnsync",
119
+ path: cnsyncDir
120
+ };
121
+ if (!(self && cnsync)) {
122
+ const found = (await $`rg -l '"${PKG_NAME}"' ${home} -g package.json -g '!**/node_modules/**' -g '!**/.cache/**' --max-count 1`.quiet().nothrow()).stdout.toString().trim().split("\n").filter(Boolean).map((f) => dirname(f));
123
+ if (!self) {
124
+ const dir = found.find((d) => {
125
+ const n = d.split("/").pop();
126
+ return n === PKG_NAME || n === "pm4ai-monorepo";
127
+ });
128
+ if (dir) self = {
129
+ isCnsync: false,
130
+ isSelf: true,
131
+ name: PKG_NAME,
132
+ path: (await $`git rev-parse --show-toplevel`.cwd(dir).quiet().nothrow()).stdout.toString().trim() || dir
133
+ };
134
+ }
135
+ if (!cnsync) {
136
+ const match = (await Promise.all(found.map(async (d) => ({
137
+ d,
138
+ is: await isCnsyncRepo(d)
139
+ })))).find((c) => c.is);
140
+ if (match) cnsync = {
141
+ isCnsync: true,
142
+ isSelf: false,
143
+ name: "cnsync",
144
+ path: match.d
145
+ };
146
+ }
147
+ }
148
+ if (!self) {
149
+ await cloneIfMissing(PKG_NAME, selfDir);
150
+ self = {
151
+ isCnsync: false,
152
+ isSelf: true,
153
+ name: PKG_NAME,
154
+ path: selfDir
155
+ };
156
+ }
157
+ if (!cnsync) {
158
+ await cloneIfMissing("cnsync", cnsyncDir);
159
+ cnsync = {
160
+ isCnsync: true,
161
+ isSelf: false,
162
+ name: "cnsync",
163
+ path: cnsyncDir
164
+ };
165
+ }
166
+ return {
167
+ cnsync,
168
+ self
169
+ };
170
+ };
171
+ //#endregion
172
+ export { discoverSources as n, discover as t };