@moda-ai/cli 1.55.21 → 1.55.23
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/{agent-mj622k8s.js → agent-f8a1kejk.js} +8 -9
- package/dist/{cli-569hj0bj.js → cli-2cggp8ww.js} +1 -1
- package/dist/{cli-a5c9te95.js → cli-4f6jkrnm.js} +5 -5
- package/dist/cli-4yarp5s1.js +61 -0
- package/dist/{cli-4j8nffsm.js → cli-54zhv6cx.js} +57 -28
- package/dist/{cli-sf0g0fjg.js → cli-8dtnwp9r.js} +152 -57
- package/dist/{cli-9x9adrtj.js → cli-9g8vz4sv.js} +1 -1
- package/dist/{cli-cehskjde.js → cli-cv3jt1ms.js} +5 -7
- package/dist/{cli-033n2mpb.js → cli-d2zews6c.js} +6 -9
- package/dist/{cli-38p87ykc.js → cli-frhm2wz7.js} +1 -1
- package/dist/{cli-wf7gfa1e.js → cli-s6tw1fje.js} +8 -10
- package/dist/{cli-5ke3xe6y.js → cli-t3a6eknn.js} +26 -1
- package/dist/{cli-rqk6f6n6.js → cli-wkmdw40s.js} +24 -5
- package/dist/{cli-pc5cw7mx.js → cli-z3g07t65.js} +5 -5
- package/dist/cli.js +50 -35
- package/dist/{doctor-mx6jf435.js → doctor-snb87jvy.js} +8 -9
- package/dist/{fix-9fmy6p2x.js → fix-fr48gr0w.js} +6 -7
- package/dist/{harness-github-actions-c826bfxs.js → harness-github-actions-cfkx9ty9.js} +5 -6
- package/dist/{harness-xvg6ws6x.js → harness-m63b6s0k.js} +5 -6
- package/dist/{index-ewqbpnwz.js → index-nsaph4as.js} +58 -22
- package/dist/{production-intelligence-dbe9dmc5.js → production-intelligence-fwjh572m.js} +8 -7
- package/dist/{profile-commands-kmmd9y86.js → profile-commands-548ts402.js} +7 -48
- package/dist/prompt-source-mcp.js +5 -6
- package/dist/{prompts-1tkhrxkq.js → prompts-p66y7wep.js} +6 -7
- package/dist/{provision-wzttap58.js → provision-r9fhrvpw.js} +5 -4
- package/dist/{registry-atqyb5fa.js → registry-n7dsdxmg.js} +3 -4
- package/dist/{skills-bundled-r9a2vs07.js → skills-bundled-dq20spzk.js} +2 -2
- package/dist/skills-c17gg69y.js +18 -0
- package/dist/{sync-25vn78je.js → sync-bne90xvj.js} +8 -9
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/dist/cli-7ea02wca.js +0 -30
- package/dist/skills-br82cpt7.js +0 -19
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildDoctorReport
|
|
3
|
-
} from "./cli-
|
|
4
|
-
import"./cli-
|
|
5
|
-
import"./cli-
|
|
3
|
+
} from "./cli-z3g07t65.js";
|
|
4
|
+
import"./cli-9g8vz4sv.js";
|
|
5
|
+
import"./cli-s6tw1fje.js";
|
|
6
6
|
import"./cli-mm48br00.js";
|
|
7
7
|
import {
|
|
8
8
|
HARNESS_REPORT_APPROVAL_PATH,
|
|
@@ -14,19 +14,18 @@ import {
|
|
|
14
14
|
readHarnessSyncState,
|
|
15
15
|
summarizeHarness,
|
|
16
16
|
validateHarnessReport
|
|
17
|
-
} from "./cli-
|
|
17
|
+
} from "./cli-4f6jkrnm.js";
|
|
18
18
|
import"./cli-97fnztpy.js";
|
|
19
|
-
import"./cli-
|
|
20
|
-
import"./cli-
|
|
21
|
-
import"./cli-
|
|
22
|
-
import"./cli-7ea02wca.js";
|
|
19
|
+
import"./cli-2cggp8ww.js";
|
|
20
|
+
import"./cli-cv3jt1ms.js";
|
|
21
|
+
import"./cli-wkmdw40s.js";
|
|
23
22
|
import {
|
|
24
23
|
CliInputError,
|
|
25
24
|
resolveApiKey,
|
|
26
25
|
resolveIngestUrl,
|
|
27
26
|
resolveModaBaseUrl,
|
|
28
27
|
resolveTenantId
|
|
29
|
-
} from "./cli-
|
|
28
|
+
} from "./cli-t3a6eknn.js";
|
|
30
29
|
import"./cli-0v6na3yp.js";
|
|
31
30
|
|
|
32
31
|
// src/agent.ts
|
|
@@ -4,16 +4,16 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
createCommandContext,
|
|
6
6
|
stringOption
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-2cggp8ww.js";
|
|
8
8
|
import {
|
|
9
9
|
assertFlags,
|
|
10
10
|
assertPositionals,
|
|
11
11
|
requireTenant
|
|
12
|
-
} from "./cli-
|
|
12
|
+
} from "./cli-cv3jt1ms.js";
|
|
13
13
|
import {
|
|
14
14
|
ApiError,
|
|
15
15
|
callControlAPI
|
|
16
|
-
} from "./cli-
|
|
16
|
+
} from "./cli-wkmdw40s.js";
|
|
17
17
|
import {
|
|
18
18
|
CliInputError,
|
|
19
19
|
CliInputRequiredError,
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
resolveModaBaseUrl,
|
|
40
40
|
resolveTenantId,
|
|
41
41
|
shouldInventoryFile
|
|
42
|
-
} from "./cli-
|
|
42
|
+
} from "./cli-t3a6eknn.js";
|
|
43
43
|
import {
|
|
44
44
|
__commonJS,
|
|
45
45
|
__require,
|
|
@@ -195871,7 +195871,7 @@ async function runHarnessCommand(context) {
|
|
|
195871
195871
|
throw new CliInputError("Focused remote prompt analysis is not supported yet.", "Use local --analyst=claude|codex|cursor|local-scan, or drop --concern for a full remote report.");
|
|
195872
195872
|
}
|
|
195873
195873
|
if (context.flags["github-actions"] === "true") {
|
|
195874
|
-
const { runGithubActionsAnalyze } = await import("./harness-github-actions-
|
|
195874
|
+
const { runGithubActionsAnalyze } = await import("./harness-github-actions-cfkx9ty9.js");
|
|
195875
195875
|
const result = await runGithubActionsAnalyze(rootDir, {
|
|
195876
195876
|
writeReport: (report2) => {
|
|
195877
195877
|
const normalized = normalizeHarnessReport(report2);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
apiKeyHeaders,
|
|
3
|
+
embeddedTenantIdFromApiKey,
|
|
4
|
+
recordApiRequest,
|
|
5
|
+
resolveApiKey,
|
|
6
|
+
resolveApiKeyFromActiveProfile,
|
|
7
|
+
resolveModaBaseUrl
|
|
8
|
+
} from "./cli-t3a6eknn.js";
|
|
9
|
+
|
|
10
|
+
// src/api-key-auth.ts
|
|
11
|
+
var PROBE_TIMEOUT_MS = 1e4;
|
|
12
|
+
function resolveApiKeyCredential(options = {}) {
|
|
13
|
+
const key = resolveApiKey(options);
|
|
14
|
+
if (!key)
|
|
15
|
+
return;
|
|
16
|
+
const env = options.env ?? process.env;
|
|
17
|
+
const source = env.MODA_API_KEY === key ? "env" : resolveApiKeyFromActiveProfile(options) === key ? "profile" : "config";
|
|
18
|
+
return { key, source, tenantId: embeddedTenantIdFromApiKey(key) };
|
|
19
|
+
}
|
|
20
|
+
function redactApiKey(key) {
|
|
21
|
+
if (key.length <= 12)
|
|
22
|
+
return "<redacted>";
|
|
23
|
+
return `${key.slice(0, 8)}...${key.slice(-4)}`;
|
|
24
|
+
}
|
|
25
|
+
async function fetchApiKeyProbe(baseUrl, headers, timeoutMs) {
|
|
26
|
+
const probe = (path) => {
|
|
27
|
+
recordApiRequest();
|
|
28
|
+
return fetch(`${baseUrl}/api/v1/data/${path}`, {
|
|
29
|
+
method: "GET",
|
|
30
|
+
headers,
|
|
31
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
const response = await probe("whoami");
|
|
35
|
+
if (response.status !== 404)
|
|
36
|
+
return response;
|
|
37
|
+
return probe("overview");
|
|
38
|
+
}
|
|
39
|
+
async function probeApiKey(key, options = {}) {
|
|
40
|
+
let response;
|
|
41
|
+
try {
|
|
42
|
+
response = await fetchApiKeyProbe(resolveModaBaseUrl(options), apiKeyHeaders(key, options), PROBE_TIMEOUT_MS);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
45
|
+
return { outcome: "unreachable", reason };
|
|
46
|
+
}
|
|
47
|
+
if (response.status === 401 || response.status === 403) {
|
|
48
|
+
return { outcome: "rejected", status: response.status };
|
|
49
|
+
}
|
|
50
|
+
if (response.status !== 200) {
|
|
51
|
+
return { outcome: "unreachable", reason: `HTTP ${response.status}` };
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
await response.json();
|
|
55
|
+
} catch {
|
|
56
|
+
return { outcome: "unreachable", reason: "non-JSON response from the Data API" };
|
|
57
|
+
}
|
|
58
|
+
return { outcome: "live" };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { resolveApiKeyCredential, redactApiKey, fetchApiKeyProbe, probeApiKey };
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createCommandContext
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-2cggp8ww.js";
|
|
4
4
|
import {
|
|
5
5
|
assertFlags,
|
|
6
6
|
assertPositionals
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-cv3jt1ms.js";
|
|
8
8
|
import {
|
|
9
9
|
callControlAPI
|
|
10
|
-
} from "./cli-
|
|
10
|
+
} from "./cli-wkmdw40s.js";
|
|
11
11
|
import {
|
|
12
12
|
CliInputError,
|
|
13
13
|
recordApiRequest,
|
|
14
14
|
resolveApiKey,
|
|
15
15
|
resolveIngestUrl,
|
|
16
16
|
resolveTenantId
|
|
17
|
-
} from "./cli-
|
|
17
|
+
} from "./cli-t3a6eknn.js";
|
|
18
18
|
import {
|
|
19
19
|
SKILL_SOURCE_HARNESS,
|
|
20
20
|
SKILL_SOURCE_INTEGRATION_BUNDLED,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
pruneManagedSkillPaths,
|
|
24
24
|
readSkillsManifest,
|
|
25
25
|
writeSkillsManifest
|
|
26
|
-
} from "./cli-
|
|
26
|
+
} from "./cli-8dtnwp9r.js";
|
|
27
27
|
import {
|
|
28
28
|
__require
|
|
29
29
|
} from "./cli-0v6na3yp.js";
|
|
@@ -80,27 +80,39 @@ function renderFixesBlock() {
|
|
|
80
80
|
].join(`
|
|
81
81
|
`);
|
|
82
82
|
}
|
|
83
|
+
function countOccurrences(text, needle) {
|
|
84
|
+
return text.split(needle).length - 1;
|
|
85
|
+
}
|
|
83
86
|
function upsertManagedBlock(existing, block) {
|
|
84
87
|
if (existing === null) {
|
|
85
|
-
return `${block}
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
const beginIdx = existing.indexOf(FIXES_BLOCK_BEGIN);
|
|
89
|
-
const endIdx = existing.indexOf(FIXES_BLOCK_END);
|
|
90
|
-
if (beginIdx !== -1 && endIdx !== -1 && endIdx >= beginIdx) {
|
|
91
|
-
const before = existing.slice(0, beginIdx);
|
|
92
|
-
const after = existing.slice(endIdx + FIXES_BLOCK_END.length);
|
|
93
|
-
return `${before}${block}${after}`;
|
|
88
|
+
return { ok: true, content: `${block}
|
|
89
|
+
` };
|
|
94
90
|
}
|
|
95
|
-
const
|
|
91
|
+
const begins = countOccurrences(existing, FIXES_BLOCK_BEGIN);
|
|
92
|
+
const ends = countOccurrences(existing, FIXES_BLOCK_END);
|
|
93
|
+
if (begins === 0 && ends === 0) {
|
|
94
|
+
const separator = existing === "" || existing.endsWith(`
|
|
96
95
|
|
|
97
96
|
`) ? "" : existing.endsWith(`
|
|
98
97
|
`) ? `
|
|
99
98
|
` : `
|
|
100
99
|
|
|
101
100
|
`;
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
return { ok: true, content: `${existing}${separator}${block}
|
|
102
|
+
` };
|
|
103
|
+
}
|
|
104
|
+
const beginIdx = existing.indexOf(FIXES_BLOCK_BEGIN);
|
|
105
|
+
const endIdx = existing.indexOf(FIXES_BLOCK_END);
|
|
106
|
+
if (begins === 1 && ends === 1 && endIdx > beginIdx) {
|
|
107
|
+
const before = existing.slice(0, beginIdx);
|
|
108
|
+
const after = existing.slice(endIdx + FIXES_BLOCK_END.length);
|
|
109
|
+
return { ok: true, content: `${before}${block}${after}` };
|
|
110
|
+
}
|
|
111
|
+
const problem = begins === 1 && ends === 1 ? `its ${FIXES_BLOCK_END} marker comes before its ${FIXES_BLOCK_BEGIN} marker` : `it has ${begins} ${FIXES_BLOCK_BEGIN} and ${ends} ${FIXES_BLOCK_END} marker(s) instead of one of each`;
|
|
112
|
+
return {
|
|
113
|
+
ok: false,
|
|
114
|
+
reason: `The Moda block in ${AGENTS_MD_FILENAME} was not updated because ${problem}. ` + "The file was left unchanged so no text is lost. Keep exactly one begin marker followed by one end marker " + "(or delete the stray markers), then rerun `moda skills sync`."
|
|
115
|
+
};
|
|
104
116
|
}
|
|
105
117
|
function writeFileIfChanged(filePath, content) {
|
|
106
118
|
try {
|
|
@@ -108,14 +120,14 @@ function writeFileIfChanged(filePath, content) {
|
|
|
108
120
|
if (existsSync(filePath)) {
|
|
109
121
|
const current = readFileSync(filePath, "utf8");
|
|
110
122
|
if (current === content)
|
|
111
|
-
return "unchanged";
|
|
123
|
+
return { status: "unchanged" };
|
|
112
124
|
writeFileSync(filePath, content);
|
|
113
|
-
return "updated";
|
|
125
|
+
return { status: "updated" };
|
|
114
126
|
}
|
|
115
127
|
writeFileSync(filePath, content);
|
|
116
|
-
return "wrote";
|
|
117
|
-
} catch {
|
|
118
|
-
return "failed";
|
|
128
|
+
return { status: "wrote" };
|
|
129
|
+
} catch (err) {
|
|
130
|
+
return { status: "failed", error: err instanceof Error ? err.message : String(err) };
|
|
119
131
|
}
|
|
120
132
|
}
|
|
121
133
|
function upsertFixesAgentsBlock(cwd) {
|
|
@@ -124,12 +136,28 @@ function upsertFixesAgentsBlock(cwd) {
|
|
|
124
136
|
if (existsSync(filePath)) {
|
|
125
137
|
try {
|
|
126
138
|
existing = readFileSync(filePath, "utf8");
|
|
127
|
-
} catch {
|
|
128
|
-
return {
|
|
139
|
+
} catch (err) {
|
|
140
|
+
return {
|
|
141
|
+
path: AGENTS_MD_FILENAME,
|
|
142
|
+
status: "failed",
|
|
143
|
+
reason: `Could not read ${AGENTS_MD_FILENAME}: ${err instanceof Error ? err.message : String(err)}`
|
|
144
|
+
};
|
|
129
145
|
}
|
|
130
146
|
}
|
|
131
147
|
const next = upsertManagedBlock(existing, renderFixesBlock());
|
|
132
|
-
|
|
148
|
+
if (!next.ok)
|
|
149
|
+
return { path: AGENTS_MD_FILENAME, status: "skipped", reason: next.reason };
|
|
150
|
+
const written = writeFileIfChanged(filePath, next.content);
|
|
151
|
+
return {
|
|
152
|
+
path: AGENTS_MD_FILENAME,
|
|
153
|
+
status: written.status,
|
|
154
|
+
...written.error ? { reason: `Could not write ${AGENTS_MD_FILENAME}: ${written.error}` } : {}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function agentsMdProblem(agentsMd) {
|
|
158
|
+
if (!agentsMd || agentsMd.status !== "skipped" && agentsMd.status !== "failed")
|
|
159
|
+
return;
|
|
160
|
+
return agentsMd.reason ?? `The Moda block in ${agentsMd.path} could not be updated.`;
|
|
133
161
|
}
|
|
134
162
|
|
|
135
163
|
// src/skills.ts
|
|
@@ -717,11 +745,12 @@ async function runSkillsCommand(input) {
|
|
|
717
745
|
if (sub === "sync") {
|
|
718
746
|
assertFlags("skills sync", flags, ["dry-run"]);
|
|
719
747
|
const result = await runSkillSync(flags, { profile: context.profile, env: context.env });
|
|
748
|
+
const agentsMdWarning = agentsMdProblem(result.agentsMd);
|
|
720
749
|
context.output.writeData({
|
|
721
750
|
synced: result.synced,
|
|
722
751
|
dryRun: result.dryRun,
|
|
723
752
|
...result.agentsMd ? { agentsMd: result.agentsMd } : {}
|
|
724
|
-
});
|
|
753
|
+
}, agentsMdWarning ? { status: "degraded", warnings: [agentsMdWarning] } : {});
|
|
725
754
|
return 0;
|
|
726
755
|
}
|
|
727
756
|
if (sub === "install") {
|
|
@@ -866,7 +895,7 @@ function sha256(input) {
|
|
|
866
895
|
return createHash("sha256").update(input).digest("hex");
|
|
867
896
|
}
|
|
868
897
|
async function runSkillsInstall(context, ids) {
|
|
869
|
-
const { installIntegrationSkill, readBundledIndex, readBundledSkillMd } = await import("./skills-bundled-
|
|
898
|
+
const { installIntegrationSkill, readBundledIndex, readBundledSkillMd } = await import("./skills-bundled-dq20spzk.js");
|
|
870
899
|
const index = readBundledIndex();
|
|
871
900
|
if (context.flags.list === "true" || ids.length === 0) {
|
|
872
901
|
if (!index) {
|
|
@@ -947,4 +976,4 @@ async function runSkillRegistryCommand(sub, positionals, flags, context) {
|
|
|
947
976
|
return 0;
|
|
948
977
|
}
|
|
949
978
|
|
|
950
|
-
export { pullSkills, runSkillSync, runSkillsCommand, parseScopeInt };
|
|
979
|
+
export { agentsMdProblem, pullSkills, runSkillSync, runSkillsCommand, parseScopeInt };
|
|
@@ -7,8 +7,8 @@ function packageAssetPath(...parts) {
|
|
|
7
7
|
|
|
8
8
|
// src/init/rules.ts
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
10
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
-
import { dirname as dirname2, join as join2 } from "node:path";
|
|
10
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { dirname as dirname2, join as join2, sep } from "node:path";
|
|
12
12
|
import { confirm } from "@inquirer/prompts";
|
|
13
13
|
var SKILLS_SOURCE_DIR = packageAssetPath("skills");
|
|
14
14
|
var SKILLS = [
|
|
@@ -93,18 +93,68 @@ function skillVersion(skillContent) {
|
|
|
93
93
|
const frontmatter = FRONTMATTER_RE.exec(skillContent)?.[1] ?? "";
|
|
94
94
|
return /^version:\s*(\S+)\s*$/m.exec(frontmatter)?.[1] ?? "unknown";
|
|
95
95
|
}
|
|
96
|
+
function normalizeEol(text) {
|
|
97
|
+
return text.replace(/\r\n/g, `
|
|
98
|
+
`);
|
|
99
|
+
}
|
|
100
|
+
function matchesRecord(existing, recorded) {
|
|
101
|
+
const hash = recorded?.sha256;
|
|
102
|
+
return typeof hash === "string" && (hash === sha256(existing) || hash === sha256(normalizeEol(existing)));
|
|
103
|
+
}
|
|
96
104
|
function looksLikeShippedSkill(existing, skill) {
|
|
97
|
-
|
|
105
|
+
const text = normalizeEol(existing);
|
|
106
|
+
return text.startsWith(`---
|
|
98
107
|
name: ${skill.id}
|
|
99
|
-
`) || /^# Moda CLI$/m.test(
|
|
108
|
+
`) || /^# Moda CLI$/m.test(text);
|
|
109
|
+
}
|
|
110
|
+
function lstatOrUndefined(path) {
|
|
111
|
+
try {
|
|
112
|
+
return lstatSync(path);
|
|
113
|
+
} catch {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function firstSymlinkedComponent(relativePath) {
|
|
118
|
+
let current = "";
|
|
119
|
+
for (const part of relativePath.split(/[\\/]+/).filter(Boolean)) {
|
|
120
|
+
current = current ? join2(current, part) : part;
|
|
121
|
+
const stat = lstatOrUndefined(join2(process.cwd(), current));
|
|
122
|
+
if (!stat)
|
|
123
|
+
return;
|
|
124
|
+
if (stat.isSymbolicLink())
|
|
125
|
+
return current;
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
100
128
|
}
|
|
101
129
|
function unusedBackupPath(relativePath) {
|
|
102
130
|
let candidate = `${relativePath}.bak`;
|
|
103
|
-
for (let n = 1;
|
|
131
|
+
for (let n = 1;lstatOrUndefined(join2(process.cwd(), candidate)); n++) {
|
|
104
132
|
candidate = `${relativePath}.bak.${n}`;
|
|
105
133
|
}
|
|
106
134
|
return candidate;
|
|
107
135
|
}
|
|
136
|
+
function removeStaleReferences(referencesDir, source, record) {
|
|
137
|
+
const removed = [];
|
|
138
|
+
const kept = [];
|
|
139
|
+
const prefix = `${referencesDir}${sep}`;
|
|
140
|
+
for (const key of Object.keys(record)) {
|
|
141
|
+
if (!key.startsWith(prefix))
|
|
142
|
+
continue;
|
|
143
|
+
const name = key.slice(prefix.length);
|
|
144
|
+
if (source.references.has(name) || !/^[^\\/]+\.md$/.test(name))
|
|
145
|
+
continue;
|
|
146
|
+
const path = join2(process.cwd(), key);
|
|
147
|
+
const stat = lstatOrUndefined(path);
|
|
148
|
+
if (stat?.isFile() && matchesRecord(readFileSync(path, "utf8"), record[key])) {
|
|
149
|
+
unlinkSync(path);
|
|
150
|
+
removed.push(key);
|
|
151
|
+
} else if (stat) {
|
|
152
|
+
kept.push(key);
|
|
153
|
+
}
|
|
154
|
+
delete record[key];
|
|
155
|
+
}
|
|
156
|
+
return { removed, kept };
|
|
157
|
+
}
|
|
108
158
|
function installSkillForAgent(skill, adapter, source, record) {
|
|
109
159
|
const { dir, filename } = adapter.targetFor(skill.id);
|
|
110
160
|
const dirPath = join2(process.cwd(), dir);
|
|
@@ -112,6 +162,10 @@ function installSkillForAgent(skill, adapter, source, record) {
|
|
|
112
162
|
const relativePath = join2(dir, filename);
|
|
113
163
|
const referencesDir = adapter.referencesDirFor(skill.id);
|
|
114
164
|
const content = adapter.transform(source.content, referencesDir);
|
|
165
|
+
const symlinkPath = firstSymlinkedComponent(relativePath) ?? firstSymlinkedComponent(referencesDir);
|
|
166
|
+
if (symlinkPath) {
|
|
167
|
+
return { agent: adapter.name, relativePath, status: "symlink", symlinkPath };
|
|
168
|
+
}
|
|
115
169
|
try {
|
|
116
170
|
mkdirSync(dirPath, { recursive: true });
|
|
117
171
|
let status = "wrote";
|
|
@@ -119,9 +173,9 @@ function installSkillForAgent(skill, adapter, source, record) {
|
|
|
119
173
|
if (existsSync(filePath)) {
|
|
120
174
|
const existing = readFileSync(filePath, "utf8");
|
|
121
175
|
const recorded = record[relativePath];
|
|
122
|
-
if (existing === content) {
|
|
176
|
+
if (normalizeEol(existing) === content) {
|
|
123
177
|
status = "current";
|
|
124
|
-
} else if (recorded ?
|
|
178
|
+
} else if (recorded ? matchesRecord(existing, recorded) : looksLikeShippedSkill(existing, skill)) {
|
|
125
179
|
if (!recorded) {
|
|
126
180
|
backupPath = unusedBackupPath(relativePath);
|
|
127
181
|
writeFileSync(join2(process.cwd(), backupPath), existing);
|
|
@@ -138,12 +192,18 @@ function installSkillForAgent(skill, adapter, source, record) {
|
|
|
138
192
|
const referencesPath = join2(process.cwd(), referencesDir);
|
|
139
193
|
mkdirSync(referencesPath, { recursive: true });
|
|
140
194
|
const keptReferences = [];
|
|
195
|
+
const symlinkedReferences = [];
|
|
141
196
|
for (const [name, text] of source.references) {
|
|
142
197
|
const referencePath = join2(referencesPath, name);
|
|
143
198
|
const referenceKey = join2(referencesDir, name);
|
|
144
|
-
const
|
|
145
|
-
if (
|
|
146
|
-
|
|
199
|
+
const stat = lstatOrUndefined(referencePath);
|
|
200
|
+
if (stat?.isSymbolicLink()) {
|
|
201
|
+
symlinkedReferences.push(referenceKey);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const existing = stat ? readFileSync(referencePath, "utf8") : undefined;
|
|
205
|
+
if (existing === undefined || normalizeEol(existing) !== text) {
|
|
206
|
+
if (existing !== undefined && !matchesRecord(existing, record[referenceKey])) {
|
|
147
207
|
keptReferences.push(referenceKey);
|
|
148
208
|
continue;
|
|
149
209
|
}
|
|
@@ -151,12 +211,16 @@ function installSkillForAgent(skill, adapter, source, record) {
|
|
|
151
211
|
}
|
|
152
212
|
record[referenceKey] = { version, sha256: sha256(text) };
|
|
153
213
|
}
|
|
214
|
+
const stale = removeStaleReferences(referencesDir, source, record);
|
|
154
215
|
return {
|
|
155
216
|
agent: adapter.name,
|
|
156
217
|
relativePath,
|
|
157
218
|
status,
|
|
158
219
|
...backupPath ? { backupPath } : {},
|
|
159
|
-
...keptReferences.length > 0 ? { keptReferences } : {}
|
|
220
|
+
...keptReferences.length > 0 ? { keptReferences } : {},
|
|
221
|
+
...symlinkedReferences.length > 0 ? { symlinkedReferences } : {},
|
|
222
|
+
...stale.removed.length > 0 ? { removedReferences: stale.removed } : {},
|
|
223
|
+
...stale.kept.length > 0 ? { keptStaleReferences: stale.kept } : {}
|
|
160
224
|
};
|
|
161
225
|
} catch (err) {
|
|
162
226
|
return {
|
|
@@ -167,30 +231,59 @@ function installSkillForAgent(skill, adapter, source, record) {
|
|
|
167
231
|
};
|
|
168
232
|
}
|
|
169
233
|
}
|
|
234
|
+
function emptyResult(status, agents) {
|
|
235
|
+
return {
|
|
236
|
+
status,
|
|
237
|
+
agents,
|
|
238
|
+
outcomes: [],
|
|
239
|
+
counts: { written: 0, updated: 0, current: 0, kept: 0, skipped: 0, failed: 0 },
|
|
240
|
+
warnings: [],
|
|
241
|
+
notes: []
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function summarizeInstall(result) {
|
|
245
|
+
const { written, updated, current, kept, skipped, failed } = result.counts;
|
|
246
|
+
return [
|
|
247
|
+
written && `${written} written`,
|
|
248
|
+
updated && `${updated} updated`,
|
|
249
|
+
current && `${current} already current`,
|
|
250
|
+
kept && `${kept} kept (edited locally)`,
|
|
251
|
+
skipped && `${skipped} skipped (symlink)`,
|
|
252
|
+
failed && `${failed} failed`
|
|
253
|
+
].filter(Boolean).join(", ");
|
|
254
|
+
}
|
|
170
255
|
async function installRules(detectedAgents, options = {}) {
|
|
171
256
|
const adapters = detectedAgents.map((a) => ADAPTERS[a]).filter((a) => a !== undefined);
|
|
172
257
|
if (adapters.length === 0) {
|
|
173
258
|
if (!options.silent)
|
|
174
259
|
console.log(" No supported coding agents detected");
|
|
175
|
-
return;
|
|
260
|
+
return emptyResult("skipped", []);
|
|
176
261
|
}
|
|
177
|
-
const
|
|
262
|
+
const agentNames = adapters.map((a) => a.name);
|
|
178
263
|
const skillList = SKILLS.map((s) => `
|
|
179
264
|
• ${s.label}`).join("");
|
|
180
265
|
const shouldInstall = options.yes ? true : await confirm({
|
|
181
|
-
message: `Install Moda skills for ${
|
|
266
|
+
message: `Install Moda skills for ${agentNames.join(", ")}?${skillList}
|
|
182
267
|
Continue?`,
|
|
183
268
|
default: true
|
|
184
269
|
});
|
|
185
270
|
if (!shouldInstall) {
|
|
186
271
|
if (!options.silent)
|
|
187
272
|
console.log(" Skipped");
|
|
188
|
-
return;
|
|
273
|
+
return emptyResult("skipped", agentNames);
|
|
189
274
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
275
|
+
const result = emptyResult("ok", agentNames);
|
|
276
|
+
const { counts, warnings, notes } = result;
|
|
277
|
+
const warn = (message) => {
|
|
278
|
+
warnings.push(message);
|
|
279
|
+
if (!options.silent)
|
|
280
|
+
console.error(` ${message}`);
|
|
281
|
+
};
|
|
282
|
+
const note = (message) => {
|
|
283
|
+
notes.push(message);
|
|
284
|
+
if (!options.silent)
|
|
285
|
+
console.log(` ${message}`);
|
|
286
|
+
};
|
|
194
287
|
const record = readInstallRecord();
|
|
195
288
|
const recordBefore = JSON.stringify(record);
|
|
196
289
|
for (const skill of SKILLS) {
|
|
@@ -198,45 +291,54 @@ async function installRules(detectedAgents, options = {}) {
|
|
|
198
291
|
try {
|
|
199
292
|
source = loadSkillSource(skill.id);
|
|
200
293
|
} catch (err) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
failed += adapters.length;
|
|
294
|
+
warn(`Warning: ${err instanceof Error ? err.message : String(err)}`);
|
|
295
|
+
counts.failed += adapters.length;
|
|
204
296
|
continue;
|
|
205
297
|
}
|
|
206
298
|
for (const adapter of adapters) {
|
|
207
299
|
const outcome = installSkillForAgent(skill, adapter, source, record);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
300
|
+
result.outcomes.push(outcome);
|
|
301
|
+
const agent = outcome.agent;
|
|
302
|
+
for (const kept of outcome.keptReferences ?? []) {
|
|
303
|
+
note(`${agent}: ${kept} was edited locally — skipping (delete it to reinstall)`);
|
|
304
|
+
}
|
|
305
|
+
for (const linked of outcome.symlinkedReferences ?? []) {
|
|
306
|
+
warn(`${agent}: ${linked} is a symlink — skipping so the file it points to is not overwritten`);
|
|
307
|
+
}
|
|
308
|
+
for (const removed of outcome.removedReferences ?? []) {
|
|
309
|
+
note(`${agent}: removed ${removed} (no longer part of the skill)`);
|
|
310
|
+
}
|
|
311
|
+
for (const kept of outcome.keptStaleReferences ?? []) {
|
|
312
|
+
note(`${agent}: ${kept} is no longer part of the skill but was edited locally — leaving it in place`);
|
|
212
313
|
}
|
|
213
314
|
switch (outcome.status) {
|
|
214
315
|
case "wrote":
|
|
215
316
|
if (!options.silent)
|
|
216
|
-
console.log(` ${
|
|
217
|
-
|
|
317
|
+
console.log(` ${agent}: wrote ${outcome.relativePath}`);
|
|
318
|
+
counts.written++;
|
|
218
319
|
break;
|
|
219
320
|
case "updated":
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
321
|
+
if (outcome.backupPath) {
|
|
322
|
+
warn(`${agent}: replaced ${outcome.relativePath} with the current skill (previous copy saved to ${outcome.backupPath})`);
|
|
323
|
+
} else if (!options.silent) {
|
|
324
|
+
console.log(` ${agent}: updated ${outcome.relativePath} to the current skill`);
|
|
223
325
|
}
|
|
224
|
-
updated++;
|
|
326
|
+
counts.updated++;
|
|
225
327
|
break;
|
|
226
328
|
case "current":
|
|
227
|
-
|
|
329
|
+
counts.current++;
|
|
228
330
|
break;
|
|
229
331
|
case "exists":
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
332
|
+
note(`${agent}: ${outcome.relativePath} was edited locally — skipping (delete it to reinstall)`);
|
|
333
|
+
counts.kept++;
|
|
334
|
+
break;
|
|
335
|
+
case "symlink":
|
|
336
|
+
warn(`${agent}: skipped ${outcome.relativePath} because ${outcome.symlinkPath} is a symlink ` + "(install never writes through a link, which could overwrite files outside this project)");
|
|
337
|
+
counts.skipped++;
|
|
234
338
|
break;
|
|
235
339
|
case "failed":
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
failed++;
|
|
340
|
+
warn(`${agent}: failed to write ${outcome.relativePath} — ${outcome.error}`);
|
|
341
|
+
counts.failed++;
|
|
240
342
|
break;
|
|
241
343
|
}
|
|
242
344
|
}
|
|
@@ -245,21 +347,14 @@ async function installRules(detectedAgents, options = {}) {
|
|
|
245
347
|
try {
|
|
246
348
|
writeInstallRecord(record);
|
|
247
349
|
} catch (err) {
|
|
248
|
-
|
|
249
|
-
console.error(` Could not save ${INSTALL_RECORD_PATH} — ${err instanceof Error ? err.message : String(err)}. ` + "A later moda init may overwrite local edits to these skill files.");
|
|
250
|
-
}
|
|
350
|
+
warn(`Could not save ${INSTALL_RECORD_PATH} — ${err instanceof Error ? err.message : String(err)}. ` + "A later moda init may overwrite local edits to these skill files.");
|
|
251
351
|
}
|
|
252
352
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
].filter(Boolean).join(", ");
|
|
259
|
-
if (summary) {
|
|
260
|
-
if (!options.silent)
|
|
261
|
-
console.log(` Done — ${summary}`);
|
|
262
|
-
}
|
|
353
|
+
result.status = counts.failed > 0 ? "failed" : warnings.length > 0 ? "warning" : "ok";
|
|
354
|
+
const summary = summarizeInstall(result);
|
|
355
|
+
if (summary && !options.silent)
|
|
356
|
+
console.log(` Done — ${summary}`);
|
|
357
|
+
return result;
|
|
263
358
|
}
|
|
264
359
|
|
|
265
360
|
// src/init/mcp.ts
|
|
@@ -293,7 +388,7 @@ import {
|
|
|
293
388
|
readdirSync as readdirSync2,
|
|
294
389
|
readFileSync as readFileSync2,
|
|
295
390
|
rmdirSync,
|
|
296
|
-
unlinkSync,
|
|
391
|
+
unlinkSync as unlinkSync2,
|
|
297
392
|
writeFileSync as writeFileSync2
|
|
298
393
|
} from "node:fs";
|
|
299
394
|
import { dirname as dirname3, isAbsolute, join as join4, normalize, relative, resolve } from "node:path";
|
|
@@ -376,7 +471,7 @@ function pruneManagedSkillPaths(cwd, managedPaths, log) {
|
|
|
376
471
|
if (!absolutePath || !existsSync3(absolutePath))
|
|
377
472
|
continue;
|
|
378
473
|
try {
|
|
379
|
-
|
|
474
|
+
unlinkSync2(absolutePath);
|
|
380
475
|
pruned++;
|
|
381
476
|
log(` pruned ${safePath}`);
|
|
382
477
|
pruneEmptyClaudeSkillDir(cwd, safePath);
|
|
@@ -505,4 +600,4 @@ function yamlQuote(value) {
|
|
|
505
600
|
return JSON.stringify(value);
|
|
506
601
|
}
|
|
507
602
|
|
|
508
|
-
export { packageAssetPath, claudeSkillToCursorRule, installRules, detectAgents, SKILL_SOURCE_HARNESS, SKILL_SOURCE_INTEGRATION_BUNDLED, readSkillsManifest, writeSkillsManifest, upsertSkillManifestRecord, pruneManagedSkillPaths };
|
|
603
|
+
export { packageAssetPath, claudeSkillToCursorRule, summarizeInstall, installRules, detectAgents, SKILL_SOURCE_HARNESS, SKILL_SOURCE_INTEGRATION_BUNDLED, readSkillsManifest, writeSkillsManifest, upsertSkillManifestRecord, pruneManagedSkillPaths };
|