@moda-ai/cli 1.30.1 → 1.31.1

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/README.md CHANGED
@@ -126,6 +126,11 @@ Prompt management is code-first. Keep prompts in `prompts/**/*.prompt.md` or
126
126
  `prompts/**/*.prompt.json`, sync them with the CLI, and render them through the
127
127
  SDK so LLM spans include prompt metadata.
128
128
 
129
+ JSON definitions can provide `content`, `systemPrompt`, or `messages`. Existing
130
+ files with a nonempty text `template` also work: the CLI uses it as content when
131
+ none of those fields supplies usable text. Sync preserves placeholders and
132
+ leaves the source file unchanged; empty definitions still produce an error.
133
+
129
134
  ```bash
130
135
  moda prompts init
131
136
  moda prompts status
@@ -11,7 +11,7 @@ import {
11
11
  renderStatusHuman,
12
12
  scanHarness,
13
13
  validateHarnessReport
14
- } from "./cli-js4bmw21.js";
14
+ } from "./cli-p3271yp2.js";
15
15
  import {
16
16
  isAuthSessionValid,
17
17
  loadAuthSession
@@ -772,7 +772,19 @@ function parsePromptFile(absPath) {
772
772
  if (absPath.endsWith(".json")) {
773
773
  const parsed = JSON.parse(raw);
774
774
  const key2 = parsed.key || keyFromPath(sourcePath);
775
- const content = parsed.content || JSON.stringify(parsed.messages ?? [], null, 2);
775
+ const hasContent = typeof parsed.content === "string" && parsed.content.trim().length > 0;
776
+ const hasSystemPrompt = typeof parsed.systemPrompt === "string" && parsed.systemPrompt.trim().length > 0;
777
+ const hasTemplate = typeof parsed.template === "string" && parsed.template.trim().length > 0;
778
+ const hasMessages = Array.isArray(parsed.messages) && parsed.messages.some((message) => {
779
+ if (!message || typeof message !== "object" || !("role" in message) || !["system", "developer", "user", "assistant", "tool"].includes(String(message.role)) || !("content" in message))
780
+ return false;
781
+ const content2 = message.content;
782
+ return typeof content2 === "string" ? content2.trim().length > 0 : Array.isArray(content2) && content2.some((part) => part && typeof part === "object" && part.type === "text" && typeof part.text === "string" && part.text.trim().length > 0);
783
+ });
784
+ if (!hasContent && !hasSystemPrompt && !hasMessages && !hasTemplate) {
785
+ throw new Error(`${sourcePath}: prompt JSON requires non-empty content, systemPrompt, messages, or a text template.`);
786
+ }
787
+ const content = hasContent ? parsed.content : hasSystemPrompt ? parsed.systemPrompt : hasMessages ? JSON.stringify(parsed.messages, null, 2) : parsed.template;
776
788
  return normalizePrompt({ ...parsed, key: key2, name: parsed.name || key2, sourcePath, content });
777
789
  }
778
790
  const { frontmatter, body } = parseFrontmatter(raw);
@@ -931,11 +943,18 @@ function printJson(value) {
931
943
 
932
944
  // src/init/rules.ts
933
945
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
946
+ import { join as join3 } from "node:path";
947
+ import { confirm } from "@inquirer/prompts";
948
+
949
+ // src/package-assets.ts
934
950
  import { dirname as dirname2, join as join2 } from "node:path";
935
951
  import { fileURLToPath } from "node:url";
936
- import { confirm } from "@inquirer/prompts";
937
- var PACKAGE_ROOT = join2(dirname2(fileURLToPath(import.meta.url)), "..", "..");
938
- var SKILLS_SOURCE_DIR = join2(PACKAGE_ROOT, "skills");
952
+ function packageAssetPath(...parts) {
953
+ return join2(dirname2(fileURLToPath(import.meta.url)), "..", ...parts);
954
+ }
955
+
956
+ // src/init/rules.ts
957
+ var SKILLS_SOURCE_DIR = packageAssetPath("skills");
939
958
  var SKILLS = [
940
959
  {
941
960
  id: "moda-cli",
@@ -946,7 +965,7 @@ var ADAPTERS = {
946
965
  "Claude Code": {
947
966
  name: "Claude Code",
948
967
  targetFor: (skillId) => ({
949
- dir: join2(".claude", "skills", skillId),
968
+ dir: join3(".claude", "skills", skillId),
950
969
  filename: "SKILL.md"
951
970
  }),
952
971
  transform: (content) => content
@@ -954,7 +973,7 @@ var ADAPTERS = {
954
973
  Cursor: {
955
974
  name: "Cursor",
956
975
  targetFor: (skillId) => ({
957
- dir: join2(".cursor", "rules"),
976
+ dir: join3(".cursor", "rules"),
958
977
  filename: `${skillId}.mdc`
959
978
  }),
960
979
  transform: claudeSkillToCursorRule
@@ -979,7 +998,7 @@ function claudeSkillToCursorRule(skillContent) {
979
998
  ${body.replace(/^\s+/, "")}`;
