@dadado/agent-kit-cli 5.3.0 → 5.4.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/dashboard/README.md +25 -0
- package/dashboard/lib/guards.mjs +4 -4
- package/dashboard/lib/triage-heading.mjs +1 -2
- package/dist/index.js +89 -51
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Mission Control runtime
|
|
2
|
+
|
|
3
|
+
The dashboard the CLI serves. Plain ES modules, no build step: `serve.mjs` reads this directory
|
|
4
|
+
directly, and `packages/cli/dashboard/` is a generated copy (gitignored, produced by
|
|
5
|
+
`scripts/sync-cli-dashboard.mjs` at build/prepack time so the npm tarball can ship it).
|
|
6
|
+
|
|
7
|
+
| Path | Role |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `serve.mjs` | HTTP server, auth gate, SSE |
|
|
10
|
+
| `start.mjs` / `start-broadcast.mjs` | loopback and LAN entry points, per-workspace port allocation |
|
|
11
|
+
| `dashboard-data.mjs` | snapshot builder for the panel |
|
|
12
|
+
| `dashboard.html`, `open.html` | panel and share shell |
|
|
13
|
+
| `lib/*.mjs` | guards, semantic model, live refresh, browser open, terminal snapshot |
|
|
14
|
+
| `lib/guards.d.mts` | hand-written types consumed by the CLI package (parity is pinned by a test) |
|
|
15
|
+
|
|
16
|
+
## Where the tests live
|
|
17
|
+
|
|
18
|
+
Tests for these modules are in `packages/cli/src/dashboard/*.test.ts`, not next to the source. The
|
|
19
|
+
CLI package owns the only test runner in the workspace (vitest), and those suites import the `.mjs`
|
|
20
|
+
files directly (`../../../../dashboard/lib/...`) so there is one implementation under test rather
|
|
21
|
+
than a copy. Lint and format are covered from the repository root (`pnpm lint` checks `dashboard/**`
|
|
22
|
+
before it fans out to the workspace packages).
|
|
23
|
+
|
|
24
|
+
`dashboard.html` is outside Biome's scope; CSS/HTML-only changes are covered by
|
|
25
|
+
`packages/cli/src/dashboard/plugin-ux-validation.test.ts` instead.
|
package/dashboard/lib/guards.mjs
CHANGED
|
@@ -47,12 +47,12 @@ export function escapePerlDoubleQuoted(value) {
|
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Resolve the repository root Mission Control should snapshot.
|
|
50
|
-
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>}
|
|
50
|
+
* @param {NodeJS.ProcessEnv | Record<string, string | undefined> | undefined} env - `undefined` falls back to `process.env`
|
|
51
51
|
* @param {string} kitRoot - absolute path to the kit tree (parent of `dashboard/`)
|
|
52
52
|
* @returns {string} absolute snapshot root
|
|
53
53
|
*/
|
|
54
|
-
export function resolveSnapshotRepoRoot(env
|
|
55
|
-
const raw = env?.[REPO_ROOT_ENV];
|
|
54
|
+
export function resolveSnapshotRepoRoot(env, kitRoot) {
|
|
55
|
+
const raw = (env ?? process.env)?.[REPO_ROOT_ENV];
|
|
56
56
|
if (typeof raw === "string" && raw.trim()) {
|
|
57
57
|
return resolve(raw.trim());
|
|
58
58
|
}
|
|
@@ -400,7 +400,7 @@ export function tokensMatch(a, b) {
|
|
|
400
400
|
/**
|
|
401
401
|
* Resolve bind + token gate for Mission Control serve.
|
|
402
402
|
* Non-loopback bind requires a valid MISSION_CONTROL_TOKEN (no warn-only 0.0.0.0).
|
|
403
|
-
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>}
|
|
403
|
+
* @param {NodeJS.ProcessEnv | Record<string, string | undefined> | undefined} env - `undefined` falls back to `process.env`
|
|
404
404
|
* @returns
|
|
405
405
|
* | { ok: true, host: string, tokenRequired: boolean, token: string | null, broadcast: boolean }
|
|
406
406
|
* | { ok: false, error: string }
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
* ## Follow-up plan (also "Followup plan")
|
|
12
12
|
* ## Residuals plan
|
|
13
13
|
*/
|
|
14
|
-
export const TRIAGE_HEADING_RE =
|
|
15
|
-
/^#{2,6}\s+(?:Triage note|Follow-?up plan|Residuals plan)\b/im;
|
|
14
|
+
export const TRIAGE_HEADING_RE = /^#{2,6}\s+(?:Triage note|Follow-?up plan|Residuals plan)\b/im;
|
|
16
15
|
|
|
17
16
|
/** True when markdown carries a durable triage heading. */
|
|
18
17
|
export function hasTriageHeading(text) {
|
package/dist/index.js
CHANGED
|
@@ -214,7 +214,9 @@ var KNOWN_SHIPPED_OVERLAY_HASHES = /* @__PURE__ */ new Set([
|
|
|
214
214
|
"73cd50bac290b84df6245d3647e686dee8ec3b0434736d08fdba6e30d342ef0a",
|
|
215
215
|
"61b635ea8a08171062bd329a4615d7426eb787eb796271aa882f55f39db56810",
|
|
216
216
|
"86afbea8f64de68a79ad5e374f3132bdbe2582b94321fb3d438314838e36c776",
|
|
217
|
-
"65cc1c0293b145e48ed73ad0ca9ab33cbba5ed834a5bfb31f195ed30c2f143df"
|
|
217
|
+
"65cc1c0293b145e48ed73ad0ca9ab33cbba5ed834a5bfb31f195ed30c2f143df",
|
|
218
|
+
"f04fcfe31354d1b09aeb256a17e4aab91c98ea48a5cff25e4a0281af3cfb289f",
|
|
219
|
+
"34e559ad9036d93d9cbc96d394bc2bbeb10ce50ead00d58a914158ae19daa76c"
|
|
218
220
|
]);
|
|
219
221
|
|
|
220
222
|
// src/lifecycle/paths.ts
|
|
@@ -1088,17 +1090,38 @@ async function findPack(rootDir, packId) {
|
|
|
1088
1090
|
}
|
|
1089
1091
|
|
|
1090
1092
|
// src/registry/install.ts
|
|
1091
|
-
import { readFile as readFile5 } from "fs/promises";
|
|
1093
|
+
import { readFile as readFile5, readdir as readdir3 } from "fs/promises";
|
|
1092
1094
|
import path8 from "path";
|
|
1095
|
+
function skillTargetDir(skillPath, skillId) {
|
|
1096
|
+
const category = skillPath.includes("/core/") ? "core" : "community";
|
|
1097
|
+
return path8.posix.join(".cursor", "skills", category, skillId);
|
|
1098
|
+
}
|
|
1099
|
+
async function skillFileTargets(registryRoot, skillPath, skillId) {
|
|
1100
|
+
const targetDir = skillTargetDir(skillPath, skillId);
|
|
1101
|
+
const pair = (rel) => ({
|
|
1102
|
+
sourceRel: path8.posix.join(skillPath, rel),
|
|
1103
|
+
targetRel: path8.posix.join(targetDir, rel)
|
|
1104
|
+
});
|
|
1105
|
+
let companions = [];
|
|
1106
|
+
try {
|
|
1107
|
+
const dirAbs = resolveContained(registryRoot, skillPath);
|
|
1108
|
+
const entries = await readdir3(dirAbs, { withFileTypes: true, recursive: true });
|
|
1109
|
+
companions = entries.filter((entry) => entry.isFile()).map((entry) => {
|
|
1110
|
+
const parent = path8.relative(dirAbs, entry.parentPath ?? dirAbs);
|
|
1111
|
+
return path8.join(parent, entry.name).split(path8.sep).join("/");
|
|
1112
|
+
}).filter((rel) => rel !== "SKILL.md" && !rel.split("/").some((seg) => seg.startsWith("."))).sort();
|
|
1113
|
+
} catch {
|
|
1114
|
+
companions = [];
|
|
1115
|
+
}
|
|
1116
|
+
return [pair("SKILL.md"), ...companions.map(pair)];
|
|
1117
|
+
}
|
|
1093
1118
|
function targetForMember(member) {
|
|
1094
1119
|
switch (member.kind) {
|
|
1095
|
-
case "skill":
|
|
1096
|
-
const category = member.source.includes("/core/") ? "core" : "community";
|
|
1120
|
+
case "skill":
|
|
1097
1121
|
return {
|
|
1098
1122
|
sourceRel: path8.posix.join(member.source, "SKILL.md"),
|
|
1099
|
-
targetRel: path8.posix.join(
|
|
1123
|
+
targetRel: path8.posix.join(skillTargetDir(member.source, member.id), "SKILL.md")
|
|
1100
1124
|
};
|
|
1101
|
-
}
|
|
1102
1125
|
case "rule":
|
|
1103
1126
|
return {
|
|
1104
1127
|
sourceRel: member.source,
|
|
@@ -1139,19 +1162,22 @@ function targetForMember(member) {
|
|
|
1139
1162
|
}
|
|
1140
1163
|
async function installSkill(registryRoot, projectRoot, skill, options = {}) {
|
|
1141
1164
|
const stats = emptyStats();
|
|
1142
|
-
const category = skill.path.includes("/core/") ? "core" : "community";
|
|
1143
|
-
const sourceRel = path8.posix.join(skill.path, "SKILL.md");
|
|
1144
|
-
const targetRel = path8.posix.join(".cursor", "skills", category, skill.id, "SKILL.md");
|
|
1145
1165
|
const managedHashes = await loadManagedHashLedger(projectRoot);
|
|
1146
|
-
const
|
|
1166
|
+
for (const { sourceRel, targetRel } of await skillFileTargets(
|
|
1147
1167
|
registryRoot,
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1168
|
+
skill.path,
|
|
1169
|
+
skill.id
|
|
1170
|
+
)) {
|
|
1171
|
+
const outcome = await copyRegistryFile(
|
|
1172
|
+
registryRoot,
|
|
1173
|
+
projectRoot,
|
|
1174
|
+
sourceRel,
|
|
1175
|
+
targetRel,
|
|
1176
|
+
options.protectedGlobs ?? [],
|
|
1177
|
+
{ managedHashes, persistManagedHashes: false }
|
|
1178
|
+
);
|
|
1179
|
+
recordOutcome(stats, targetRel, outcome);
|
|
1180
|
+
}
|
|
1155
1181
|
await saveManagedHashLedger(projectRoot, managedHashes);
|
|
1156
1182
|
return stats;
|
|
1157
1183
|
}
|
|
@@ -1185,16 +1211,18 @@ async function installPack(registryRoot, projectRoot, packId, options = {}) {
|
|
|
1185
1211
|
const managedHashes = await loadManagedHashLedger(projectRoot);
|
|
1186
1212
|
const copyOpts = { managedHashes, persistManagedHashes: false };
|
|
1187
1213
|
for (const member of packManifest.members) {
|
|
1188
|
-
const
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1214
|
+
const pairs = member.kind === "skill" ? await skillFileTargets(registryRoot, member.source, member.id) : [targetForMember(member)];
|
|
1215
|
+
for (const { sourceRel, targetRel } of pairs) {
|
|
1216
|
+
const outcome = await copyRegistryFile(
|
|
1217
|
+
registryRoot,
|
|
1218
|
+
projectRoot,
|
|
1219
|
+
sourceRel,
|
|
1220
|
+
targetRel,
|
|
1221
|
+
protectedGlobs,
|
|
1222
|
+
copyOpts
|
|
1223
|
+
);
|
|
1224
|
+
recordOutcome(stats, targetRel, outcome);
|
|
1225
|
+
}
|
|
1198
1226
|
}
|
|
1199
1227
|
await saveManagedHashLedger(projectRoot, managedHashes);
|
|
1200
1228
|
return stats;
|
|
@@ -1620,8 +1648,10 @@ async function buildRegistryPathMap(registryRoot, manifest) {
|
|
|
1620
1648
|
for (const packId of manifest.packs ?? []) {
|
|
1621
1649
|
const pack = await loadPackManifest(registryRoot, packId);
|
|
1622
1650
|
for (const member of pack.members) {
|
|
1623
|
-
const
|
|
1624
|
-
|
|
1651
|
+
const pairs = member.kind === "skill" ? await skillFileTargets(registryRoot, member.source, member.id) : [packMemberTargets(member)];
|
|
1652
|
+
for (const { sourceRel, targetRel } of pairs) {
|
|
1653
|
+
map.set(targetRel.split(path9.sep).join("/"), sourceRel.split(path9.sep).join("/"));
|
|
1654
|
+
}
|
|
1625
1655
|
}
|
|
1626
1656
|
}
|
|
1627
1657
|
if ((manifest.skills ?? []).length > 0) {
|
|
@@ -1630,25 +1660,28 @@ async function buildRegistryPathMap(registryRoot, manifest) {
|
|
|
1630
1660
|
for (const id of manifest.skills ?? []) {
|
|
1631
1661
|
const skill = pool.find((s) => s.id === id);
|
|
1632
1662
|
if (!skill) continue;
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1663
|
+
for (const { sourceRel, targetRel } of await skillFileTargets(
|
|
1664
|
+
registryRoot,
|
|
1665
|
+
skill.path,
|
|
1666
|
+
skill.id
|
|
1667
|
+
)) {
|
|
1668
|
+
map.set(targetRel, sourceRel);
|
|
1669
|
+
}
|
|
1637
1670
|
}
|
|
1638
1671
|
}
|
|
1639
1672
|
return map;
|
|
1640
1673
|
}
|
|
1641
1674
|
function guessRegistryPath(projectRel) {
|
|
1642
1675
|
const p = projectRel.split(path9.sep).join("/");
|
|
1643
|
-
if (p.startsWith(".cursor/skills/")
|
|
1676
|
+
if (p.startsWith(".cursor/skills/")) {
|
|
1644
1677
|
const rest = p.slice(".cursor/skills/".length);
|
|
1645
1678
|
const parts = rest.split("/");
|
|
1646
|
-
if (parts.length
|
|
1679
|
+
if (parts.length >= 3 && (parts[0] === "core" || parts[0] === "community")) {
|
|
1647
1680
|
return path9.posix.join("registry/skills", rest);
|
|
1648
1681
|
}
|
|
1649
1682
|
const skillId = parts[0];
|
|
1650
|
-
if (parts.length
|
|
1651
|
-
return path9.posix.join("registry/skills", "community",
|
|
1683
|
+
if (parts.length >= 2 && skillId) {
|
|
1684
|
+
return path9.posix.join("registry/skills", "community", rest);
|
|
1652
1685
|
}
|
|
1653
1686
|
}
|
|
1654
1687
|
if (p.startsWith(".cursor/rules/")) {
|
|
@@ -2751,8 +2784,10 @@ async function diffAgainstRegistry(registryRoot, projectRoot, manifest) {
|
|
|
2751
2784
|
for (const packId of manifest.packs ?? []) {
|
|
2752
2785
|
const pack = await loadPackManifest(registryRoot, packId);
|
|
2753
2786
|
for (const member of pack.members) {
|
|
2754
|
-
const
|
|
2755
|
-
|
|
2787
|
+
const pairs = member.kind === "skill" ? await skillFileTargets(registryRoot, member.source, member.id) : [packMemberTargets(member)];
|
|
2788
|
+
for (const { sourceRel, targetRel } of pairs) {
|
|
2789
|
+
await pushUnique(sourceRel, targetRel);
|
|
2790
|
+
}
|
|
2756
2791
|
}
|
|
2757
2792
|
}
|
|
2758
2793
|
if ((manifest.skills ?? []).length > 0) {
|
|
@@ -2764,10 +2799,13 @@ async function diffAgainstRegistry(registryRoot, projectRoot, manifest) {
|
|
|
2764
2799
|
entries.push({ path: `skill:${id}`, status: "missing-registry" });
|
|
2765
2800
|
continue;
|
|
2766
2801
|
}
|
|
2767
|
-
const
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2802
|
+
for (const { sourceRel, targetRel } of await skillFileTargets(
|
|
2803
|
+
registryRoot,
|
|
2804
|
+
skill.path,
|
|
2805
|
+
skill.id
|
|
2806
|
+
)) {
|
|
2807
|
+
await pushUnique(sourceRel, targetRel);
|
|
2808
|
+
}
|
|
2771
2809
|
}
|
|
2772
2810
|
}
|
|
2773
2811
|
return entries;
|
|
@@ -2843,7 +2881,7 @@ import { defineCommand as defineCommand7 } from "citty";
|
|
|
2843
2881
|
|
|
2844
2882
|
// src/invariants/hooks-health.ts
|
|
2845
2883
|
import { execFile as execFile2 } from "child_process";
|
|
2846
|
-
import { constants as constants2, access as access5, readFile as readFile9, readdir as
|
|
2884
|
+
import { constants as constants2, access as access5, readFile as readFile9, readdir as readdir4, stat as stat2 } from "fs/promises";
|
|
2847
2885
|
import path14 from "path";
|
|
2848
2886
|
import { promisify as promisify2 } from "util";
|
|
2849
2887
|
var execFileAsync2 = promisify2(execFile2);
|
|
@@ -2927,7 +2965,7 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
2927
2965
|
const installHint = "Install or refresh with: `cp git-hooks/<name> .git/hooks/<name> && chmod +x .git/hooks/<name>` (see git-hooks/README.md)";
|
|
2928
2966
|
let names = [...GIT_HOOK_CANONICAL_NAMES];
|
|
2929
2967
|
try {
|
|
2930
|
-
const listed = await
|
|
2968
|
+
const listed = await readdir4(canonicalDir);
|
|
2931
2969
|
const fromDisk = listed.filter(
|
|
2932
2970
|
(n) => GIT_HOOK_CANONICAL_NAMES.includes(n)
|
|
2933
2971
|
);
|
|
@@ -4772,7 +4810,7 @@ var guardCommand = defineCommand8({
|
|
|
4772
4810
|
|
|
4773
4811
|
// src/commands/handoff.ts
|
|
4774
4812
|
import { spawn as spawn4 } from "child_process";
|
|
4775
|
-
import { readFile as readFile13, readdir as
|
|
4813
|
+
import { readFile as readFile13, readdir as readdir5, writeFile as writeFile6 } from "fs/promises";
|
|
4776
4814
|
import path25 from "path";
|
|
4777
4815
|
import { defineCommand as defineCommand9 } from "citty";
|
|
4778
4816
|
function parsePlanFrontmatter(raw) {
|
|
@@ -4791,7 +4829,7 @@ function parsePlanFrontmatter(raw) {
|
|
|
4791
4829
|
}
|
|
4792
4830
|
async function findActivePlan(plansDir) {
|
|
4793
4831
|
if (!await fileExists(plansDir)) return null;
|
|
4794
|
-
const files = (await
|
|
4832
|
+
const files = (await readdir5(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
|
|
4795
4833
|
for (const file of files) {
|
|
4796
4834
|
const raw = await readFile13(path25.join(plansDir, file), "utf8");
|
|
4797
4835
|
const fm = parsePlanFrontmatter(raw);
|
|
@@ -6425,7 +6463,7 @@ import { defineCommand as defineCommand13 } from "citty";
|
|
|
6425
6463
|
|
|
6426
6464
|
// src/invariants/monitors-untriaged.ts
|
|
6427
6465
|
import { execFile as execFile5 } from "child_process";
|
|
6428
|
-
import { readFile as readFile17, readdir as
|
|
6466
|
+
import { readFile as readFile17, readdir as readdir6, stat as stat3 } from "fs/promises";
|
|
6429
6467
|
import path34 from "path";
|
|
6430
6468
|
import { promisify as promisify5 } from "util";
|
|
6431
6469
|
|
|
@@ -6450,7 +6488,7 @@ function hasOpenGaps(content) {
|
|
|
6450
6488
|
}
|
|
6451
6489
|
async function listMonitorFiles(memoryDir) {
|
|
6452
6490
|
try {
|
|
6453
|
-
const names = await
|
|
6491
|
+
const names = await readdir6(memoryDir);
|
|
6454
6492
|
return names.filter((n) => n.startsWith("plan-monitor-") && n.endsWith(".md")).sort();
|
|
6455
6493
|
} catch {
|
|
6456
6494
|
return [];
|
|
@@ -6858,7 +6896,7 @@ function createPersonaBannerPrinter(persona) {
|
|
|
6858
6896
|
}
|
|
6859
6897
|
|
|
6860
6898
|
// src/plan-loop/plan-state.ts
|
|
6861
|
-
import { readFile as readFile18, readdir as
|
|
6899
|
+
import { readFile as readFile18, readdir as readdir7 } from "fs/promises";
|
|
6862
6900
|
import path38 from "path";
|
|
6863
6901
|
function countPendingTodos(raw) {
|
|
6864
6902
|
const lines = raw.split(/\r?\n/);
|
|
@@ -6886,7 +6924,7 @@ function countPendingTodos(raw) {
|
|
|
6886
6924
|
}
|
|
6887
6925
|
async function findActivePlanFile(plansDir) {
|
|
6888
6926
|
if (!await fileExists(plansDir)) return null;
|
|
6889
|
-
const files = (await
|
|
6927
|
+
const files = (await readdir7(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
|
|
6890
6928
|
return files[0] ? path38.join(plansDir, files[0]) : null;
|
|
6891
6929
|
}
|
|
6892
6930
|
async function readPlan(planPath) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dadado/agent-kit-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
4
4
|
"description": "Agent Kit CLI: HITL framework install and tooling for AI-assisted IDEs (rules, skills, plan/handoff, context).",
|
|
5
5
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
6
6
|
"type": "module",
|