@moda-ai/cli 1.38.0 → 1.40.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/README.md +19 -0
- package/dist/{cli-kegd748v.js → cli-dyeywy9d.js} +1 -1
- package/dist/{cli-q79sq80a.js → cli-rp912ypf.js} +163 -21
- package/dist/{cli-3kcy61fr.js → cli-w9an3j54.js} +538 -61
- package/dist/{cli-07tv75te.js → cli-xsmsjny3.js} +696 -139
- package/dist/{cli-k38j2fpq.js → cli-yy8fwg1a.js} +2 -2
- package/dist/cli.js +2522 -263
- package/dist/{harness-n2xgzz31.js → harness-191xt7bq.js} +2 -2
- package/dist/{harness-github-actions-qz5d22rz.js → harness-github-actions-1qt4sgxd.js} +2 -2
- package/dist/{index-hwfe7gs8.js → index-1pb2ftqr.js} +5 -5
- package/dist/prompt-source-mcp.js +2 -2
- package/dist/{provision-64c2n9aw.js → provision-gabtks29.js} +3 -3
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/skills/moda-cli/SKILL.md +239 -20
package/README.md
CHANGED
|
@@ -275,6 +275,25 @@ rendered = moda.prompt("support.triage").render({
|
|
|
275
275
|
})
|
|
276
276
|
```
|
|
277
277
|
|
|
278
|
+
## Push prompts, skills, and tools without a repo scan
|
|
279
|
+
|
|
280
|
+
When the GitHub App or `moda harness analyze` isn't an option, have your coding
|
|
281
|
+
agent write `.moda/registry.json` from the codebase and push it. Only what the
|
|
282
|
+
manifest lists is uploaded. It needs just an API key.
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
moda registry template --out=.moda/registry.json # starter manifest (moda_registry.v1)
|
|
286
|
+
moda registry validate # local validation, no network
|
|
287
|
+
moda registry push --dry-run # server-side preview
|
|
288
|
+
moda registry push # prompts, skills, and tools
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Prompts go to the prompt registry, skills to the skill library (pass `--live`
|
|
292
|
+
or set `"live": true` to serve them), and tools to the tool registry as OpenAI
|
|
293
|
+
function schemas. The format is in the bundled `moda-cli` skill. This fills
|
|
294
|
+
the registries only: the harness graph (agents and how they connect) still
|
|
295
|
+
comes from `moda harness analyze`, the GitHub App, or an approved report.
|
|
296
|
+
|
|
278
297
|
## Anchors and message windows
|
|
279
298
|
|
|
280
299
|
Rows returned by `moda frustrations` and `moda tool-failure-detail` carry
|
|
@@ -254,6 +254,33 @@ function normalizeProfileConfig(raw, options = {}) {
|
|
|
254
254
|
migrations: inspectProfileMigrationState(options)
|
|
255
255
|
}, options);
|
|
256
256
|
}
|
|
257
|
+
function profileBaseUrlOrigin(name, options = {}) {
|
|
258
|
+
const raw = safeReadRecord(getModaPaths(options).profileConfigPath);
|
|
259
|
+
const profiles = raw && typeof raw.profiles === "object" && raw.profiles !== null ? raw.profiles : {};
|
|
260
|
+
let stored;
|
|
261
|
+
for (const [key, value] of Object.entries(profiles)) {
|
|
262
|
+
if (!isRecord(value))
|
|
263
|
+
continue;
|
|
264
|
+
if ((optionalString(value.name) ?? key) === name)
|
|
265
|
+
stored = value;
|
|
266
|
+
}
|
|
267
|
+
const url = optionalString(stored?.base_url);
|
|
268
|
+
if (!url)
|
|
269
|
+
return "default";
|
|
270
|
+
const environment = normalizeEnvironment(stored?.environment);
|
|
271
|
+
return sanitizeProfileBaseUrl(url, environment) === normalizeModaBaseUrl(url) ? "profile" : "default";
|
|
272
|
+
}
|
|
273
|
+
function describeBaseUrlSource(options) {
|
|
274
|
+
const envBase = optionalString(getEnv(options).MODA_BASE_URL);
|
|
275
|
+
if (envBase) {
|
|
276
|
+
const environment = activeProfileEnvironment(options);
|
|
277
|
+
if (!environmentAllowsLoopback(environment) && isLoopbackBaseUrl(normalizeModaBaseUrl(envBase))) {
|
|
278
|
+
return `default (env:MODA_BASE_URL loopback ignored for ${environment} profile)`;
|
|
279
|
+
}
|
|
280
|
+
return "env:MODA_BASE_URL";
|
|
281
|
+
}
|
|
282
|
+
return profileBaseUrlOrigin(options.profile, options);
|
|
283
|
+
}
|
|
257
284
|
function loadProfileConfig(options = {}) {
|
|
258
285
|
const path = getModaPaths(options).profileConfigPath;
|
|
259
286
|
const parsed = safeReadRecord(path);
|
|
@@ -600,6 +627,10 @@ function resolveTenantId(options = {}) {
|
|
|
600
627
|
const env = options.env ?? process.env;
|
|
601
628
|
return env.MODA_TENANT_ID || resolveTenantIdFromActiveProfile(options) || loadCliConfig(options).tenant_id || undefined;
|
|
602
629
|
}
|
|
630
|
+
function resolveApiTenantId(options = {}) {
|
|
631
|
+
const apiKey = resolveApiKey(options);
|
|
632
|
+
return (apiKey ? embeddedTenantIdFromApiKey(apiKey) : undefined) || resolveTenantId(options);
|
|
633
|
+
}
|
|
603
634
|
var MODA_API_KEY = process.env.MODA_API_KEY;
|
|
604
635
|
var MODA_INGEST_URL = process.env.MODA_INGEST_URL || DEFAULT_INGEST_URL;
|
|
605
636
|
function resolveModaBaseUrl(options = {}) {
|
|
@@ -1160,6 +1191,10 @@ function createCliOutput(options) {
|
|
|
1160
1191
|
}
|
|
1161
1192
|
safeWriteStdout(`${JSON.stringify(redactDeep(data), null, 2)}
|
|
1162
1193
|
`);
|
|
1194
|
+
for (const warning of writeOptions.warnings ?? []) {
|
|
1195
|
+
safeWriteStderr(`Warning: ${warning}
|
|
1196
|
+
`);
|
|
1197
|
+
}
|
|
1163
1198
|
},
|
|
1164
1199
|
writeEvent(event) {
|
|
1165
1200
|
if (options.quiet)
|
|
@@ -1242,8 +1277,8 @@ function createCliOutput(options) {
|
|
|
1242
1277
|
}
|
|
1243
1278
|
function createAgentEnvelope(options) {
|
|
1244
1279
|
const safeData = redactDeep(options.data);
|
|
1245
|
-
const
|
|
1246
|
-
const
|
|
1280
|
+
const maxBytes = agentMaxOutputBytes();
|
|
1281
|
+
const build = (bounded) => ({
|
|
1247
1282
|
schema_version: AGENT_SCHEMA_VERSION,
|
|
1248
1283
|
command: options.command,
|
|
1249
1284
|
status: options.status,
|
|
@@ -1255,14 +1290,31 @@ function createAgentEnvelope(options) {
|
|
|
1255
1290
|
recommended_actions: options.recommendedActions ?? [],
|
|
1256
1291
|
next_commands: normalizeNextCommands(options.nextCommands ?? []),
|
|
1257
1292
|
artifacts: options.artifacts ?? [],
|
|
1258
|
-
warnings:
|
|
1293
|
+
warnings: [
|
|
1294
|
+
...options.warnings ?? inferWarnings(safeData),
|
|
1295
|
+
...bounded.warning ? [bounded.warning] : []
|
|
1296
|
+
],
|
|
1259
1297
|
errors: options.errors ?? [],
|
|
1260
1298
|
data: bounded.data,
|
|
1261
1299
|
...options.meta ? { meta: options.meta } : {},
|
|
1262
1300
|
...bounded.truncation ? { truncation: bounded.truncation } : {}
|
|
1263
|
-
};
|
|
1301
|
+
});
|
|
1302
|
+
let envelope = build(boundForAgent(safeData, maxBytes));
|
|
1303
|
+
const envelopeBytes = Buffer.byteLength(JSON.stringify(envelope), "utf8");
|
|
1304
|
+
if (envelopeBytes > maxBytes) {
|
|
1305
|
+
const overhead = envelopeBytes - Buffer.byteLength(JSON.stringify(envelope.data) ?? "", "utf8");
|
|
1306
|
+
envelope = build(boundForAgent(safeData, Math.max(1024, maxBytes - overhead - 1024)));
|
|
1307
|
+
}
|
|
1264
1308
|
return redactDeep(envelope);
|
|
1265
1309
|
}
|
|
1310
|
+
var AGENT_ENVELOPE_RESERVE_BYTES = 2048;
|
|
1311
|
+
function agentEnvelopeFits(command, data, options = {}) {
|
|
1312
|
+
const maxBytes = agentMaxOutputBytes() - AGENT_ENVELOPE_RESERVE_BYTES;
|
|
1313
|
+
if (Buffer.byteLength(JSON.stringify(data) ?? "", "utf8") > maxBytes)
|
|
1314
|
+
return false;
|
|
1315
|
+
const envelope = createAgentEnvelope({ command, status: "ok", data, ...options });
|
|
1316
|
+
return !envelope.truncation && Buffer.byteLength(JSON.stringify(envelope), "utf8") <= maxBytes;
|
|
1317
|
+
}
|
|
1266
1318
|
function createAgentErrorEnvelope(command, error, runId) {
|
|
1267
1319
|
return createAgentEnvelope({
|
|
1268
1320
|
command,
|
|
@@ -1277,7 +1329,7 @@ function createAgentErrorEnvelope(command, error, runId) {
|
|
|
1277
1329
|
nextCommands: (error.suggested_commands ?? []).map((commandText) => ({
|
|
1278
1330
|
command: commandText,
|
|
1279
1331
|
purpose: "Suggested recovery command.",
|
|
1280
|
-
mutability:
|
|
1332
|
+
mutability: recoveryCommandMutability(commandText),
|
|
1281
1333
|
requires_approval: commandText.includes(" --yes") || commandText.includes(" sync")
|
|
1282
1334
|
}))
|
|
1283
1335
|
});
|
|
@@ -1469,8 +1521,15 @@ function normalizeNextCommands(commands) {
|
|
|
1469
1521
|
};
|
|
1470
1522
|
});
|
|
1471
1523
|
}
|
|
1524
|
+
function recoveryCommandMutability(command) {
|
|
1525
|
+
if (command.includes(" sync") || command.startsWith("moda sync"))
|
|
1526
|
+
return "sync";
|
|
1527
|
+
if (/\s--yes(\s|=|$)/.test(command))
|
|
1528
|
+
return "write";
|
|
1529
|
+
return inferMutability(command);
|
|
1530
|
+
}
|
|
1472
1531
|
function inferMutability(command) {
|
|
1473
|
-
if (/\b(sync|approve|init|install|pull|gen|remove|promote)\b/.test(command)) {
|
|
1532
|
+
if (/\b(sync|approve|init|install|pull|gen|remove|promote|delete)\b/.test(command)) {
|
|
1474
1533
|
if (/\bsync\b/.test(command))
|
|
1475
1534
|
return "sync";
|
|
1476
1535
|
return "write";
|
|
@@ -1488,25 +1547,108 @@ function inferWarnings(data) {
|
|
|
1488
1547
|
}
|
|
1489
1548
|
return [];
|
|
1490
1549
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1550
|
+
var AGENT_MAX_STRING_CHARS = 2000;
|
|
1551
|
+
var PROTECTED_STRING_KEY = /(cursor|token|_id$|^id$|url$|href$|^key$|hash$)/i;
|
|
1552
|
+
function agentMaxOutputBytes() {
|
|
1553
|
+
return Number(process.env.MODA_AGENT_MAX_OUTPUT_BYTES ?? 200000);
|
|
1554
|
+
}
|
|
1555
|
+
function boundForAgent(data, maxBytes = agentMaxOutputBytes()) {
|
|
1556
|
+
const sizeOf = (value) => Buffer.byteLength(JSON.stringify(value) ?? "", "utf8");
|
|
1557
|
+
const serialized = JSON.stringify(data) ?? "";
|
|
1558
|
+
const originalBytes = Buffer.byteLength(serialized, "utf8");
|
|
1559
|
+
if (originalBytes <= maxBytes)
|
|
1496
1560
|
return { data };
|
|
1497
|
-
|
|
1561
|
+
let shortenedStrings = 0;
|
|
1562
|
+
const shorten = (value, limit, key) => {
|
|
1563
|
+
if (typeof value === "string") {
|
|
1564
|
+
if (value.length <= limit || key !== undefined && PROTECTED_STRING_KEY.test(key))
|
|
1565
|
+
return value;
|
|
1566
|
+
shortenedStrings += 1;
|
|
1567
|
+
return `${value.slice(0, limit)}… [truncated ${value.length - limit} chars]`;
|
|
1568
|
+
}
|
|
1569
|
+
if (Array.isArray(value))
|
|
1570
|
+
return value.map((item) => shorten(item, limit));
|
|
1571
|
+
if (value && typeof value === "object") {
|
|
1572
|
+
return Object.fromEntries(Object.entries(value).map(([entryKey, item]) => [entryKey, shorten(item, limit, entryKey)]));
|
|
1573
|
+
}
|
|
1574
|
+
return value;
|
|
1575
|
+
};
|
|
1576
|
+
let bounded = data;
|
|
1577
|
+
let boundedBytes = originalBytes;
|
|
1578
|
+
for (const limit of [AGENT_MAX_STRING_CHARS, 500, 200]) {
|
|
1579
|
+
shortenedStrings = 0;
|
|
1580
|
+
bounded = shorten(data, limit);
|
|
1581
|
+
boundedBytes = sizeOf(bounded);
|
|
1582
|
+
if (boundedBytes <= maxBytes)
|
|
1583
|
+
break;
|
|
1584
|
+
}
|
|
1585
|
+
const droppedItems = {};
|
|
1586
|
+
const collectArrays = (value, path, out) => {
|
|
1587
|
+
if (Array.isArray(value)) {
|
|
1588
|
+
if (value.length > 1)
|
|
1589
|
+
out.push({ path, array: value, itemBytes: value.map((item) => sizeOf(item) + 1) });
|
|
1590
|
+
value.forEach((item, index) => {
|
|
1591
|
+
if (item && typeof item === "object")
|
|
1592
|
+
collectArrays(item, `${path}[${index}]`, out);
|
|
1593
|
+
});
|
|
1594
|
+
} else if (value && typeof value === "object") {
|
|
1595
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1596
|
+
if (item && typeof item === "object")
|
|
1597
|
+
collectArrays(item, path ? `${path}.${key}` : key, out);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
for (let round = 0;round < 25 && boundedBytes > maxBytes; round++) {
|
|
1602
|
+
const arrays = [];
|
|
1603
|
+
collectArrays(bounded, "", arrays);
|
|
1604
|
+
const largest = arrays.map((entry) => ({ ...entry, total: entry.itemBytes.reduce((sum, bytes) => sum + bytes, 0) })).sort((left, right) => right.total - left.total)[0];
|
|
1605
|
+
if (!largest)
|
|
1606
|
+
break;
|
|
1607
|
+
let overshoot = boundedBytes - maxBytes;
|
|
1608
|
+
let drop = 0;
|
|
1609
|
+
for (let index = largest.array.length - 1;index >= 1 && overshoot > 0; index--) {
|
|
1610
|
+
overshoot -= largest.itemBytes[index];
|
|
1611
|
+
drop += 1;
|
|
1612
|
+
}
|
|
1613
|
+
if (drop === 0)
|
|
1614
|
+
break;
|
|
1615
|
+
largest.array.splice(largest.array.length - drop, drop);
|
|
1616
|
+
const key = largest.path || "(root)";
|
|
1617
|
+
droppedItems[key] = (droppedItems[key] ?? 0) + drop;
|
|
1618
|
+
boundedBytes = sizeOf(bounded);
|
|
1619
|
+
}
|
|
1620
|
+
const dropped = Object.entries(droppedItems);
|
|
1621
|
+
const parts = [
|
|
1622
|
+
shortenedStrings > 0 ? `shortened ${shortenedStrings} long text field(s)` : "",
|
|
1623
|
+
...dropped.map(([path, count]) => `dropped the last ${count} item(s) of data.${path}`)
|
|
1624
|
+
].filter(Boolean);
|
|
1625
|
+
const howToGetMore = "For complete results narrow the query (a smaller --limit, with --offset or --cursor paging where the command supports it), " + "rerun with --json (no byte limit), or raise MODA_AGENT_MAX_OUTPUT_BYTES.";
|
|
1626
|
+
if (boundedBytes > maxBytes) {
|
|
1627
|
+
const stub = { truncated: true, original_bytes: originalBytes, max_bytes: maxBytes, preview: serialized.slice(0, Math.max(0, maxBytes - 256)) };
|
|
1628
|
+
for (let stubBytes = sizeOf(stub);stubBytes > maxBytes && stub.preview.length > 0; stubBytes = sizeOf(stub)) {
|
|
1629
|
+
stub.preview = stub.preview.slice(0, Math.max(0, stub.preview.length - (stubBytes - maxBytes) - 16));
|
|
1630
|
+
}
|
|
1631
|
+
return {
|
|
1632
|
+
data: stub,
|
|
1633
|
+
warning: `Output was ${originalBytes} bytes, over the ${maxBytes}-byte agent limit, and could not be reduced while keeping its structure; data is a string preview. ${howToGetMore}`,
|
|
1634
|
+
truncation: {
|
|
1635
|
+
data: { original_bytes: originalBytes, max_bytes: maxBytes, reason: "agent_output_byte_limit", structure_preserved: false, complete: false }
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1498
1639
|
return {
|
|
1499
|
-
data:
|
|
1500
|
-
|
|
1501
|
-
original_bytes: bytes,
|
|
1502
|
-
max_bytes: maxBytes,
|
|
1503
|
-
preview
|
|
1504
|
-
},
|
|
1640
|
+
data: bounded,
|
|
1641
|
+
warning: `Output was ${originalBytes} bytes, over the ${maxBytes}-byte agent limit: ${parts.join("; ")}. ${howToGetMore}`,
|
|
1505
1642
|
truncation: {
|
|
1506
1643
|
data: {
|
|
1507
|
-
original_bytes:
|
|
1644
|
+
original_bytes: originalBytes,
|
|
1645
|
+
bounded_bytes: boundedBytes,
|
|
1508
1646
|
max_bytes: maxBytes,
|
|
1509
|
-
reason: "agent_output_byte_limit"
|
|
1647
|
+
reason: "agent_output_byte_limit",
|
|
1648
|
+
shortened_strings: shortenedStrings,
|
|
1649
|
+
dropped_items: droppedItems,
|
|
1650
|
+
structure_preserved: true,
|
|
1651
|
+
complete: false
|
|
1510
1652
|
}
|
|
1511
1653
|
}
|
|
1512
1654
|
};
|
|
@@ -1566,4 +1708,4 @@ function createCommandContext(input, options = {}) {
|
|
|
1566
1708
|
};
|
|
1567
1709
|
}
|
|
1568
1710
|
|
|
1569
|
-
export { PROD_BASE_URL, DEFAULT_INGEST_URL, getModaPaths, getModaDir, isLoopbackBaseUrl, assertValidProfileName, loadProfileConfig, saveProfileConfig, getActiveProfile, setActiveProfile, upsertProfile, clearStoredApiKeyCredentials, secretFilePathFromRef, resolveSecretReference, resolveApiKeyFromActiveProfile, resolveTenantIdFromActiveProfile, ensureSecretFilePermissions, writeSecretJsonFile, CliInputError, CliAccessError, CliInputRequiredError, CliAuthError, CliCancellationError, isNetworkLikeError, isApiLikeError, VERSION, MODA_BASE_URL, loadCliConfig, saveCliConfig, resolveApiKey, embeddedTenantIdFromApiKey, resolveIngestUrl2 as resolveIngestUrl, resolveTenantId, resolveModaBaseUrl, validateConfig, MAX_SCANNED_FILE_BYTES, DEFAULT_MAX_INVENTORY_FILES, DEFAULT_MAX_INVENTORY_TEXT_BYTES, MAX_RECORDED_SKIPS, SKIPPED_DIRS, isSkippedDir, isSkippedPathPrefix, isEvalLikePath, shouldInventoryFile, isTextFile, isSecretishPath, SECRET_KEYS, redactDeep, redactString, packSnapshot, EXIT_OK, EXIT_ERROR, EXIT_DEGRADED, EXIT_AUTH_REQUIRED, EXIT_INPUT_REQUIRED, EXIT_CANCELLED, AGENT_ERROR_CODES, wasOutputCancelled, safeWriteStdout, safeWriteStderr, resolveOutputMode, createCliOutput, stringOption, createCommandContext };
|
|
1711
|
+
export { PROD_BASE_URL, DEFAULT_INGEST_URL, getModaPaths, getModaDir, isLoopbackBaseUrl, assertValidProfileName, describeBaseUrlSource, loadProfileConfig, saveProfileConfig, getActiveProfile, setActiveProfile, upsertProfile, clearStoredApiKeyCredentials, secretFilePathFromRef, resolveSecretReference, resolveApiKeyFromActiveProfile, resolveTenantIdFromActiveProfile, ensureSecretFilePermissions, writeSecretJsonFile, CliInputError, CliAccessError, CliInputRequiredError, CliAuthError, CliCancellationError, isNetworkLikeError, isApiLikeError, VERSION, MODA_BASE_URL, loadCliConfig, saveCliConfig, resolveApiKey, embeddedTenantIdFromApiKey, resolveIngestUrl2 as resolveIngestUrl, resolveTenantId, resolveApiTenantId, resolveModaBaseUrl, validateConfig, MAX_SCANNED_FILE_BYTES, DEFAULT_MAX_INVENTORY_FILES, DEFAULT_MAX_INVENTORY_TEXT_BYTES, MAX_RECORDED_SKIPS, SKIPPED_DIRS, isSkippedDir, isSkippedPathPrefix, isEvalLikePath, shouldInventoryFile, isTextFile, isSecretishPath, SECRET_KEYS, redactDeep, redactString, packSnapshot, EXIT_OK, EXIT_ERROR, EXIT_DEGRADED, EXIT_AUTH_REQUIRED, EXIT_INPUT_REQUIRED, EXIT_CANCELLED, AGENT_ERROR_CODES, wasOutputCancelled, safeWriteStdout, safeWriteStderr, resolveOutputMode, createCliOutput, agentEnvelopeFits, stringOption, createCommandContext };
|