980
999
  }
981
1000
  function loadSkillSource(skillId) {
982
- const path = join2(SKILLS_SOURCE_DIR, skillId, "SKILL.md");
1001
+ const path = join3(SKILLS_SOURCE_DIR, skillId, "SKILL.md");
983
1002
  if (!existsSync3(path)) {
984
1003
  throw new Error(`Skill source missing: ${path}`);
985
1004
  }
@@ -987,9 +1006,9 @@ function loadSkillSource(skillId) {
987
1006
  }
988
1007
  function installSkillForAgent(skill, adapter, content) {
989
1008
  const { dir, filename } = adapter.targetFor(skill.id);
990
- const dirPath = join2(process.cwd(), dir);
991
- const filePath = join2(dirPath, filename);
992
- const relativePath = join2(dir, filename);
1009
+ const dirPath = join3(process.cwd(), dir);
1010
+ const filePath = join3(dirPath, filename);
1011
+ const relativePath = join3(dir, filename);
993
1012
  try {
994
1013
  mkdirSync2(dirPath, { recursive: true });
995
1014
  if (existsSync3(filePath)) {
@@ -1075,7 +1094,7 @@ async function installRules(detectedAgents, options = {}) {
1075
1094
 
1076
1095
  // src/init/mcp.ts
1077
1096
  import { existsSync as existsSync4 } from "node:fs";
1078
- import { join as join3 } from "node:path";
1097
+ import { join as join4 } from "node:path";
1079
1098
  import { execSync } from "node:child_process";
1080
1099
  function isOnPath(cmd) {
1081
1100
  try {
@@ -1088,10 +1107,10 @@ function isOnPath(cmd) {
1088
1107
  function detectAgents() {
1089
1108
  const cwd = process.cwd();
1090
1109
  const agents = [];
1091
- if (existsSync4(join3(cwd, ".claude")) || isOnPath("claude")) {
1110
+ if (existsSync4(join4(cwd, ".claude")) || isOnPath("claude")) {
1092
1111
  agents.push("Claude Code");
1093
1112
  }
1094
- if (existsSync4(join3(cwd, ".cursor"))) {
1113
+ if (existsSync4(join4(cwd, ".cursor"))) {
1095
1114
  agents.push("Cursor");
1096
1115
  }
1097
1116
  return agents;
@@ -1108,7 +1127,7 @@ import {
1108
1127
  readFileSync as readFileSync5,
1109
1128
  writeFileSync as writeFileSync6
1110
1129
  } from "node:fs";
1111
- import { basename, dirname as dirname5, join as join6, relative as relative3 } from "node:path";
1130
+ import { basename, dirname as dirname5, join as join7, relative as relative3 } from "node:path";
1112
1131
 
1113
1132
  // src/skills-manifest.ts
1114
1133
  import {
@@ -1120,12 +1139,12 @@ import {
1120
1139
  unlinkSync,
1121
1140
  writeFileSync as writeFileSync4
1122
1141
  } from "node:fs";
1123
- import { dirname as dirname3, isAbsolute as isAbsolute4, join as join4, normalize, relative as relative2, resolve as resolve4 } from "node:path";
1124
- var SKILLS_MANIFEST_PATH = join4(".moda", "skills.yml");
1142
+ import { dirname as dirname3, isAbsolute as isAbsolute4, join as join5, normalize, relative as relative2, resolve as resolve4 } from "node:path";
1143
+ var SKILLS_MANIFEST_PATH = join5(".moda", "skills.yml");
1125
1144
  var SKILL_SOURCE_HARNESS = "moda";
1126
1145
  var SKILL_SOURCE_INTEGRATION_BUNDLED = "integration-bundled";
1127
1146
  function readSkillsManifest(cwd) {
1128
- const manifestPath = join4(cwd, SKILLS_MANIFEST_PATH);
1147
+ const manifestPath = join5(cwd, SKILLS_MANIFEST_PATH);
1129
1148
  if (!existsSync5(manifestPath))
1130
1149
  return null;
1131
1150
  let text = "";
@@ -1156,7 +1175,7 @@ function manifestHeaderFrom(manifest) {
1156
1175
  };
1157
1176
  }
1158
1177
  function writeSkillsManifest(cwd, header, records) {
1159
- const manifestPath = join4(cwd, SKILLS_MANIFEST_PATH);
1178
+ const manifestPath = join5(cwd, SKILLS_MANIFEST_PATH);
1160
1179
  const managedPaths = [...new Set(records.flatMap((record) => record.paths))].sort();
1161
1180
  const lines = [
1162
1181
  "version: 1",
@@ -1211,7 +1230,7 @@ function pruneManagedSkillPaths(cwd, managedPaths, log) {
1211
1230
  function pruneEmptyClaudeSkillDir(cwd, relativePath) {
1212
1231
  if (!relativePath.startsWith(".claude/skills/") || !relativePath.endsWith("/SKILL.md"))
1213
1232
  return;
1214
- const dir = dirname3(join4(cwd, relativePath));
1233
+ const dir = dirname3(join5(cwd, relativePath));
1215
1234
  try {
1216
1235
  if (existsSync5(dir) && readdirSync2(dir).length === 0) {
1217
1236
  rmdirSync(dir);
@@ -1331,7 +1350,7 @@ function yamlQuote(value) {
1331
1350
 
1332
1351
  // src/agents-md.ts
1333
1352
  import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "node:fs";
1334
- import { dirname as dirname4, join as join5 } from "node:path";
1353
+ import { dirname as dirname4, join as join6 } from "node:path";
1335
1354
  var AGENTS_MD_FILENAME = "AGENTS.md";
1336
1355
  var FIXES_BLOCK_BEGIN = "<!-- moda:fixes:begin -->";
1337
1356
  var FIXES_BLOCK_END = "<!-- moda:fixes:end -->";
@@ -1392,7 +1411,7 @@ function writeFileIfChanged(filePath, content) {
1392
1411
  }
1393
1412
  }
1394
1413
  function upsertFixesAgentsBlock(cwd) {
1395
- const filePath = join5(cwd, AGENTS_MD_FILENAME);
1414
+ const filePath = join6(cwd, AGENTS_MD_FILENAME);
1396
1415
  let existing = null;
1397
1416
  if (existsSync6(filePath)) {
1398
1417
  try {
@@ -1408,7 +1427,7 @@ function upsertFixesAgentsBlock(cwd) {
1408
1427
  // src/skills.ts
1409
1428
  var SKILLS_REQUEST_TIMEOUT_MS = 15000;
1410
1429
  var VALID_STATUSES = new Set(["approved", "proposed", "all"]);
1411
- var SKILL_PROPOSALS_MANIFEST_PATH = join6(".moda", "skill-proposals.yml");
1430
+ var SKILL_PROPOSALS_MANIFEST_PATH = join7(".moda", "skill-proposals.yml");
1412
1431
  async function fetchSkills(status) {
1413
1432
  const apiKey = resolveApiKey();
1414
1433
  if (!apiKey) {
@@ -1578,7 +1597,7 @@ async function pullSkills(options = {}) {
1578
1597
  }
1579
1598
  }
1580
1599
  const agents = detectAgents();
1581
- const writeCursor = agents.includes("Cursor") || existsSync7(join6(cwd, ".cursor"));
1600
+ const writeCursor = agents.includes("Cursor") || existsSync7(join7(cwd, ".cursor"));
1582
1601
  const records = [];
1583
1602
  for (const skill of skills) {
1584
1603
  if (!skill.skill_id || !skill.skill_md) {
@@ -1591,7 +1610,7 @@ async function pullSkills(options = {}) {
1591
1610
  log(` skipped invalid skill_id: ${JSON.stringify(skill.skill_id).slice(0, 80)}`);
1592
1611
  continue;
1593
1612
  }
1594
- const claudePath = join6(cwd, ".claude", "skills", safeId, "SKILL.md");
1613
+ const claudePath = join7(cwd, ".claude", "skills", safeId, "SKILL.md");
1595
1614
  const claudeStatus = writeIfChanged(claudePath, skill.skill_md);
1596
1615
  const claudeRelativePath = `.claude/skills/${safeId}/SKILL.md`;
1597
1616
  recordOutcome(summary, claudeStatus, log, claudeRelativePath);
@@ -1599,7 +1618,7 @@ async function pullSkills(options = {}) {
1599
1618
  if (claudeStatus !== "failed")
1600
1619
  paths.push(claudeRelativePath);
1601
1620
  if (writeCursor) {
1602
- const cursorPath = join6(cwd, ".cursor", "rules", `${safeId}.mdc`);
1621
+ const cursorPath = join7(cwd, ".cursor", "rules", `${safeId}.mdc`);
1603
1622
  const cursorStatus = writeIfChanged(cursorPath, claudeSkillToCursorRule(skill.skill_md));
1604
1623
  const cursorRelativePath = `.cursor/rules/${safeId}.mdc`;
1605
1624
  recordOutcome(summary, cursorStatus, log, cursorRelativePath);
@@ -1637,7 +1656,7 @@ function recordOutcome(summary, status, log, relativePath) {
1637
1656
  break;
1638
1657
  }
1639
1658
  }
1640
- var SKILLS_DIR = join6(".claude", "skills");
1659
+ var SKILLS_DIR = join7(".claude", "skills");
1641
1660
  var SKILL_FILE = "SKILL.md";
1642
1661
  var MAX_SKILL_WALK_DEPTH = 4;
1643
1662
  async function runSkillSync(flags = {}, profileOptions = {}, options = {}) {
@@ -1668,7 +1687,7 @@ async function runSkillSync(flags = {}, profileOptions = {}, options = {}) {
1668
1687
  return { synced: response.synced ?? [], dryRun, ...agentsMd ? { agentsMd } : {} };
1669
1688
  }
1670
1689
  function discoverLocalSkills(cwd) {
1671
- const skillsRoot = join6(cwd, SKILLS_DIR);
1690
+ const skillsRoot = join7(cwd, SKILLS_DIR);
1672
1691
  if (!existsSync7(skillsRoot))
1673
1692
  return [];
1674
1693
  const managed = new Set(managedSkillPaths(cwd));
@@ -1717,7 +1736,7 @@ function walkSkillFiles(dir, depth) {
1717
1736
  }
1718
1737
  const files = [];
1719
1738
  for (const entry of entries) {
1720
- const fullPath = join6(dir, entry);
1739
+ const fullPath = join7(dir, entry);
1721
1740
  let stat;
1722
1741
  try {
1723
1742
  stat = lstatSync2(fullPath);
@@ -1791,7 +1810,7 @@ async function applySkillProposal(proposalId, flags, profileOptions = {}, option
1791
1810
  }
1792
1811
  const safeId = safeSkillId(proposal.skillId);
1793
1812
  const relativePath = `.claude/skills/${safeId}/SKILL.md`;
1794
- const writeStatus = writeIfChanged(join6(cwd, relativePath), proposal.skillMd);
1813
+ const writeStatus = writeIfChanged(join7(cwd, relativePath), proposal.skillMd);
1795
1814
  if (writeStatus === "failed") {
1796
1815
  throw new Error(`Failed to write ${relativePath}`);
1797
1816
  }
@@ -1824,7 +1843,7 @@ function safeSkillId(skillId) {
1824
1843
  return safeId;
1825
1844
  }
1826
1845
  function writeSkillProposalManifest(cwd, proposal, relativePath) {
1827
- const manifestPath = join6(cwd, SKILL_PROPOSALS_MANIFEST_PATH);
1846
+ const manifestPath = join7(cwd, SKILL_PROPOSALS_MANIFEST_PATH);
1828
1847
  mkdirSync5(dirname5(manifestPath), { recursive: true });
1829
1848
  const existing = existsSync7(manifestPath) ? readFileSync5(manifestPath, "utf8") : `applied_proposals:
1830
1849
  `;
@@ -2085,7 +2104,7 @@ function sha2562(input) {
2085
2104
  // src/doctor.ts
2086
2105
  import { createHash as createHash3 } from "node:crypto";
2087
2106
  import { existsSync as existsSync8, readFileSync as readFileSync6, statSync } from "node:fs";
2088
- import { join as join7 } from "node:path";
2107
+ import { join as join8 } from "node:path";
2089
2108
  async function runDoctorCommand(context) {
2090
2109
  const report = await buildDoctorReport(context, {
2091
2110
  offline: context.flags.offline === "true"
@@ -2208,7 +2227,7 @@ async function buildDoctorReport(context, options) {
2208
2227
  checks.push(urlCheck("base_url_valid", "config", "Base URL is valid", baseUrl));
2209
2228
  checks.push(urlCheck("ingest_url_valid", "config", "Ingest URL is valid", ingestUrl));
2210
2229
  checks.push(localBaseUrlCheck(activeProfile.environment, baseUrl));
2211
- const manifestPath = join7(context.cwd, ".moda", "harness.json");
2230
+ const manifestPath = join8(context.cwd, ".moda", "harness.json");
2212
2231
  const manifestExists = existsSync8(manifestPath);
2213
2232
  checks.push({
2214
2233
  id: "harness_manifest_exists",
@@ -2498,7 +2517,7 @@ function harnessSyncStateCheck(cwd) {
2498
2517
  };
2499
2518
  }
2500
2519
  function harnessReportStateCheck(cwd) {
2501
- const reportPath = join7(cwd, HARNESS_REPORT_PATH);
2520
+ const reportPath = join8(cwd, HARNESS_REPORT_PATH);
2502
2521
  if (!existsSync8(reportPath)) {
2503
2522
  return {
2504
2523
  id: "harness_report_approved_synced",
@@ -2592,7 +2611,7 @@ function harnessReportStateCheck(cwd) {
2592
2611
  }
2593
2612
  }
2594
2613
  function readHarnessReportApproval(cwd) {
2595
- const approvalPath = join7(cwd, HARNESS_REPORT_APPROVAL_PATH);
2614
+ const approvalPath = join8(cwd, HARNESS_REPORT_APPROVAL_PATH);
2596
2615
  if (!existsSync8(approvalPath))
2597
2616
  return null;
2598
2617
  try {
@@ -2610,7 +2629,7 @@ function readHarnessReportApproval(cwd) {
2610
2629
  }
2611
2630
  }
2612
2631
  function discoveryCheck(id, group, title, cwd, relativePaths, nextCommand) {
2613
- const found = relativePaths.filter((path) => existsSync8(join7(cwd, path)));
2632
+ const found = relativePaths.filter((path) => existsSync8(join8(cwd, path)));
2614
2633
  return {
2615
2634
  id,
2616
2635
  group,
@@ -2626,7 +2645,7 @@ function sdkInitializationCheck(cwd) {
2626
2645
  const candidates = ["src", "app", "agent_harness"];
2627
2646
  let detected = false;
2628
2647
  for (const candidate of candidates) {
2629
- const path = join7(cwd, candidate);
2648
+ const path = join8(cwd, candidate);
2630
2649
  if (!existsSync8(path))
2631
2650
  continue;
2632
2651
  try {
@@ -2950,4 +2969,4 @@ function isRecord(value) {
2950
2969
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2951
2970
  }
2952
2971
 
2953
- export { DEFAULT_POLL_INTERVAL_MS, DEFAULT_WAIT_TIMEOUT_MS, fetchLatestRun, runPromptsCommand, initPrompts, runPromptSync, claudeSkillToCursorRule, installRules, detectAgents, SKILL_SOURCE_INTEGRATION_BUNDLED, readSkillsManifest, upsertSkillManifestRecord, runSkillSync, runSkillsCommand, runDoctorCommand, runStatusCommand, buildDoctorReport };
2972
+ export { DEFAULT_POLL_INTERVAL_MS, DEFAULT_WAIT_TIMEOUT_MS, fetchLatestRun, runPromptsCommand, initPrompts, runPromptSync, packageAssetPath, claudeSkillToCursorRule, installRules, detectAgents, SKILL_SOURCE_INTEGRATION_BUNDLED, readSkillsManifest, upsertSkillManifestRecord, runSkillSync, runSkillsCommand, runDoctorCommand, runStatusCommand, buildDoctorReport };