@hacksmith/doraval 0.2.46 → 0.2.48
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 +16 -0
- package/bin/doraval.js +1533 -725
- package/bin/ui/index.html +156 -5
- package/package.json +1 -1
package/bin/doraval.js
CHANGED
|
@@ -599,7 +599,7 @@ var init_dist = __esm(() => {
|
|
|
599
599
|
var require_package = __commonJS((exports, module) => {
|
|
600
600
|
module.exports = {
|
|
601
601
|
name: "@hacksmith/doraval",
|
|
602
|
-
version: "0.2.
|
|
602
|
+
version: "0.2.48",
|
|
603
603
|
author: "Saif",
|
|
604
604
|
repository: {
|
|
605
605
|
type: "git",
|
|
@@ -873,7 +873,8 @@ var init_skill_validate = __esm(() => {
|
|
|
873
873
|
"agent",
|
|
874
874
|
"hooks",
|
|
875
875
|
"paths",
|
|
876
|
-
"shell"
|
|
876
|
+
"shell",
|
|
877
|
+
"expected-eval"
|
|
877
878
|
]);
|
|
878
879
|
SUPPORTING_DIRS = ["references", "scripts", "assets", "examples"];
|
|
879
880
|
OPTIONAL_DIRS = ["references", "scripts", "assets"];
|
|
@@ -1164,85 +1165,452 @@ Check that the path points to a skill directory containing SKILL.md.`);
|
|
|
1164
1165
|
});
|
|
1165
1166
|
});
|
|
1166
1167
|
|
|
1167
|
-
// src/
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1168
|
+
// src/core/session-parse.ts
|
|
1169
|
+
function extractUserText(message) {
|
|
1170
|
+
if (typeof message === "string")
|
|
1171
|
+
return message.trim() || null;
|
|
1172
|
+
if (Array.isArray(message)) {
|
|
1173
|
+
for (const block of message) {
|
|
1174
|
+
if (block && typeof block === "object" && block.type === "text") {
|
|
1175
|
+
const text = block.text;
|
|
1176
|
+
if (typeof text === "string" && text.trim())
|
|
1177
|
+
return text.trim();
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
return null;
|
|
1182
|
+
}
|
|
1183
|
+
function parseSession(jsonlText) {
|
|
1184
|
+
const lines = jsonlText.split(`
|
|
1185
|
+
`).filter((l) => l.trim());
|
|
1186
|
+
const messages = [];
|
|
1187
|
+
for (const line of lines) {
|
|
1188
|
+
try {
|
|
1189
|
+
messages.push(JSON.parse(line));
|
|
1190
|
+
} catch {}
|
|
1191
|
+
}
|
|
1192
|
+
let sessionId = "";
|
|
1193
|
+
let sessionTitle;
|
|
1194
|
+
let model = "unknown";
|
|
1195
|
+
let agent = "claude-code";
|
|
1196
|
+
let cwd = "";
|
|
1197
|
+
let gitBranch;
|
|
1198
|
+
let durationMs;
|
|
1199
|
+
const toolCalls = [];
|
|
1200
|
+
const userMessages = [];
|
|
1201
|
+
let toolIndex = 0;
|
|
1202
|
+
for (const msg of messages) {
|
|
1203
|
+
if (!sessionId && typeof msg.sessionId === "string")
|
|
1204
|
+
sessionId = msg.sessionId;
|
|
1205
|
+
if (!cwd && typeof msg.cwd === "string")
|
|
1206
|
+
cwd = msg.cwd;
|
|
1207
|
+
if (!gitBranch && typeof msg.gitBranch === "string")
|
|
1208
|
+
gitBranch = msg.gitBranch;
|
|
1209
|
+
if (msg.type === "ai-title") {
|
|
1210
|
+
sessionTitle = typeof msg.aiTitle === "string" ? msg.aiTitle : undefined;
|
|
1211
|
+
}
|
|
1212
|
+
if (msg.type === "system") {
|
|
1213
|
+
if (typeof msg.durationMs === "number")
|
|
1214
|
+
durationMs = msg.durationMs;
|
|
1215
|
+
}
|
|
1216
|
+
if (msg.type === "assistant") {
|
|
1217
|
+
const message = msg.message;
|
|
1218
|
+
if (!message)
|
|
1219
|
+
continue;
|
|
1220
|
+
if (typeof message.model === "string" && message.model !== "<synthetic>") {
|
|
1221
|
+
model = message.model;
|
|
1222
|
+
agent = typeof msg.entrypoint === "string" ? msg.entrypoint === "cli" ? "claude-code" : msg.entrypoint : "claude-code";
|
|
1223
|
+
}
|
|
1224
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
1225
|
+
for (const block of content) {
|
|
1226
|
+
if (!block || typeof block !== "object")
|
|
1227
|
+
continue;
|
|
1228
|
+
const b = block;
|
|
1229
|
+
if (b.type === "tool_use" && typeof b.name === "string") {
|
|
1230
|
+
const input = b.input ?? {};
|
|
1231
|
+
toolCalls.push({
|
|
1232
|
+
name: b.name,
|
|
1233
|
+
input,
|
|
1234
|
+
timestamp: typeof msg.timestamp === "string" ? msg.timestamp : "",
|
|
1235
|
+
index: toolIndex++
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
if (msg.type === "user") {
|
|
1241
|
+
const isAttachment = typeof msg.attachment !== "undefined";
|
|
1242
|
+
if (isAttachment)
|
|
1243
|
+
continue;
|
|
1244
|
+
const message = msg.message;
|
|
1245
|
+
if (!message)
|
|
1246
|
+
continue;
|
|
1247
|
+
const text = extractUserText(message.content);
|
|
1248
|
+
if (text)
|
|
1249
|
+
userMessages.push(text);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
const skillsInvoked = toolCalls.filter((t) => t.name === "Skill").map((t) => typeof t.input.skill === "string" ? t.input.skill : "unknown").filter((s, i, arr) => arr.indexOf(s) === i);
|
|
1253
|
+
const toolCallCounts = {};
|
|
1254
|
+
for (const t of toolCalls) {
|
|
1255
|
+
toolCallCounts[t.name] = (toolCallCounts[t.name] ?? 0) + 1;
|
|
1256
|
+
}
|
|
1257
|
+
return {
|
|
1258
|
+
sessionId,
|
|
1259
|
+
sessionTitle,
|
|
1260
|
+
model,
|
|
1261
|
+
agent,
|
|
1262
|
+
cwd,
|
|
1263
|
+
gitBranch,
|
|
1264
|
+
toolCalls,
|
|
1265
|
+
toolCallCounts,
|
|
1266
|
+
skillsInvoked,
|
|
1267
|
+
userMessages,
|
|
1268
|
+
userTurnCount: userMessages.length,
|
|
1269
|
+
durationMs
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
function truncateToolCalls(calls, maxCalls) {
|
|
1273
|
+
if (calls.length <= maxCalls)
|
|
1274
|
+
return calls;
|
|
1275
|
+
const skillCalls = calls.filter((c) => c.name === "Skill");
|
|
1276
|
+
const nonSkillCalls = calls.filter((c) => c.name !== "Skill");
|
|
1277
|
+
const budget = Math.max(0, maxCalls - skillCalls.length);
|
|
1278
|
+
if (budget === 0)
|
|
1279
|
+
return skillCalls;
|
|
1280
|
+
const head = nonSkillCalls.slice(0, Math.ceil(budget / 2));
|
|
1281
|
+
const tail = nonSkillCalls.slice(-Math.floor(budget / 2));
|
|
1282
|
+
const headSet = new Set(head.map((c) => c.index));
|
|
1283
|
+
const tailSet = new Set(tail.map((c) => c.index));
|
|
1284
|
+
const selected = new Set([...headSet, ...tailSet, ...skillCalls.map((c) => c.index)]);
|
|
1285
|
+
return calls.filter((c) => selected.has(c.index));
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// src/core/session-adapters.ts
|
|
1289
|
+
import { existsSync as existsSync3, readdirSync, readFileSync, statSync } from "fs";
|
|
1290
|
+
import { homedir } from "os";
|
|
1291
|
+
import { join } from "path";
|
|
1292
|
+
function cwdToProjectHash(cwd) {
|
|
1293
|
+
return cwd.replace(/\//g, "-");
|
|
1294
|
+
}
|
|
1295
|
+
function getAdapter() {
|
|
1296
|
+
return ADAPTERS.find((a) => a.detect()) ?? null;
|
|
1297
|
+
}
|
|
1298
|
+
var claudeCodeAdapter, ADAPTERS;
|
|
1299
|
+
var init_session_adapters = __esm(() => {
|
|
1300
|
+
claudeCodeAdapter = {
|
|
1301
|
+
agent: "claude-code",
|
|
1302
|
+
detect() {
|
|
1303
|
+
return existsSync3(join(homedir(), ".claude"));
|
|
1180
1304
|
},
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
},
|
|
1187
|
-
for
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1305
|
+
findLatestSession(cwd) {
|
|
1306
|
+
const hash = cwdToProjectHash(cwd);
|
|
1307
|
+
const dir = join(homedir(), ".claude", "projects", hash);
|
|
1308
|
+
if (!existsSync3(dir))
|
|
1309
|
+
return null;
|
|
1310
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ name: f, path: join(dir, f), mtime: statSync(join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
1311
|
+
for (const file of files) {
|
|
1312
|
+
const content = readFileSync(file.path, "utf8");
|
|
1313
|
+
if (content.includes('"type":"assistant"') || content.includes('"type": "assistant"')) {
|
|
1314
|
+
return file.path;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
return files[0]?.path ?? null;
|
|
1318
|
+
},
|
|
1319
|
+
listRecentSessions(cwd, limit = 10) {
|
|
1320
|
+
const hash = cwdToProjectHash(cwd);
|
|
1321
|
+
const dir = join(homedir(), ".claude", "projects", hash);
|
|
1322
|
+
if (!existsSync3(dir))
|
|
1323
|
+
return [];
|
|
1324
|
+
const allFiles = readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ name: f, path: join(dir, f), mtime: statSync(join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
1325
|
+
const results = [];
|
|
1326
|
+
for (const file of allFiles) {
|
|
1327
|
+
try {
|
|
1328
|
+
const text = readFileSync(file.path, "utf8");
|
|
1329
|
+
if (!text.includes('"type":"assistant"') && !text.includes('"type": "assistant"')) {
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
const prim = parseSession(text);
|
|
1333
|
+
results.push({
|
|
1334
|
+
path: file.path,
|
|
1335
|
+
mtime: file.mtime,
|
|
1336
|
+
title: prim.sessionTitle,
|
|
1337
|
+
skillCount: prim.skillsInvoked.length
|
|
1338
|
+
});
|
|
1339
|
+
if (results.length >= limit)
|
|
1340
|
+
break;
|
|
1341
|
+
} catch {}
|
|
1202
1342
|
}
|
|
1343
|
+
return results;
|
|
1203
1344
|
},
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1345
|
+
parse(path) {
|
|
1346
|
+
const text = readFileSync(path, "utf8");
|
|
1347
|
+
return parseSession(text);
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
ADAPTERS = [claudeCodeAdapter];
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
// src/core/agent-invoke.ts
|
|
1354
|
+
var {spawnSync } = globalThis.Bun;
|
|
1355
|
+
function buildAgentArgv(template, promptText) {
|
|
1356
|
+
const marker = "__DORA_PROMPT__";
|
|
1357
|
+
const substituted = template.replace("{{prompt}}", marker);
|
|
1358
|
+
const rawParts = substituted.split(/\s+/).filter(Boolean);
|
|
1359
|
+
return rawParts.map((part) => {
|
|
1360
|
+
let cleaned = part;
|
|
1361
|
+
if (cleaned.startsWith('"') && cleaned.endsWith('"'))
|
|
1362
|
+
cleaned = cleaned.slice(1, -1);
|
|
1363
|
+
if (cleaned.startsWith("'") && cleaned.endsWith("'"))
|
|
1364
|
+
cleaned = cleaned.slice(1, -1);
|
|
1365
|
+
return cleaned === marker ? promptText : cleaned;
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
function extractCandidates(text) {
|
|
1369
|
+
let cleaned = text.replace(/```(?:json)?\s*([\s\S]*?)\s*```/gi, "$1").trim();
|
|
1370
|
+
const candidates = [];
|
|
1371
|
+
const allMatches = cleaned.match(/\{[\s\S]*?\}(?=\s*(?:\{|$))/g) ?? [];
|
|
1372
|
+
const fullMatch = cleaned.match(/\{[\s\S]*\}/);
|
|
1373
|
+
if (fullMatch) {
|
|
1374
|
+
try {
|
|
1375
|
+
candidates.push(JSON.parse(fullMatch[0]));
|
|
1376
|
+
} catch {}
|
|
1377
|
+
}
|
|
1378
|
+
for (const m of allMatches) {
|
|
1379
|
+
try {
|
|
1380
|
+
candidates.push(JSON.parse(m));
|
|
1381
|
+
} catch {}
|
|
1382
|
+
}
|
|
1383
|
+
if (cleaned.startsWith("{") || cleaned.startsWith("[")) {
|
|
1384
|
+
try {
|
|
1385
|
+
const direct = JSON.parse(cleaned);
|
|
1386
|
+
if (direct && typeof direct === "object")
|
|
1387
|
+
candidates.push(direct);
|
|
1388
|
+
} catch {}
|
|
1389
|
+
}
|
|
1390
|
+
const unwrapped = [];
|
|
1391
|
+
for (const c of candidates) {
|
|
1392
|
+
if (c.result) {
|
|
1393
|
+
let inner = c.result;
|
|
1394
|
+
if (typeof inner === "string") {
|
|
1395
|
+
try {
|
|
1396
|
+
inner = JSON.parse(inner);
|
|
1397
|
+
} catch {}
|
|
1398
|
+
}
|
|
1399
|
+
if (inner && typeof inner === "object")
|
|
1400
|
+
unwrapped.push(inner);
|
|
1401
|
+
}
|
|
1402
|
+
unwrapped.push(c);
|
|
1403
|
+
}
|
|
1404
|
+
return unwrapped;
|
|
1405
|
+
}
|
|
1406
|
+
async function invokeAgent(promptText, agentCfg, expectedKeys) {
|
|
1407
|
+
const template = agentCfg.prompt_template ?? '-p "{{prompt}}" --output-format json --bare';
|
|
1408
|
+
const extraArgs = buildAgentArgv(template, promptText);
|
|
1409
|
+
const shortTemplate = template.slice(0, 80);
|
|
1410
|
+
ui.write(` ${import_picocolors4.default.dim(`\u2192 ${agentCfg.command} ${shortTemplate}...`)}`);
|
|
1411
|
+
let result;
|
|
1412
|
+
try {
|
|
1413
|
+
result = spawnSync([agentCfg.command, ...extraArgs], {
|
|
1414
|
+
stdout: "pipe",
|
|
1415
|
+
stderr: "pipe",
|
|
1416
|
+
env: { ...process.env }
|
|
1417
|
+
});
|
|
1418
|
+
} catch (e) {
|
|
1419
|
+
ui.write(` ${import_picocolors4.default.yellow("\u26A0")} Failed to spawn ${agentCfg.command}: ${e.message}`);
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
const stdout = result.stdout.toString().trim();
|
|
1423
|
+
const stderr = result.stderr.toString().trim();
|
|
1424
|
+
if (result.exitCode !== 0) {
|
|
1425
|
+
ui.write(` ${import_picocolors4.default.yellow("\u26A0")} Agent exited with code ${result.exitCode}.`);
|
|
1426
|
+
const out = stdout.replace(/sk-[a-zA-Z0-9_-]+/g, "sk-REDACTED").slice(0, 800);
|
|
1427
|
+
const err = stderr.replace(/sk-[a-zA-Z0-9_-]+/g, "sk-REDACTED").slice(0, 800);
|
|
1428
|
+
if (err)
|
|
1429
|
+
ui.write(` stderr: ${err}`);
|
|
1430
|
+
if (out)
|
|
1431
|
+
ui.write(` stdout: ${out}`);
|
|
1432
|
+
if (!err && !out)
|
|
1433
|
+
ui.write(` (no output captured)`);
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
let cleaned = stdout.replace(/```(?:json)?\s*([\s\S]*?)\s*```/gi, "$1").trim();
|
|
1437
|
+
const unwrapped = extractCandidates(cleaned);
|
|
1438
|
+
for (const c of unwrapped) {
|
|
1439
|
+
if (expectedKeys.some((k) => (k in c)))
|
|
1440
|
+
return c;
|
|
1441
|
+
}
|
|
1442
|
+
if (unwrapped[0])
|
|
1443
|
+
return unwrapped[0];
|
|
1444
|
+
ui.write(` ${import_picocolors4.default.yellow("\u26A0")} Agent produced no usable JSON. stdout (700 chars): ${stdout.slice(0, 700)}`);
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
var import_picocolors4;
|
|
1448
|
+
var init_agent_invoke = __esm(() => {
|
|
1449
|
+
init_out();
|
|
1450
|
+
import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
// src/core/session-eval.ts
|
|
1454
|
+
function toolCallSummary(call) {
|
|
1455
|
+
const inputStr = JSON.stringify(call.input).slice(0, 100);
|
|
1456
|
+
return `${call.name}: ${inputStr}`;
|
|
1457
|
+
}
|
|
1458
|
+
function buildEvalPrompt(primitives, skillContent, maxToolCalls) {
|
|
1459
|
+
const truncated = truncateToolCalls(primitives.toolCalls, maxToolCalls);
|
|
1460
|
+
const wasTruncated = truncated.length < primitives.toolCalls.length;
|
|
1461
|
+
const toolCallLines = truncated.map((c) => toolCallSummary(c)).join(`
|
|
1207
1462
|
`);
|
|
1208
|
-
|
|
1463
|
+
const truncationNote = wasTruncated ? `
|
|
1464
|
+
[truncated: showing ${truncated.length} of ${primitives.toolCalls.length} total tool calls]` : "";
|
|
1465
|
+
const userMsgLines = primitives.userMessages.slice(0, 5).join(`
|
|
1466
|
+
---
|
|
1209
1467
|
`);
|
|
1210
|
-
|
|
1211
|
-
|
|
1468
|
+
return `You are evaluating whether a coding agent followed a skill's instructions during a real session.
|
|
1469
|
+
|
|
1470
|
+
SKILL CONTENT:
|
|
1471
|
+
${skillContent}
|
|
1472
|
+
|
|
1473
|
+
TOOL CALL SEQUENCE (ordered):
|
|
1474
|
+
${toolCallLines}${truncationNote}
|
|
1475
|
+
|
|
1476
|
+
USER MESSAGES (first 5, for familiarity inference):
|
|
1477
|
+
${userMsgLines}
|
|
1478
|
+
|
|
1479
|
+
TASKS:
|
|
1480
|
+
1. Extract the key actions the skill instructs (e.g. "invoke X tool", "fetch N URLs", "create tasks for each item").
|
|
1481
|
+
2. For each expected action, check if the tool call sequence shows it happened.
|
|
1482
|
+
3. Infer user familiarity 1-10 from the user messages:
|
|
1483
|
+
- 1-3: vague/brief prompts, many typos, relies on agent to figure things out
|
|
1484
|
+
- 4-6: clear intent but informal, some corrections
|
|
1485
|
+
- 7-10: precise, technical, specific file paths/function names
|
|
1486
|
+
4. Determine closure:
|
|
1487
|
+
- "1-shot": \u22642 user turns after first Skill invocation
|
|
1488
|
+
- "multi-turn": >2 user turns after first Skill invocation
|
|
1489
|
+
- "incomplete": no end_turn signal or session appears cut off
|
|
1490
|
+
5. Overall verdict: "PASS" if all critical instructions were followed, "FAIL" if any critical instruction was missed.
|
|
1491
|
+
|
|
1492
|
+
CRITICAL: Output *ONLY* the JSON object. No markdown fences, no explanations, no text before or after it. The first character of your response must be '{' and the last must be '}'.
|
|
1493
|
+
|
|
1494
|
+
Return ONLY a valid JSON object with exactly these keys:
|
|
1495
|
+
{
|
|
1496
|
+
"userFamiliarity": <number 1-10>,
|
|
1497
|
+
"userFamiliarityReason": "<one sentence>",
|
|
1498
|
+
"closure": "<1-shot|multi-turn|incomplete>",
|
|
1499
|
+
"userTurnsAfterSkill": <number>,
|
|
1500
|
+
"verdict": "<PASS|FAIL>",
|
|
1501
|
+
"verdictReason": "<one sentence>",
|
|
1502
|
+
"checklist": [
|
|
1503
|
+
{ "instruction": "<what skill said>", "pass": <true|false>, "detail": "<optional>" }
|
|
1504
|
+
]
|
|
1505
|
+
}`;
|
|
1506
|
+
}
|
|
1507
|
+
function makeUnknownResult(primitives, skillName, reason) {
|
|
1508
|
+
return {
|
|
1509
|
+
schemaVersion: 1,
|
|
1510
|
+
sessionId: primitives.sessionId,
|
|
1511
|
+
sessionTitle: primitives.sessionTitle,
|
|
1512
|
+
timestamp: new Date().toISOString(),
|
|
1513
|
+
agent: primitives.agent,
|
|
1514
|
+
model: primitives.model,
|
|
1515
|
+
skill: skillName,
|
|
1516
|
+
userFamiliarity: 0,
|
|
1517
|
+
userFamiliarityReason: "",
|
|
1518
|
+
closure: "incomplete",
|
|
1519
|
+
userTurnsAfterSkill: 0,
|
|
1520
|
+
skillsInvoked: primitives.skillsInvoked,
|
|
1521
|
+
toolCallCounts: primitives.toolCallCounts,
|
|
1522
|
+
verdict: "UNKNOWN",
|
|
1523
|
+
verdictReason: reason,
|
|
1524
|
+
checklist: []
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
async function runEval(primitives, skillName, skillContent, agentCfg, evalCfg) {
|
|
1528
|
+
const prompt = buildEvalPrompt(primitives, skillContent, evalCfg.max_tool_calls);
|
|
1529
|
+
const raw = await invokeAgent(prompt, agentCfg, ["verdict", "checklist"]);
|
|
1530
|
+
if (!raw) {
|
|
1531
|
+
return makeUnknownResult(primitives, skillName, "LLM call failed \u2014 no response");
|
|
1532
|
+
}
|
|
1533
|
+
if (typeof raw.verdict !== "string" || !Array.isArray(raw.checklist)) {
|
|
1534
|
+
ui.write(` ${import_picocolors5.default.yellow("\u26A0")} Received from agent (first 800 chars): ${JSON.stringify(raw).slice(0, 800)}`);
|
|
1535
|
+
return makeUnknownResult(primitives, skillName, "LLM returned malformed response");
|
|
1536
|
+
}
|
|
1537
|
+
const checklist = raw.checklist.map((item) => {
|
|
1538
|
+
const i = item;
|
|
1539
|
+
return {
|
|
1540
|
+
instruction: typeof i.instruction === "string" ? i.instruction : "unknown",
|
|
1541
|
+
pass: i.pass === true,
|
|
1542
|
+
detail: typeof i.detail === "string" ? i.detail : undefined
|
|
1543
|
+
};
|
|
1212
1544
|
});
|
|
1545
|
+
return {
|
|
1546
|
+
schemaVersion: 1,
|
|
1547
|
+
sessionId: primitives.sessionId,
|
|
1548
|
+
sessionTitle: primitives.sessionTitle,
|
|
1549
|
+
timestamp: new Date().toISOString(),
|
|
1550
|
+
agent: primitives.agent,
|
|
1551
|
+
model: primitives.model,
|
|
1552
|
+
skill: skillName,
|
|
1553
|
+
userFamiliarity: typeof raw.userFamiliarity === "number" ? raw.userFamiliarity : 0,
|
|
1554
|
+
userFamiliarityReason: typeof raw.userFamiliarityReason === "string" ? raw.userFamiliarityReason : "",
|
|
1555
|
+
closure: raw.closure ?? "incomplete",
|
|
1556
|
+
userTurnsAfterSkill: typeof raw.userTurnsAfterSkill === "number" ? raw.userTurnsAfterSkill : 0,
|
|
1557
|
+
skillsInvoked: primitives.skillsInvoked,
|
|
1558
|
+
toolCallCounts: primitives.toolCallCounts,
|
|
1559
|
+
verdict: raw.verdict === "PASS" ? "PASS" : raw.verdict === "FAIL" ? "FAIL" : "UNKNOWN",
|
|
1560
|
+
verdictReason: typeof raw.verdictReason === "string" ? raw.verdictReason : "",
|
|
1561
|
+
checklist
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
var import_picocolors5;
|
|
1565
|
+
var init_session_eval = __esm(() => {
|
|
1566
|
+
init_agent_invoke();
|
|
1567
|
+
init_out();
|
|
1568
|
+
import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
1213
1569
|
});
|
|
1214
1570
|
|
|
1215
1571
|
// src/core/journal-config.ts
|
|
1216
|
-
import { existsSync as
|
|
1217
|
-
import { homedir } from "os";
|
|
1218
|
-
import { join } from "path";
|
|
1572
|
+
import { existsSync as existsSync4, mkdirSync } from "fs";
|
|
1573
|
+
import { homedir as homedir2 } from "os";
|
|
1574
|
+
import { join as join2 } from "path";
|
|
1219
1575
|
var {YAML: YAML2 } = globalThis.Bun;
|
|
1220
1576
|
function getDoravalDir() {
|
|
1221
|
-
return process.env.DORAVAL_HOME ??
|
|
1577
|
+
return process.env.DORAVAL_HOME ?? join2(homedir2(), ".doraval");
|
|
1222
1578
|
}
|
|
1223
1579
|
function getConfigPath() {
|
|
1224
|
-
return
|
|
1580
|
+
return join2(getDoravalDir(), "config.yml");
|
|
1225
1581
|
}
|
|
1226
1582
|
function getJournalsDir() {
|
|
1227
|
-
return
|
|
1583
|
+
return join2(getDoravalDir(), "journals");
|
|
1228
1584
|
}
|
|
1229
1585
|
function getPendingDir() {
|
|
1230
|
-
return
|
|
1586
|
+
return join2(getDoravalDir(), "pending");
|
|
1231
1587
|
}
|
|
1232
1588
|
function getPendingProjectDir(project) {
|
|
1233
|
-
return
|
|
1589
|
+
return join2(getPendingDir(), project);
|
|
1590
|
+
}
|
|
1591
|
+
function getEvalsDir() {
|
|
1592
|
+
return join2(getDoravalDir(), "evals");
|
|
1593
|
+
}
|
|
1594
|
+
function getEvalConfig(config) {
|
|
1595
|
+
const defaults = {
|
|
1596
|
+
model: "",
|
|
1597
|
+
api_key: undefined,
|
|
1598
|
+
max_tool_calls: 200,
|
|
1599
|
+
save_history: true
|
|
1600
|
+
};
|
|
1601
|
+
return { ...defaults, ...config?.eval ?? {} };
|
|
1234
1602
|
}
|
|
1235
1603
|
function ensureDoravalDirs() {
|
|
1236
1604
|
const base = getDoravalDir();
|
|
1237
|
-
for (const dir of [base, getJournalsDir(), getPendingDir()]) {
|
|
1238
|
-
if (!
|
|
1605
|
+
for (const dir of [base, getJournalsDir(), getPendingDir(), getEvalsDir()]) {
|
|
1606
|
+
if (!existsSync4(dir)) {
|
|
1239
1607
|
mkdirSync(dir, { recursive: true });
|
|
1240
1608
|
}
|
|
1241
1609
|
}
|
|
1242
1610
|
}
|
|
1243
1611
|
async function readConfig() {
|
|
1244
1612
|
const path = getConfigPath();
|
|
1245
|
-
if (!
|
|
1613
|
+
if (!existsSync4(path))
|
|
1246
1614
|
return null;
|
|
1247
1615
|
const raw = await Bun.file(path).text();
|
|
1248
1616
|
return YAML2.parse(raw);
|
|
@@ -1281,10 +1649,307 @@ function sanitizeProjectName(name) {
|
|
|
1281
1649
|
}
|
|
1282
1650
|
var init_journal_config = () => {};
|
|
1283
1651
|
|
|
1652
|
+
// src/cli/prompt.ts
|
|
1653
|
+
function prompt(label, fallback) {
|
|
1654
|
+
process.stderr.write(`${label} ${import_picocolors6.default.dim(`(${fallback})`)} `);
|
|
1655
|
+
const buf = new Uint8Array(1024);
|
|
1656
|
+
const n = __require("fs").readSync(0, buf);
|
|
1657
|
+
const input = new TextDecoder().decode(buf.subarray(0, n)).trim();
|
|
1658
|
+
return input || fallback;
|
|
1659
|
+
}
|
|
1660
|
+
var import_picocolors6;
|
|
1661
|
+
var init_prompt = __esm(() => {
|
|
1662
|
+
import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
1663
|
+
});
|
|
1664
|
+
|
|
1665
|
+
// src/cli/commands/eval.ts
|
|
1666
|
+
var exports_eval = {};
|
|
1667
|
+
__export(exports_eval, {
|
|
1668
|
+
default: () => eval_default
|
|
1669
|
+
});
|
|
1670
|
+
import { join as join3, basename } from "path";
|
|
1671
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1672
|
+
function renderResult(result, verbose) {
|
|
1673
|
+
const verdictColor = result.verdict === "PASS" ? import_picocolors7.default.green : result.verdict === "FAIL" ? import_picocolors7.default.red : import_picocolors7.default.yellow;
|
|
1674
|
+
const verdictSymbol = result.verdict === "PASS" ? "\u2713" : result.verdict === "FAIL" ? "\u2717" : "?";
|
|
1675
|
+
ui.write(`
|
|
1676
|
+
${verdictColor(`[${result.verdict}]`)} ${import_picocolors7.default.bold(result.skill)}`);
|
|
1677
|
+
ui.write(` agent: ${result.agent}`);
|
|
1678
|
+
ui.write(` model: ${result.model}`);
|
|
1679
|
+
if (result.userFamiliarity > 0) {
|
|
1680
|
+
ui.write(` familiarity: ${result.userFamiliarity}/10 (${result.userFamiliarityReason})`);
|
|
1681
|
+
}
|
|
1682
|
+
ui.write(` closure: ${result.closure}${result.userTurnsAfterSkill > 0 ? ` (${result.userTurnsAfterSkill} turns)` : ""}`);
|
|
1683
|
+
if (result.sessionTitle) {
|
|
1684
|
+
ui.write(` session: ${result.sessionId.slice(0, 8)} "${result.sessionTitle}"`);
|
|
1685
|
+
}
|
|
1686
|
+
if (result.checklist.length > 0) {
|
|
1687
|
+
ui.write(`
|
|
1688
|
+
Adherence:`);
|
|
1689
|
+
for (const item of result.checklist) {
|
|
1690
|
+
const sym = item.pass ? import_picocolors7.default.green("\u2713") : import_picocolors7.default.red("\u2717");
|
|
1691
|
+
const detail = item.detail ? ` ${import_picocolors7.default.dim(item.detail)}` : "";
|
|
1692
|
+
ui.write(` ${sym} ${item.instruction}${detail}`);
|
|
1693
|
+
}
|
|
1694
|
+
const passed = result.checklist.filter((c) => c.pass).length;
|
|
1695
|
+
ui.write(`
|
|
1696
|
+
Result: ${passed}/${result.checklist.length} [${verdictColor(result.verdict)}${result.verdictReason ? ` \u2014 ${result.verdictReason}` : ""}]`);
|
|
1697
|
+
} else if (result.verdictReason) {
|
|
1698
|
+
ui.write(`
|
|
1699
|
+
${verdictColor(verdictSymbol)} ${result.verdictReason}`);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
function selectRecentSessions(recent) {
|
|
1703
|
+
if (recent.length === 0)
|
|
1704
|
+
return [];
|
|
1705
|
+
if (recent.length === 1)
|
|
1706
|
+
return [recent[0].path];
|
|
1707
|
+
ui.write(`
|
|
1708
|
+
Recent sessions for this directory:`);
|
|
1709
|
+
recent.forEach((s, i) => {
|
|
1710
|
+
const date = new Date(s.mtime).toISOString().slice(0, 10);
|
|
1711
|
+
const titleStr = s.title ? ` "${s.title.slice(0, 45)}"` : "";
|
|
1712
|
+
const skillStr = s.skillCount > 0 ? ` (${s.skillCount} skill${s.skillCount === 1 ? "" : "s"})` : "";
|
|
1713
|
+
const short = basename(s.path);
|
|
1714
|
+
ui.write(` ${i + 1}. ${date}${titleStr}${skillStr} ${import_picocolors7.default.dim(short)}`);
|
|
1715
|
+
});
|
|
1716
|
+
const input = prompt(`
|
|
1717
|
+
Select session(s) (e.g. 1,3 or 2-4 or all or latest): `, "1").trim().toLowerCase();
|
|
1718
|
+
if (input === "all")
|
|
1719
|
+
return recent.map((s) => s.path);
|
|
1720
|
+
if (input === "latest")
|
|
1721
|
+
return [recent[0].path];
|
|
1722
|
+
if (input.includes("/") || input.endsWith(".jsonl"))
|
|
1723
|
+
return [input];
|
|
1724
|
+
const selected = new Set;
|
|
1725
|
+
const parts = input.split(/[\s,]+/);
|
|
1726
|
+
for (const part of parts) {
|
|
1727
|
+
if (part.includes("-")) {
|
|
1728
|
+
const nums = part.split("-").map((n) => parseInt(n, 10)).filter((n) => !isNaN(n));
|
|
1729
|
+
const start = nums[0] ?? 0;
|
|
1730
|
+
const end = nums[1] ?? start;
|
|
1731
|
+
for (let n = start;n <= end; n++) {
|
|
1732
|
+
const item = recent[n - 1];
|
|
1733
|
+
if (item)
|
|
1734
|
+
selected.add(item.path);
|
|
1735
|
+
}
|
|
1736
|
+
} else {
|
|
1737
|
+
const n = parseInt(part, 10);
|
|
1738
|
+
if (!isNaN(n)) {
|
|
1739
|
+
const item = recent[n - 1];
|
|
1740
|
+
if (item)
|
|
1741
|
+
selected.add(item.path);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
return selected.size > 0 ? Array.from(selected) : [recent[0].path];
|
|
1746
|
+
}
|
|
1747
|
+
var import_picocolors7, eval_default;
|
|
1748
|
+
var init_eval = __esm(() => {
|
|
1749
|
+
init_dist();
|
|
1750
|
+
init_out();
|
|
1751
|
+
init_session_adapters();
|
|
1752
|
+
init_session_eval();
|
|
1753
|
+
init_journal_config();
|
|
1754
|
+
init_skill_validate();
|
|
1755
|
+
init_prompt();
|
|
1756
|
+
import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
1757
|
+
eval_default = defineCommand({
|
|
1758
|
+
meta: {
|
|
1759
|
+
name: "eval",
|
|
1760
|
+
description: "Evaluate a real coding agent session against skill instructions"
|
|
1761
|
+
},
|
|
1762
|
+
args: {
|
|
1763
|
+
session: {
|
|
1764
|
+
type: "string",
|
|
1765
|
+
description: "Path to .jsonl session file(s). Supports comma/space separated values, or omit to interactively select from recent sessions."
|
|
1766
|
+
},
|
|
1767
|
+
skill: {
|
|
1768
|
+
type: "string",
|
|
1769
|
+
description: "Path to a skill directory to filter to (default: all skills in session)"
|
|
1770
|
+
},
|
|
1771
|
+
format: {
|
|
1772
|
+
type: "string",
|
|
1773
|
+
alias: "f",
|
|
1774
|
+
description: "Output format: table (default) or json",
|
|
1775
|
+
default: "table"
|
|
1776
|
+
},
|
|
1777
|
+
ci: {
|
|
1778
|
+
type: "boolean",
|
|
1779
|
+
description: "Exit with code 1 if any verdict is FAIL",
|
|
1780
|
+
default: false
|
|
1781
|
+
},
|
|
1782
|
+
verbose: {
|
|
1783
|
+
type: "boolean",
|
|
1784
|
+
alias: "v",
|
|
1785
|
+
description: "Show full checklist reasoning",
|
|
1786
|
+
default: false
|
|
1787
|
+
}
|
|
1788
|
+
},
|
|
1789
|
+
async run({ args }) {
|
|
1790
|
+
ui.heading("doraval eval \u2014 Session skill adherence");
|
|
1791
|
+
const config = await readConfig();
|
|
1792
|
+
const evalCfg = getEvalConfig(config);
|
|
1793
|
+
const agentCfg = config?.agent;
|
|
1794
|
+
if (!agentCfg) {
|
|
1795
|
+
ui.fail("No coding agent configured. Run: dora init");
|
|
1796
|
+
process.exit(2);
|
|
1797
|
+
}
|
|
1798
|
+
if (!evalCfg.model) {
|
|
1799
|
+
ui.warn("No eval.model configured for the judge LLM.");
|
|
1800
|
+
ui.info(" doraval will use your configured agent (" + agentCfg.command + ").");
|
|
1801
|
+
ui.info(" If you want to record a specific model, run: dora config set eval.model claude-3-5-sonnet-20241022");
|
|
1802
|
+
}
|
|
1803
|
+
let sessionPaths = [];
|
|
1804
|
+
if (args.session) {
|
|
1805
|
+
sessionPaths = String(args.session).split(/[\s,]+/).map((s) => s.trim()).filter(Boolean);
|
|
1806
|
+
} else {
|
|
1807
|
+
const adapter2 = getAdapter();
|
|
1808
|
+
if (!adapter2) {
|
|
1809
|
+
ui.fail("No supported coding agent detected. Is Claude Code installed?");
|
|
1810
|
+
process.exit(2);
|
|
1811
|
+
}
|
|
1812
|
+
let recent = adapter2.listRecentSessions(process.cwd(), 12);
|
|
1813
|
+
const withSkills = recent.filter((s) => s.skillCount > 0);
|
|
1814
|
+
if (withSkills.length > 0)
|
|
1815
|
+
recent = withSkills;
|
|
1816
|
+
if (recent.length === 0) {
|
|
1817
|
+
ui.fail(`No sessions with skills found for ${process.cwd()}`);
|
|
1818
|
+
ui.info(" Use --session <path> to specify a session file.");
|
|
1819
|
+
process.exit(2);
|
|
1820
|
+
}
|
|
1821
|
+
if (recent.length === 1) {
|
|
1822
|
+
sessionPaths = [recent[0].path];
|
|
1823
|
+
} else if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
1824
|
+
sessionPaths = [recent[0].path];
|
|
1825
|
+
} else {
|
|
1826
|
+
sessionPaths = selectRecentSessions(recent);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
if (sessionPaths.length === 0) {
|
|
1830
|
+
ui.fail("No sessions selected.");
|
|
1831
|
+
process.exit(2);
|
|
1832
|
+
}
|
|
1833
|
+
const adapter = getAdapter();
|
|
1834
|
+
if (!adapter) {
|
|
1835
|
+
ui.fail("No supported coding agent detected.");
|
|
1836
|
+
process.exit(2);
|
|
1837
|
+
}
|
|
1838
|
+
const allResults = [];
|
|
1839
|
+
for (const sessionPath of sessionPaths) {
|
|
1840
|
+
ui.info(` Session: ${import_picocolors7.default.dim(sessionPath)}`);
|
|
1841
|
+
const primitives = adapter.parse(sessionPath);
|
|
1842
|
+
if (primitives.skillsInvoked.length === 0) {
|
|
1843
|
+
ui.warn(" No skills were invoked in this session.");
|
|
1844
|
+
continue;
|
|
1845
|
+
}
|
|
1846
|
+
let skillsToEval = primitives.skillsInvoked;
|
|
1847
|
+
if (args.skill) {
|
|
1848
|
+
skillsToEval = skillsToEval.filter((s) => s.includes(args.skill));
|
|
1849
|
+
if (skillsToEval.length === 0) {
|
|
1850
|
+
ui.warn(` No matching skills found for filter: ${args.skill}`);
|
|
1851
|
+
continue;
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
ui.write(` ${import_picocolors7.default.dim("\xB7 Sending session summary (tool calls + 5 user messages) to")} ${import_picocolors7.default.dim(evalCfg.model || "configured model")}${import_picocolors7.default.dim(". Use --verbose to inspect.")}`);
|
|
1855
|
+
ensureDoravalDirs();
|
|
1856
|
+
for (const skillName of skillsToEval) {
|
|
1857
|
+
ui.info(`
|
|
1858
|
+
Evaluating: ${import_picocolors7.default.bold(skillName)}`);
|
|
1859
|
+
let skillContent = `Skill: ${skillName}
|
|
1860
|
+
(skill content not found locally \u2014 using skill name only for evaluation)`;
|
|
1861
|
+
const candidateDirs = [
|
|
1862
|
+
process.cwd(),
|
|
1863
|
+
join3(process.cwd(), ".claude", "skills", skillName.split(":").pop() ?? skillName),
|
|
1864
|
+
join3(process.cwd(), "skills", skillName.split(":").pop() ?? skillName)
|
|
1865
|
+
];
|
|
1866
|
+
for (const dir of candidateDirs) {
|
|
1867
|
+
if (existsSync5(join3(dir, "SKILL.md"))) {
|
|
1868
|
+
const loaded = await loadSkill(dir);
|
|
1869
|
+
if (loaded.ok) {
|
|
1870
|
+
skillContent = loaded.model.content;
|
|
1871
|
+
break;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
const result = await runEval(primitives, skillName, skillContent, agentCfg, evalCfg);
|
|
1876
|
+
allResults.push(result);
|
|
1877
|
+
if (evalCfg.save_history) {
|
|
1878
|
+
const evalPath = join3(getEvalsDir(), `${primitives.sessionId}-${Date.now()}.json`);
|
|
1879
|
+
await Bun.write(evalPath, JSON.stringify(result, null, 2));
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
if (args.format === "json") {
|
|
1884
|
+
process.stdout.write(JSON.stringify(allResults, null, 2) + `
|
|
1885
|
+
`);
|
|
1886
|
+
} else {
|
|
1887
|
+
for (const result of allResults) {
|
|
1888
|
+
renderResult(result, Boolean(args.verbose));
|
|
1889
|
+
}
|
|
1890
|
+
ui.blank();
|
|
1891
|
+
}
|
|
1892
|
+
if (args.ci && allResults.some((r) => r.verdict === "FAIL")) {
|
|
1893
|
+
process.exit(1);
|
|
1894
|
+
}
|
|
1895
|
+
process.exit(0);
|
|
1896
|
+
}
|
|
1897
|
+
});
|
|
1898
|
+
});
|
|
1899
|
+
|
|
1900
|
+
// src/cli/commands/judge.ts
|
|
1901
|
+
var exports_judge = {};
|
|
1902
|
+
__export(exports_judge, {
|
|
1903
|
+
default: () => judge_default
|
|
1904
|
+
});
|
|
1905
|
+
var judge_default;
|
|
1906
|
+
var init_judge = __esm(() => {
|
|
1907
|
+
init_dist();
|
|
1908
|
+
judge_default = defineCommand({
|
|
1909
|
+
meta: {
|
|
1910
|
+
name: "judge",
|
|
1911
|
+
description: "Evaluate the latest session for a skill (alias for eval --skill)"
|
|
1912
|
+
},
|
|
1913
|
+
args: {
|
|
1914
|
+
path: {
|
|
1915
|
+
type: "positional",
|
|
1916
|
+
description: "Path to skill directory or skill name",
|
|
1917
|
+
required: true
|
|
1918
|
+
},
|
|
1919
|
+
format: {
|
|
1920
|
+
type: "string",
|
|
1921
|
+
alias: "f",
|
|
1922
|
+
description: "Output format (json or table)",
|
|
1923
|
+
default: "table"
|
|
1924
|
+
},
|
|
1925
|
+
ci: {
|
|
1926
|
+
type: "boolean",
|
|
1927
|
+
description: "Exit non-zero if FAIL",
|
|
1928
|
+
default: false
|
|
1929
|
+
},
|
|
1930
|
+
verbose: {
|
|
1931
|
+
type: "boolean",
|
|
1932
|
+
alias: "v",
|
|
1933
|
+
description: "Show full checklist",
|
|
1934
|
+
default: false
|
|
1935
|
+
}
|
|
1936
|
+
},
|
|
1937
|
+
async run({ args }) {
|
|
1938
|
+
const evalCmd = await Promise.resolve().then(() => (init_eval(), exports_eval)).then((m) => m.default);
|
|
1939
|
+
const newArgs = {
|
|
1940
|
+
...args,
|
|
1941
|
+
skill: args.path,
|
|
1942
|
+
session: undefined
|
|
1943
|
+
};
|
|
1944
|
+
return evalCmd.run?.({ args: newArgs });
|
|
1945
|
+
}
|
|
1946
|
+
});
|
|
1947
|
+
});
|
|
1948
|
+
|
|
1284
1949
|
// src/core/journal-remote.ts
|
|
1285
|
-
var {spawnSync } = globalThis.Bun;
|
|
1950
|
+
var {spawnSync: spawnSync2 } = globalThis.Bun;
|
|
1286
1951
|
function hasGhCli() {
|
|
1287
|
-
const result =
|
|
1952
|
+
const result = spawnSync2(["gh", "--version"], {
|
|
1288
1953
|
stdout: "pipe",
|
|
1289
1954
|
stderr: "pipe"
|
|
1290
1955
|
});
|
|
@@ -1296,7 +1961,7 @@ function ensureGhCli() {
|
|
|
1296
1961
|
return { ok: false, error: "GH_CLI_MISSING" };
|
|
1297
1962
|
}
|
|
1298
1963
|
function fetchRemoteJournalFile(repo, path) {
|
|
1299
|
-
const result =
|
|
1964
|
+
const result = spawnSync2(["gh", "api", `repos/${repo}/contents/${path}`, "--jq", "{sha, content, encoding}"], { stdout: "pipe", stderr: "pipe" });
|
|
1300
1965
|
if (result.exitCode !== 0) {
|
|
1301
1966
|
const stderr = result.stderr.toString();
|
|
1302
1967
|
if (stderr.includes("404") || stderr.includes("Not Found")) {
|
|
@@ -1336,7 +2001,7 @@ async function refreshLocalJournalFile(repo, remotePath, localPath) {
|
|
|
1336
2001
|
return { ok: true, value: true };
|
|
1337
2002
|
}
|
|
1338
2003
|
function getRemoteJournalFileMeta(repo, path) {
|
|
1339
|
-
const result =
|
|
2004
|
+
const result = spawnSync2(["gh", "api", `repos/${repo}/contents/${path}`, "--jq", "{sha, content, encoding}"], { stdout: "pipe", stderr: "pipe" });
|
|
1340
2005
|
if (result.exitCode !== 0) {
|
|
1341
2006
|
const stderr = result.stderr.toString();
|
|
1342
2007
|
if (stderr.includes("404") || stderr.includes("Not Found")) {
|
|
@@ -1352,7 +2017,7 @@ function getRemoteJournalFileMeta(repo, path) {
|
|
|
1352
2017
|
}
|
|
1353
2018
|
}
|
|
1354
2019
|
function getGitRemoteOwner() {
|
|
1355
|
-
const result =
|
|
2020
|
+
const result = spawnSync2(["git", "config", "--get", "remote.origin.url"], {
|
|
1356
2021
|
stdout: "pipe",
|
|
1357
2022
|
stderr: "pipe"
|
|
1358
2023
|
});
|
|
@@ -1365,7 +2030,7 @@ function getGitRemoteOwner() {
|
|
|
1365
2030
|
return match ? match[1] : null;
|
|
1366
2031
|
}
|
|
1367
2032
|
function ghUser() {
|
|
1368
|
-
const result =
|
|
2033
|
+
const result = spawnSync2(["gh", "api", "user", "--jq", ".login"], {
|
|
1369
2034
|
stdout: "pipe",
|
|
1370
2035
|
stderr: "pipe"
|
|
1371
2036
|
});
|
|
@@ -1374,38 +2039,25 @@ function ghUser() {
|
|
|
1374
2039
|
return result.stdout.toString().trim() || null;
|
|
1375
2040
|
}
|
|
1376
2041
|
function repoExists(repo) {
|
|
1377
|
-
const result =
|
|
2042
|
+
const result = spawnSync2(["gh", "api", `repos/${repo}`, "--jq", ".full_name"], { stdout: "pipe", stderr: "pipe" });
|
|
1378
2043
|
return result.exitCode === 0 && result.stdout.toString().trim().length > 0;
|
|
1379
2044
|
}
|
|
1380
2045
|
var init_journal_remote = () => {};
|
|
1381
2046
|
|
|
1382
|
-
// src/cli/prompt.ts
|
|
1383
|
-
function prompt(label, fallback) {
|
|
1384
|
-
process.stderr.write(`${label} ${import_picocolors4.default.dim(`(${fallback})`)} `);
|
|
1385
|
-
const buf = new Uint8Array(1024);
|
|
1386
|
-
const n = __require("fs").readSync(0, buf);
|
|
1387
|
-
const input = new TextDecoder().decode(buf.subarray(0, n)).trim();
|
|
1388
|
-
return input || fallback;
|
|
1389
|
-
}
|
|
1390
|
-
var import_picocolors4;
|
|
1391
|
-
var init_prompt = __esm(() => {
|
|
1392
|
-
import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
1393
|
-
});
|
|
1394
|
-
|
|
1395
2047
|
// src/cli/commands/journal/init.ts
|
|
1396
2048
|
var exports_init = {};
|
|
1397
2049
|
__export(exports_init, {
|
|
1398
2050
|
default: () => init_default
|
|
1399
2051
|
});
|
|
1400
|
-
import { basename, join as
|
|
1401
|
-
var
|
|
2052
|
+
import { basename as basename2, join as join4 } from "path";
|
|
2053
|
+
var import_picocolors8, init_default;
|
|
1402
2054
|
var init_init = __esm(() => {
|
|
1403
2055
|
init_dist();
|
|
1404
2056
|
init_out();
|
|
1405
2057
|
init_journal_config();
|
|
1406
2058
|
init_journal_remote();
|
|
1407
2059
|
init_prompt();
|
|
1408
|
-
|
|
2060
|
+
import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
1409
2061
|
init_default = defineCommand({
|
|
1410
2062
|
meta: {
|
|
1411
2063
|
name: "init",
|
|
@@ -1430,21 +2082,21 @@ var init_init = __esm(() => {
|
|
|
1430
2082
|
},
|
|
1431
2083
|
async run({ args }) {
|
|
1432
2084
|
ui.write(`
|
|
1433
|
-
${
|
|
2085
|
+
${import_picocolors8.default.bold(import_picocolors8.default.white("dora journal init"))} (or top-level ${import_picocolors8.default.dim(import_picocolors8.default.gray("dora init"))}) \u2014 Set up your journal
|
|
1434
2086
|
`);
|
|
1435
2087
|
const ghCheck = ensureGhCli();
|
|
1436
2088
|
if (!ghCheck.ok) {
|
|
1437
|
-
ui.write(` ${
|
|
2089
|
+
ui.write(` ${import_picocolors8.default.red("\u2717")} ${import_picocolors8.default.white("The GitHub CLI (")}${import_picocolors8.default.bold("gh")}${import_picocolors8.default.white(") is not installed.")}
|
|
1438
2090
|
`);
|
|
1439
|
-
ui.write(` doraval uses ${
|
|
2091
|
+
ui.write(` doraval uses ${import_picocolors8.default.bold("gh")} to fetch and sync journal files with GitHub.
|
|
1440
2092
|
`);
|
|
1441
2093
|
ui.write(` Install it:
|
|
1442
2094
|
`);
|
|
1443
|
-
ui.write(` macOS: ${
|
|
1444
|
-
ui.write(` Linux: ${
|
|
1445
|
-
ui.write(` Windows: ${
|
|
2095
|
+
ui.write(` macOS: ${import_picocolors8.default.dim("brew install gh")}`);
|
|
2096
|
+
ui.write(` Linux: ${import_picocolors8.default.dim("https://github.com/cli/cli/blob/trunk/docs/install_linux.md")}`);
|
|
2097
|
+
ui.write(` Windows: ${import_picocolors8.default.dim("winget install --id GitHub.cli")}
|
|
1446
2098
|
`);
|
|
1447
|
-
ui.write(` Then authenticate: ${
|
|
2099
|
+
ui.write(` Then authenticate: ${import_picocolors8.default.dim("gh auth login")}
|
|
1448
2100
|
`);
|
|
1449
2101
|
process.exit(1);
|
|
1450
2102
|
}
|
|
@@ -1457,46 +2109,46 @@ var init_init = __esm(() => {
|
|
|
1457
2109
|
if (gitOwner) {
|
|
1458
2110
|
defaultRepo = `${gitOwner}/${gitOwner}.md`;
|
|
1459
2111
|
if (ghLogin && ghLogin !== gitOwner) {
|
|
1460
|
-
sourceNote = ` ${
|
|
2112
|
+
sourceNote = ` ${import_picocolors8.default.dim("(from git remote; your active gh account is " + ghLogin + ")")}
|
|
1461
2113
|
`;
|
|
1462
2114
|
} else {
|
|
1463
|
-
sourceNote = ` ${
|
|
2115
|
+
sourceNote = ` ${import_picocolors8.default.dim("(from git remote)")}
|
|
1464
2116
|
`;
|
|
1465
2117
|
}
|
|
1466
2118
|
} else if (ghLogin) {
|
|
1467
2119
|
defaultRepo = `${ghLogin}/${ghLogin}.md`;
|
|
1468
|
-
sourceNote = ` ${
|
|
2120
|
+
sourceNote = ` ${import_picocolors8.default.dim("(from your active gh account)")}
|
|
1469
2121
|
`;
|
|
1470
2122
|
} else {
|
|
1471
|
-
ui.write(` ${
|
|
2123
|
+
ui.write(` ${import_picocolors8.default.yellow("\u26A0")} Not logged in to GitHub. Run ${import_picocolors8.default.dim("gh auth login")} first.
|
|
1472
2124
|
`);
|
|
1473
2125
|
process.exit(1);
|
|
1474
2126
|
}
|
|
1475
2127
|
const existingConfig = await readConfig();
|
|
1476
2128
|
if (existingConfig?.journal.repo) {
|
|
1477
2129
|
defaultRepo = existingConfig.journal.repo;
|
|
1478
|
-
sourceNote = ` ${
|
|
2130
|
+
sourceNote = ` ${import_picocolors8.default.dim("(from your previous journal setup)")}
|
|
1479
2131
|
`;
|
|
1480
2132
|
}
|
|
1481
|
-
ui.write(` Journal repo ${
|
|
2133
|
+
ui.write(` Journal repo ${import_picocolors8.default.dim(import_picocolors8.default.gray("(owner/name)"))}`);
|
|
1482
2134
|
if (sourceNote)
|
|
1483
2135
|
ui.write(sourceNote);
|
|
1484
2136
|
repo = prompt(" >", defaultRepo);
|
|
1485
2137
|
}
|
|
1486
2138
|
let project = args.project || process.env.DORAVAL_PROJECT;
|
|
1487
2139
|
if (!project) {
|
|
1488
|
-
const defaultProject =
|
|
2140
|
+
const defaultProject = basename2(process.cwd());
|
|
1489
2141
|
project = prompt(" Project name", defaultProject);
|
|
1490
2142
|
}
|
|
1491
2143
|
project = sanitizeProjectName(project);
|
|
1492
2144
|
if (!repoExists(repo)) {
|
|
1493
|
-
ui.write(` ${
|
|
2145
|
+
ui.write(` ${import_picocolors8.default.red("\u2717")} Repository ${import_picocolors8.default.bold(import_picocolors8.default.white(repo))} not found on GitHub.
|
|
1494
2146
|
`);
|
|
1495
2147
|
ui.write(` Create it first:
|
|
1496
2148
|
`);
|
|
1497
|
-
ui.write(` ${
|
|
2149
|
+
ui.write(` ${import_picocolors8.default.dim(`gh repo create ${repo} --private --description "Personal journal for agent decisions"`)}
|
|
1498
2150
|
`);
|
|
1499
|
-
ui.write(` The repo should be private. doraval will populate it on first ${
|
|
2151
|
+
ui.write(` The repo should be private. doraval will populate it on first ${import_picocolors8.default.dim("dora journal sync")}.
|
|
1500
2152
|
`);
|
|
1501
2153
|
process.exit(1);
|
|
1502
2154
|
}
|
|
@@ -1504,20 +2156,20 @@ var init_init = __esm(() => {
|
|
|
1504
2156
|
const alreadyRegistered = existing?.journal.projects[project];
|
|
1505
2157
|
const isRefresh = alreadyRegistered && args.refresh;
|
|
1506
2158
|
if (alreadyRegistered && !isRefresh) {
|
|
1507
|
-
ui.write(` ${
|
|
2159
|
+
ui.write(` ${import_picocolors8.default.yellow("\u26A0")} Project ${import_picocolors8.default.bold(import_picocolors8.default.white(project))} is already registered.
|
|
1508
2160
|
`);
|
|
1509
|
-
ui.write(` Repo: ${
|
|
2161
|
+
ui.write(` Repo: ${import_picocolors8.default.gray(existing.journal.repo)}`);
|
|
1510
2162
|
ui.write(` Remote: ${existing.journal.projects[project]?.remote_path}
|
|
1511
2163
|
`);
|
|
1512
|
-
ui.write(` To refresh local files, run: ${
|
|
2164
|
+
ui.write(` To refresh local files, run: ${import_picocolors8.default.dim(import_picocolors8.default.gray(`dora journal update`))}
|
|
1513
2165
|
` + ` (init --refresh still works for compatibility.)
|
|
1514
|
-
` + ` Or remove the project from ${
|
|
2166
|
+
` + ` Or remove the project from ${import_picocolors8.default.dim(import_picocolors8.default.gray("~/.doraval/config.yml"))} to fully re-initialize.
|
|
1515
2167
|
`);
|
|
1516
2168
|
process.exit(0);
|
|
1517
2169
|
}
|
|
1518
2170
|
const journalsDir = getJournalsDir();
|
|
1519
2171
|
const remotePath = `projects/${project}.md`;
|
|
1520
|
-
const localPath =
|
|
2172
|
+
const localPath = join4(journalsDir, `${project}.md`);
|
|
1521
2173
|
const effectiveRepo = isRefresh && !args.repo ? existing.journal.repo : repo;
|
|
1522
2174
|
const config = existing ?? {
|
|
1523
2175
|
journal: { repo: effectiveRepo, projects: {} }
|
|
@@ -1529,16 +2181,16 @@ var init_init = __esm(() => {
|
|
|
1529
2181
|
};
|
|
1530
2182
|
ensureDoravalDirs();
|
|
1531
2183
|
const actionLabel = isRefresh ? "Refreshing" : "Fetching";
|
|
1532
|
-
ui.write(` ${
|
|
2184
|
+
ui.write(` ${import_picocolors8.default.dim(import_picocolors8.default.gray(`${actionLabel} journal files from`))} ${import_picocolors8.default.gray(effectiveRepo)}${import_picocolors8.default.dim(import_picocolors8.default.gray("..."))}
|
|
1533
2185
|
`);
|
|
1534
|
-
const globalDest =
|
|
2186
|
+
const globalDest = join4(journalsDir, "global.md");
|
|
1535
2187
|
const refreshGlobalRes = await refreshLocalJournalFile(effectiveRepo, "global.md", globalDest);
|
|
1536
2188
|
let wroteGlobal;
|
|
1537
2189
|
if (!refreshGlobalRes.ok) {
|
|
1538
2190
|
if (refreshGlobalRes.isNotFound) {
|
|
1539
2191
|
wroteGlobal = false;
|
|
1540
2192
|
} else {
|
|
1541
|
-
ui.write(` ${
|
|
2193
|
+
ui.write(` ${import_picocolors8.default.red("\u2717")} Failed to fetch global.md from ${effectiveRepo}:`);
|
|
1542
2194
|
ui.write(refreshGlobalRes.error);
|
|
1543
2195
|
process.exit(1);
|
|
1544
2196
|
}
|
|
@@ -1546,9 +2198,9 @@ var init_init = __esm(() => {
|
|
|
1546
2198
|
wroteGlobal = refreshGlobalRes.value;
|
|
1547
2199
|
}
|
|
1548
2200
|
if (wroteGlobal) {
|
|
1549
|
-
ui.write(` ${
|
|
2201
|
+
ui.write(` ${import_picocolors8.default.green("\u2713")} global.md`);
|
|
1550
2202
|
} else {
|
|
1551
|
-
ui.write(` ${
|
|
2203
|
+
ui.write(` ${import_picocolors8.default.dim("\xB7")} global.md ${import_picocolors8.default.dim("(not found \u2014 will be created on first sync)")}`);
|
|
1552
2204
|
await Bun.write(globalDest, `# Global Journal
|
|
1553
2205
|
|
|
1554
2206
|
Cross-project principles.
|
|
@@ -1560,7 +2212,7 @@ Cross-project principles.
|
|
|
1560
2212
|
if (refreshProjectRes.isNotFound) {
|
|
1561
2213
|
wroteProject = false;
|
|
1562
2214
|
} else {
|
|
1563
|
-
ui.write(` ${
|
|
2215
|
+
ui.write(` ${import_picocolors8.default.red("\u2717")} Failed to fetch ${remotePath} from ${effectiveRepo}:`);
|
|
1564
2216
|
ui.write(refreshProjectRes.error);
|
|
1565
2217
|
process.exit(1);
|
|
1566
2218
|
}
|
|
@@ -1568,9 +2220,9 @@ Cross-project principles.
|
|
|
1568
2220
|
wroteProject = refreshProjectRes.value;
|
|
1569
2221
|
}
|
|
1570
2222
|
if (wroteProject) {
|
|
1571
|
-
ui.write(` ${
|
|
2223
|
+
ui.write(` ${import_picocolors8.default.green("\u2713")} ${remotePath}`);
|
|
1572
2224
|
} else {
|
|
1573
|
-
ui.write(` ${
|
|
2225
|
+
ui.write(` ${import_picocolors8.default.dim("\xB7")} ${remotePath} ${import_picocolors8.default.dim("(not found \u2014 will be created on first sync)")}`);
|
|
1574
2226
|
await Bun.write(localPath, `# ${project} Journal
|
|
1575
2227
|
|
|
1576
2228
|
Project-specific decisions.
|
|
@@ -1578,13 +2230,13 @@ Project-specific decisions.
|
|
|
1578
2230
|
}
|
|
1579
2231
|
await writeConfig(config);
|
|
1580
2232
|
ui.write(`
|
|
1581
|
-
${
|
|
2233
|
+
${import_picocolors8.default.green("\u2713")} Project ${import_picocolors8.default.bold(import_picocolors8.default.white(project))} registered to ${import_picocolors8.default.bold(import_picocolors8.default.white(repo))}.
|
|
1582
2234
|
`);
|
|
1583
|
-
ui.write(` Config: ${
|
|
1584
|
-
ui.write(` Journals: ${
|
|
1585
|
-
ui.write(` Pending: ${
|
|
2235
|
+
ui.write(` Config: ${import_picocolors8.default.dim(import_picocolors8.default.gray("~/.doraval/config.yml"))}`);
|
|
2236
|
+
ui.write(` Journals: ${import_picocolors8.default.dim(import_picocolors8.default.gray("~/.doraval/journals/"))}`);
|
|
2237
|
+
ui.write(` Pending: ${import_picocolors8.default.dim(import_picocolors8.default.gray("~/.doraval/pending/"))}
|
|
1586
2238
|
`);
|
|
1587
|
-
ui.write(` Use ${
|
|
2239
|
+
ui.write(` Use ${import_picocolors8.default.dim(import_picocolors8.default.gray("dora journal add"))} to propose decisions and ${import_picocolors8.default.dim(import_picocolors8.default.gray("dora journal list"))} to view them.
|
|
1588
2240
|
`);
|
|
1589
2241
|
process.exit(0);
|
|
1590
2242
|
}
|
|
@@ -1658,15 +2310,15 @@ var exports_list = {};
|
|
|
1658
2310
|
__export(exports_list, {
|
|
1659
2311
|
default: () => list_default
|
|
1660
2312
|
});
|
|
1661
|
-
import { existsSync as
|
|
1662
|
-
import { join as
|
|
1663
|
-
var
|
|
2313
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
|
|
2314
|
+
import { join as join5 } from "path";
|
|
2315
|
+
var import_picocolors9, list_default;
|
|
1664
2316
|
var init_list = __esm(() => {
|
|
1665
2317
|
init_dist();
|
|
1666
2318
|
init_out();
|
|
1667
2319
|
init_journal_config();
|
|
1668
2320
|
init_journal_parse();
|
|
1669
|
-
|
|
2321
|
+
import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
1670
2322
|
list_default = defineCommand({
|
|
1671
2323
|
meta: {
|
|
1672
2324
|
name: "list",
|
|
@@ -1700,15 +2352,15 @@ var init_list = __esm(() => {
|
|
|
1700
2352
|
project = sanitizeProjectName(project);
|
|
1701
2353
|
}
|
|
1702
2354
|
if (!project) {
|
|
1703
|
-
ui.write(`${
|
|
2355
|
+
ui.write(`${import_picocolors9.default.yellow("\u26A0")} ${import_picocolors9.default.yellow("No project mapping found.")}
|
|
1704
2356
|
|
|
1705
|
-
` + `Run ${
|
|
2357
|
+
` + `Run ${import_picocolors9.default.dim(import_picocolors9.default.gray("dora init"))} (or ${import_picocolors9.default.dim(import_picocolors9.default.gray("doraval journal init"))}) first, or pass ${import_picocolors9.default.dim(import_picocolors9.default.gray("--project <name>"))}.`);
|
|
1706
2358
|
process.exit(1);
|
|
1707
2359
|
}
|
|
1708
2360
|
const journalRepo = config?.journal.repo ?? "(unknown)";
|
|
1709
2361
|
const journalsDir = getJournalsDir();
|
|
1710
|
-
const projectFile =
|
|
1711
|
-
const globalFile =
|
|
2362
|
+
const projectFile = join5(journalsDir, `${project}.md`);
|
|
2363
|
+
const globalFile = join5(journalsDir, "global.md");
|
|
1712
2364
|
let raw = "";
|
|
1713
2365
|
try {
|
|
1714
2366
|
raw = await Bun.file(projectFile).text();
|
|
@@ -1722,10 +2374,10 @@ var init_list = __esm(() => {
|
|
|
1722
2374
|
let staged = [];
|
|
1723
2375
|
try {
|
|
1724
2376
|
const pdir = getPendingProjectDir(project);
|
|
1725
|
-
if (
|
|
1726
|
-
const files =
|
|
2377
|
+
if (existsSync6(pdir)) {
|
|
2378
|
+
const files = readdirSync2(pdir).filter((f) => f.endsWith(".md") && f !== ".gitkeep");
|
|
1727
2379
|
const stagedResults = await Promise.all(files.map(async (f) => {
|
|
1728
|
-
const txt = await Bun.file(
|
|
2380
|
+
const txt = await Bun.file(join5(pdir, f)).text();
|
|
1729
2381
|
const parsed = parseJournalEntries(txt);
|
|
1730
2382
|
return parsed.map((e) => ({ ...e, _staged: true }));
|
|
1731
2383
|
}));
|
|
@@ -1737,7 +2389,7 @@ var init_list = __esm(() => {
|
|
|
1737
2389
|
return;
|
|
1738
2390
|
}
|
|
1739
2391
|
ui.write(`
|
|
1740
|
-
${
|
|
2392
|
+
${import_picocolors9.default.bold(import_picocolors9.default.white("dora journal list"))} \u2014 ${import_picocolors9.default.white(project)} ${import_picocolors9.default.dim(import_picocolors9.default.gray(`(from ${journalRepo})`))}
|
|
1741
2393
|
`);
|
|
1742
2394
|
const hasStaged = staged.length > 0;
|
|
1743
2395
|
const hasCommitted = allEntries.length > 0;
|
|
@@ -1751,46 +2403,46 @@ var init_list = __esm(() => {
|
|
|
1751
2403
|
}
|
|
1752
2404
|
if (dups.length > 0) {
|
|
1753
2405
|
const uniqueDups = [...new Set(dups)];
|
|
1754
|
-
ui.write(` ${
|
|
2406
|
+
ui.write(` ${import_picocolors9.default.yellow("\u26A0")} ${import_picocolors9.default.yellow("Duplicate titles in this view (clean in your journal repo + update):")} ${uniqueDups.map((t) => import_picocolors9.default.yellow(`"${t}"`)).join(", ")}
|
|
1755
2407
|
`);
|
|
1756
2408
|
}
|
|
1757
2409
|
if (!hasStaged && !hasCommitted) {
|
|
1758
|
-
ui.write(` ${
|
|
2410
|
+
ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray("No active entries found for"))} ${import_picocolors9.default.bold(import_picocolors9.default.white(project))}.
|
|
1759
2411
|
`);
|
|
1760
|
-
ui.write(` Journal repo: ${
|
|
1761
|
-
ui.write(` Local file: ${
|
|
2412
|
+
ui.write(` Journal repo: ${import_picocolors9.default.dim(import_picocolors9.default.gray(journalRepo))}`);
|
|
2413
|
+
ui.write(` Local file: ${import_picocolors9.default.dim(import_picocolors9.default.gray(projectFile))}
|
|
1762
2414
|
`);
|
|
1763
|
-
ui.write(` ${
|
|
1764
|
-
` + ` Use ${
|
|
1765
|
-
` + ` They will be staged locally until you run ${
|
|
2415
|
+
ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray("This is normal for a freshly initialized project."))}
|
|
2416
|
+
` + ` Use ${import_picocolors9.default.dim(import_picocolors9.default.gray("dora journal add"))} to propose decisions.
|
|
2417
|
+
` + ` They will be staged locally until you run ${import_picocolors9.default.dim(import_picocolors9.default.gray("dora journal sync"))}.
|
|
1766
2418
|
`);
|
|
1767
|
-
ui.write(` If you expect content, try: ${
|
|
2419
|
+
ui.write(` If you expect content, try: ${import_picocolors9.default.dim(import_picocolors9.default.gray(`dora journal update`))}
|
|
1768
2420
|
`);
|
|
1769
2421
|
return;
|
|
1770
2422
|
}
|
|
1771
2423
|
function printEntry(entry) {
|
|
1772
2424
|
const pb = entry.pushback ?? 0;
|
|
1773
|
-
let pbColor =
|
|
2425
|
+
let pbColor = import_picocolors9.default.green;
|
|
1774
2426
|
if (pb >= 7)
|
|
1775
|
-
pbColor =
|
|
2427
|
+
pbColor = import_picocolors9.default.red;
|
|
1776
2428
|
else if (pb >= 4)
|
|
1777
|
-
pbColor =
|
|
1778
|
-
const tagsStr = (entry.tags || []).join(", ") ||
|
|
1779
|
-
const statusNote = entry.status !== "active" ?
|
|
1780
|
-
const stagedNote = entry._staged ?
|
|
1781
|
-
ui.write(` ${pbColor(String(pb).padStart(2))} ${
|
|
1782
|
-
ui.write(` ${
|
|
1783
|
-
const by = entry.author?.startsWith("agent:") ?
|
|
1784
|
-
ui.write(` ${
|
|
2429
|
+
pbColor = import_picocolors9.default.yellow;
|
|
2430
|
+
const tagsStr = (entry.tags || []).join(", ") || import_picocolors9.default.dim("(none)");
|
|
2431
|
+
const statusNote = entry.status !== "active" ? import_picocolors9.default.dim(` [${entry.status}]`) : "";
|
|
2432
|
+
const stagedNote = entry._staged ? import_picocolors9.default.dim(" (staged)") : "";
|
|
2433
|
+
ui.write(` ${pbColor(String(pb).padStart(2))} ${import_picocolors9.default.bold(import_picocolors9.default.white(entry.title))}${statusNote}${stagedNote}`);
|
|
2434
|
+
ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray("tags:"))} ${import_picocolors9.default.gray(tagsStr)}`);
|
|
2435
|
+
const by = entry.author?.startsWith("agent:") ? import_picocolors9.default.cyan(entry.author) : entry.author || "human";
|
|
2436
|
+
ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray("by:"))} ${import_picocolors9.default.gray(by)} ${import_picocolors9.default.dim(import_picocolors9.default.gray("on"))} ${import_picocolors9.default.gray(entry.date)}`);
|
|
1785
2437
|
const rat = (entry.rationale || "").replace(/\s+/g, " ").trim();
|
|
1786
2438
|
if (rat) {
|
|
1787
|
-
const preview = rat.length > 88 ? rat.slice(0, 85) +
|
|
1788
|
-
ui.write(` ${
|
|
2439
|
+
const preview = rat.length > 88 ? rat.slice(0, 85) + import_picocolors9.default.dim(import_picocolors9.default.gray("\u2026")) : rat;
|
|
2440
|
+
ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray(preview))}`);
|
|
1789
2441
|
}
|
|
1790
2442
|
ui.write("");
|
|
1791
2443
|
}
|
|
1792
2444
|
if (hasStaged) {
|
|
1793
|
-
ui.write(` ${
|
|
2445
|
+
ui.write(` ${import_picocolors9.default.yellow("\u25CF")} ${import_picocolors9.default.bold(import_picocolors9.default.white("Staged / pending"))} (not yet in remote; run ${import_picocolors9.default.dim(import_picocolors9.default.gray("dora journal sync"))} to publish):
|
|
1794
2446
|
`);
|
|
1795
2447
|
for (const entry of staged) {
|
|
1796
2448
|
printEntry(entry);
|
|
@@ -1800,7 +2452,7 @@ var init_list = __esm(() => {
|
|
|
1800
2452
|
}
|
|
1801
2453
|
if (hasCommitted) {
|
|
1802
2454
|
if (hasStaged) {
|
|
1803
|
-
ui.write(` ${
|
|
2455
|
+
ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray("Committed (from local cache):"))}
|
|
1804
2456
|
`);
|
|
1805
2457
|
}
|
|
1806
2458
|
for (const entry of allEntries) {
|
|
@@ -1808,7 +2460,7 @@ var init_list = __esm(() => {
|
|
|
1808
2460
|
}
|
|
1809
2461
|
}
|
|
1810
2462
|
const totalShown = staged.length + allEntries.length;
|
|
1811
|
-
ui.write(` ${
|
|
2463
|
+
ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray(`${totalShown} entries shown from ${journalRepo}.`))}
|
|
1812
2464
|
`);
|
|
1813
2465
|
process.exit(0);
|
|
1814
2466
|
}
|
|
@@ -1821,8 +2473,8 @@ __export(exports_context, {
|
|
|
1821
2473
|
generateJournalContext: () => generateJournalContext,
|
|
1822
2474
|
default: () => context_default
|
|
1823
2475
|
});
|
|
1824
|
-
import { existsSync as
|
|
1825
|
-
import { join as
|
|
2476
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2477
|
+
import { join as join6, resolve as resolvePath } from "path";
|
|
1826
2478
|
function truncate(text, max = 180) {
|
|
1827
2479
|
const clean = text.replace(/\s+/g, " ").trim();
|
|
1828
2480
|
if (clean.length <= max)
|
|
@@ -1900,7 +2552,7 @@ function generateJournalContext(entries, project, opts = {}) {
|
|
|
1900
2552
|
async function appendOrUpdateJournalBlock(target, contextText, project, useReference) {
|
|
1901
2553
|
const absTarget = resolvePath(process.cwd(), target);
|
|
1902
2554
|
let original = "";
|
|
1903
|
-
if (
|
|
2555
|
+
if (existsSync7(absTarget)) {
|
|
1904
2556
|
original = await Bun.file(absTarget).text();
|
|
1905
2557
|
}
|
|
1906
2558
|
let blockContent;
|
|
@@ -1942,22 +2594,22 @@ async function appendOrUpdateJournalBlock(target, contextText, project, useRefer
|
|
|
1942
2594
|
`;
|
|
1943
2595
|
}
|
|
1944
2596
|
await Bun.write(absTarget, updated);
|
|
1945
|
-
const action =
|
|
2597
|
+
const action = existsSync7(absTarget) && startIdx !== -1 ? "Updated" : "Added";
|
|
1946
2598
|
ui.write(`
|
|
1947
|
-
${
|
|
2599
|
+
${import_picocolors10.default.green("\u2713")} ${action} journal decisions section in ${import_picocolors10.default.white(target)}`);
|
|
1948
2600
|
if (useReference) {
|
|
1949
|
-
ui.write(` ${
|
|
2601
|
+
ui.write(` ${import_picocolors10.default.dim("Using @import references (full files will be loaded by Claude).")}`);
|
|
1950
2602
|
} else {
|
|
1951
|
-
ui.write(` ${
|
|
2603
|
+
ui.write(` ${import_picocolors10.default.dim("Embedded compact decisions (low noise).")}`);
|
|
1952
2604
|
}
|
|
1953
2605
|
}
|
|
1954
|
-
var
|
|
2606
|
+
var import_picocolors10, JOURNAL_BLOCK_START = "<!-- doraval-journal:start -->", JOURNAL_BLOCK_END = "<!-- doraval-journal:end -->", context_default;
|
|
1955
2607
|
var init_context = __esm(() => {
|
|
1956
2608
|
init_dist();
|
|
1957
2609
|
init_out();
|
|
1958
2610
|
init_journal_config();
|
|
1959
2611
|
init_journal_parse();
|
|
1960
|
-
|
|
2612
|
+
import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
1961
2613
|
context_default = defineCommand({
|
|
1962
2614
|
meta: {
|
|
1963
2615
|
name: "context",
|
|
@@ -2029,16 +2681,16 @@ var init_context = __esm(() => {
|
|
|
2029
2681
|
}
|
|
2030
2682
|
const journalsDir = getJournalsDir();
|
|
2031
2683
|
const entries = [];
|
|
2032
|
-
const globalPath =
|
|
2033
|
-
if (
|
|
2684
|
+
const globalPath = join6(journalsDir, "global.md");
|
|
2685
|
+
if (existsSync7(globalPath)) {
|
|
2034
2686
|
try {
|
|
2035
2687
|
const raw = await Bun.file(globalPath).text();
|
|
2036
2688
|
entries.push(...parseJournalEntries(raw));
|
|
2037
2689
|
} catch {}
|
|
2038
2690
|
}
|
|
2039
2691
|
if (project) {
|
|
2040
|
-
const projectPath =
|
|
2041
|
-
if (
|
|
2692
|
+
const projectPath = join6(journalsDir, `${project}.md`);
|
|
2693
|
+
if (existsSync7(projectPath)) {
|
|
2042
2694
|
try {
|
|
2043
2695
|
const raw = await Bun.file(projectPath).text();
|
|
2044
2696
|
entries.push(...parseJournalEntries(raw));
|
|
@@ -2075,17 +2727,17 @@ __export(exports_hook, {
|
|
|
2075
2727
|
default: () => hook_default,
|
|
2076
2728
|
addHook: () => addHook
|
|
2077
2729
|
});
|
|
2078
|
-
import { existsSync as
|
|
2079
|
-
import { join as
|
|
2080
|
-
import { homedir as
|
|
2730
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync2, unlinkSync, rmdirSync, readdirSync as readdirSync3 } from "fs";
|
|
2731
|
+
import { join as join7, dirname } from "path";
|
|
2732
|
+
import { homedir as homedir3 } from "os";
|
|
2081
2733
|
function getGlobalSettingsPath() {
|
|
2082
|
-
return
|
|
2734
|
+
return join7(homedir3(), ".claude", "settings.json");
|
|
2083
2735
|
}
|
|
2084
2736
|
function getLocalHooksPath() {
|
|
2085
|
-
return
|
|
2737
|
+
return join7(process.cwd(), "hooks", "hooks.json");
|
|
2086
2738
|
}
|
|
2087
2739
|
async function readJson(file) {
|
|
2088
|
-
if (!
|
|
2740
|
+
if (!existsSync8(file))
|
|
2089
2741
|
return {};
|
|
2090
2742
|
try {
|
|
2091
2743
|
const raw = await Bun.file(file).text();
|
|
@@ -2096,7 +2748,7 @@ async function readJson(file) {
|
|
|
2096
2748
|
}
|
|
2097
2749
|
async function writeJson(file, data) {
|
|
2098
2750
|
const dir = dirname(file);
|
|
2099
|
-
if (!
|
|
2751
|
+
if (!existsSync8(dir)) {
|
|
2100
2752
|
mkdirSync2(dir, { recursive: true });
|
|
2101
2753
|
}
|
|
2102
2754
|
await Bun.write(file, JSON.stringify(data, null, 2) + `
|
|
@@ -2124,7 +2776,7 @@ async function addHook(file) {
|
|
|
2124
2776
|
return { changed: true, path: file };
|
|
2125
2777
|
}
|
|
2126
2778
|
async function removeHook(file) {
|
|
2127
|
-
if (!
|
|
2779
|
+
if (!existsSync8(file))
|
|
2128
2780
|
return { changed: false, path: file };
|
|
2129
2781
|
const original = await readJson(file);
|
|
2130
2782
|
const config = JSON.parse(JSON.stringify(original));
|
|
@@ -2147,13 +2799,13 @@ async function removeHook(file) {
|
|
|
2147
2799
|
const changed = JSON.stringify(config) !== JSON.stringify(original);
|
|
2148
2800
|
if (changed) {
|
|
2149
2801
|
const isEmpty = !config || Object.keys(config).length === 0;
|
|
2150
|
-
if (isEmpty &&
|
|
2802
|
+
if (isEmpty && existsSync8(file)) {
|
|
2151
2803
|
try {
|
|
2152
2804
|
unlinkSync(file);
|
|
2153
2805
|
} catch {}
|
|
2154
2806
|
try {
|
|
2155
2807
|
const dir = dirname(file);
|
|
2156
|
-
if (
|
|
2808
|
+
if (existsSync8(dir) && readdirSync3(dir).length === 0)
|
|
2157
2809
|
rmdirSync(dir);
|
|
2158
2810
|
} catch {}
|
|
2159
2811
|
} else {
|
|
@@ -2283,15 +2935,15 @@ var exports_update = {};
|
|
|
2283
2935
|
__export(exports_update, {
|
|
2284
2936
|
default: () => update_default
|
|
2285
2937
|
});
|
|
2286
|
-
import { existsSync as
|
|
2287
|
-
import { join as
|
|
2288
|
-
var
|
|
2938
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2939
|
+
import { join as join8 } from "path";
|
|
2940
|
+
var import_picocolors11, update_default;
|
|
2289
2941
|
var init_update = __esm(() => {
|
|
2290
2942
|
init_dist();
|
|
2291
2943
|
init_out();
|
|
2292
2944
|
init_journal_config();
|
|
2293
2945
|
init_journal_remote();
|
|
2294
|
-
|
|
2946
|
+
import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
2295
2947
|
update_default = defineCommand({
|
|
2296
2948
|
meta: {
|
|
2297
2949
|
name: "update",
|
|
@@ -2312,30 +2964,30 @@ var init_update = __esm(() => {
|
|
|
2312
2964
|
async run({ args }) {
|
|
2313
2965
|
const ghCheck = ensureGhCli();
|
|
2314
2966
|
if (!ghCheck.ok) {
|
|
2315
|
-
ui.write(` ${
|
|
2967
|
+
ui.write(` ${import_picocolors11.default.red("\u2717")} ${import_picocolors11.default.white("The GitHub CLI (")}${import_picocolors11.default.bold("gh")}${import_picocolors11.default.white(") is not installed.")}
|
|
2316
2968
|
`);
|
|
2317
|
-
ui.write(` doraval uses ${
|
|
2969
|
+
ui.write(` doraval uses ${import_picocolors11.default.bold("gh")} to fetch and sync journal files with GitHub.
|
|
2318
2970
|
`);
|
|
2319
2971
|
ui.write(` Install it:
|
|
2320
2972
|
`);
|
|
2321
|
-
ui.write(` macOS: ${
|
|
2322
|
-
ui.write(` Linux: ${
|
|
2323
|
-
ui.write(` Windows: ${
|
|
2973
|
+
ui.write(` macOS: ${import_picocolors11.default.dim("brew install gh")}`);
|
|
2974
|
+
ui.write(` Linux: ${import_picocolors11.default.dim("https://github.com/cli/cli/blob/trunk/docs/install_linux.md")}`);
|
|
2975
|
+
ui.write(` Windows: ${import_picocolors11.default.dim("winget install --id GitHub.cli")}
|
|
2324
2976
|
`);
|
|
2325
|
-
ui.write(` Then authenticate: ${
|
|
2977
|
+
ui.write(` Then authenticate: ${import_picocolors11.default.dim("gh auth login")}
|
|
2326
2978
|
`);
|
|
2327
2979
|
process.exit(1);
|
|
2328
2980
|
}
|
|
2329
2981
|
const config = await readConfig();
|
|
2330
2982
|
if (!config?.journal.repo) {
|
|
2331
|
-
ui.write(`${
|
|
2983
|
+
ui.write(`${import_picocolors11.default.red("\u2717")} No journal repo configured. Run ${import_picocolors11.default.dim("dora init")} (or ${import_picocolors11.default.dim("doraval journal init")}) first.`);
|
|
2332
2984
|
process.exit(1);
|
|
2333
2985
|
}
|
|
2334
2986
|
const journalRepo = config.journal.repo;
|
|
2335
2987
|
ensureDoravalDirs();
|
|
2336
2988
|
const journalsDir = getJournalsDir();
|
|
2337
2989
|
ui.write(`
|
|
2338
|
-
${
|
|
2990
|
+
${import_picocolors11.default.bold(import_picocolors11.default.white("dora journal update"))} \u2014 ${import_picocolors11.default.dim(import_picocolors11.default.gray(journalRepo))}
|
|
2339
2991
|
`);
|
|
2340
2992
|
const projectsToUpdate = [];
|
|
2341
2993
|
if (args.all) {
|
|
@@ -2353,19 +3005,19 @@ var init_update = __esm(() => {
|
|
|
2353
3005
|
try {
|
|
2354
3006
|
projectsToUpdate.push(sanitizeProjectName(project));
|
|
2355
3007
|
} catch {
|
|
2356
|
-
ui.write(`${
|
|
3008
|
+
ui.write(`${import_picocolors11.default.red("\u2717")} Invalid project name: ${project}`);
|
|
2357
3009
|
process.exit(1);
|
|
2358
3010
|
}
|
|
2359
3011
|
}
|
|
2360
3012
|
}
|
|
2361
|
-
const globalLocal =
|
|
3013
|
+
const globalLocal = join8(journalsDir, "global.md");
|
|
2362
3014
|
const refreshGlobalRes = await refreshLocalJournalFile(journalRepo, "global.md", globalLocal);
|
|
2363
3015
|
let gotGlobal;
|
|
2364
3016
|
if (!refreshGlobalRes.ok) {
|
|
2365
3017
|
if (refreshGlobalRes.isNotFound) {
|
|
2366
3018
|
gotGlobal = false;
|
|
2367
3019
|
} else {
|
|
2368
|
-
ui.write(`${
|
|
3020
|
+
ui.write(`${import_picocolors11.default.red("\u2717")} Failed to fetch global.md from ${journalRepo}:`);
|
|
2369
3021
|
ui.write(refreshGlobalRes.error);
|
|
2370
3022
|
process.exit(1);
|
|
2371
3023
|
}
|
|
@@ -2373,33 +3025,33 @@ var init_update = __esm(() => {
|
|
|
2373
3025
|
gotGlobal = refreshGlobalRes.value;
|
|
2374
3026
|
}
|
|
2375
3027
|
if (gotGlobal) {
|
|
2376
|
-
ui.write(` ${
|
|
3028
|
+
ui.write(` ${import_picocolors11.default.green("\u2713")} global.md`);
|
|
2377
3029
|
} else {
|
|
2378
|
-
ui.write(` ${
|
|
3030
|
+
ui.write(` ${import_picocolors11.default.dim("\xB7")} global.md ${import_picocolors11.default.dim("(not present on remote)")}`);
|
|
2379
3031
|
}
|
|
2380
3032
|
if (projectsToUpdate.length === 0) {
|
|
2381
3033
|
if (args.all) {
|
|
2382
3034
|
ui.write(`
|
|
2383
|
-
${
|
|
3035
|
+
${import_picocolors11.default.dim(import_picocolors11.default.gray("No projects registered."))}
|
|
2384
3036
|
`);
|
|
2385
3037
|
} else {
|
|
2386
3038
|
ui.write(`
|
|
2387
|
-
${
|
|
2388
|
-
` + ` Run ${
|
|
3039
|
+
${import_picocolors11.default.yellow("\u26A0")} No project mapping found.
|
|
3040
|
+
` + ` Run ${import_picocolors11.default.dim("dora init")} or pass ${import_picocolors11.default.dim("--project <name>")} / ${import_picocolors11.default.dim("--all")}.
|
|
2389
3041
|
`);
|
|
2390
3042
|
}
|
|
2391
3043
|
return;
|
|
2392
3044
|
}
|
|
2393
3045
|
for (const project of projectsToUpdate) {
|
|
2394
3046
|
const remotePath = `projects/${project}.md`;
|
|
2395
|
-
const localPath =
|
|
3047
|
+
const localPath = join8(journalsDir, `${project}.md`);
|
|
2396
3048
|
const refreshRes = await refreshLocalJournalFile(journalRepo, remotePath, localPath);
|
|
2397
3049
|
let got;
|
|
2398
3050
|
if (!refreshRes.ok) {
|
|
2399
3051
|
if (refreshRes.isNotFound) {
|
|
2400
3052
|
got = false;
|
|
2401
3053
|
} else {
|
|
2402
|
-
ui.write(`${
|
|
3054
|
+
ui.write(`${import_picocolors11.default.red("\u2717")} Failed to fetch ${remotePath} from ${journalRepo}:`);
|
|
2403
3055
|
ui.write(refreshRes.error);
|
|
2404
3056
|
process.exit(1);
|
|
2405
3057
|
}
|
|
@@ -2407,10 +3059,10 @@ var init_update = __esm(() => {
|
|
|
2407
3059
|
got = refreshRes.value;
|
|
2408
3060
|
}
|
|
2409
3061
|
if (got) {
|
|
2410
|
-
ui.write(` ${
|
|
3062
|
+
ui.write(` ${import_picocolors11.default.green("\u2713")} ${remotePath}`);
|
|
2411
3063
|
} else {
|
|
2412
|
-
ui.write(` ${
|
|
2413
|
-
if (!
|
|
3064
|
+
ui.write(` ${import_picocolors11.default.dim("\xB7")} ${remotePath} ${import_picocolors11.default.dim("(not present on remote \u2014 will be created on first sync)")}`);
|
|
3065
|
+
if (!existsSync9(localPath)) {
|
|
2414
3066
|
await Bun.write(localPath, `# ${project} Journal
|
|
2415
3067
|
|
|
2416
3068
|
Project-specific decisions.
|
|
@@ -2420,7 +3072,7 @@ Project-specific decisions.
|
|
|
2420
3072
|
}
|
|
2421
3073
|
const summary = args.all && projectsToUpdate.length > 1 ? `${projectsToUpdate.length} projects + global` : projectsToUpdate.length === 1 ? projectsToUpdate[0] : "journals";
|
|
2422
3074
|
ui.write(`
|
|
2423
|
-
${
|
|
3075
|
+
${import_picocolors11.default.dim(import_picocolors11.default.gray("Local cache refreshed for"))} ${import_picocolors11.default.bold(import_picocolors11.default.white(summary))}.
|
|
2424
3076
|
`);
|
|
2425
3077
|
}
|
|
2426
3078
|
});
|
|
@@ -2484,140 +3136,21 @@ var init_journal_validate = __esm(() => {
|
|
|
2484
3136
|
// src/cli/commands/journal/add.ts
|
|
2485
3137
|
var exports_add = {};
|
|
2486
3138
|
__export(exports_add, {
|
|
2487
|
-
default: () => add_default
|
|
2488
|
-
buildAgentArgv: () => buildAgentArgv
|
|
3139
|
+
default: () => add_default
|
|
2489
3140
|
});
|
|
2490
|
-
import { existsSync as
|
|
2491
|
-
import { join as
|
|
2492
|
-
var {spawnSync: spawnSync2 } = globalThis.Bun;
|
|
2493
|
-
function buildAgentArgv(template, promptText) {
|
|
2494
|
-
const marker = "__DORA_PROMPT__";
|
|
2495
|
-
const substituted = template.replace("{{prompt}}", marker);
|
|
2496
|
-
const rawParts = substituted.split(/\s+/).filter(Boolean);
|
|
2497
|
-
return rawParts.map((part) => {
|
|
2498
|
-
let cleaned = part;
|
|
2499
|
-
if (cleaned.startsWith('"') && cleaned.endsWith('"'))
|
|
2500
|
-
cleaned = cleaned.slice(1, -1);
|
|
2501
|
-
if (cleaned.startsWith("'") && cleaned.endsWith("'"))
|
|
2502
|
-
cleaned = cleaned.slice(1, -1);
|
|
2503
|
-
if (cleaned === marker) {
|
|
2504
|
-
return promptText;
|
|
2505
|
-
}
|
|
2506
|
-
return cleaned;
|
|
2507
|
-
});
|
|
2508
|
-
}
|
|
2509
|
-
async function invokeConfiguredAgentForEntry(decisionText, agentCfg) {
|
|
2510
|
-
if (!agentCfg || !agentCfg.command)
|
|
2511
|
-
return null;
|
|
2512
|
-
const scaffold = `Raw user capture (a decision, observation, or useful note that just happened): "${decisionText}"
|
|
2513
|
-
|
|
2514
|
-
Turn this into a clean journal entry. Infer the core decision or note even if the input is phrased as a todo or reminder. Be professional and concise.
|
|
2515
|
-
|
|
2516
|
-
**CRITICAL INSTRUCTIONS (follow exactly):**
|
|
2517
|
-
- Output *ONLY* a single valid JSON object. Nothing before it, nothing after it, no markdown fences, no explanations, no extra text.
|
|
2518
|
-
- The JSON must have exactly these keys (use the suggested values as starting point but improve them):
|
|
2519
|
-
{
|
|
2520
|
-
"title": "Short, scannable, professional title (past tense or present perfect, max ~80 chars)",
|
|
2521
|
-
"pushback": 4,
|
|
2522
|
-
"tags": ["cli", "ux"],
|
|
2523
|
-
"rationale": "2-5 sentences explaining context and implications (or the note content).",
|
|
2524
|
-
"author": "agent:claude-code"
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
If you cannot produce exactly this, output the JSON with the best you can and set "author" to "agent:claude-code" anyway.`;
|
|
2528
|
-
const template = agentCfg.prompt_template || '-p "{{prompt}}" --output-format json';
|
|
2529
|
-
const extraArgs = buildAgentArgv(template, scaffold);
|
|
2530
|
-
const shortTemplate = (agentCfg.prompt_template || '-p "{{prompt}}" --output-format json').slice(0, 80);
|
|
2531
|
-
ui.write(` ${import_picocolors9.default.dim(`\u2192 ${agentCfg.command} ${shortTemplate}...`)}`);
|
|
2532
|
-
try {
|
|
2533
|
-
const result = spawnSync2([agentCfg.command, ...extraArgs], {
|
|
2534
|
-
stdout: "pipe",
|
|
2535
|
-
stderr: "pipe"
|
|
2536
|
-
});
|
|
2537
|
-
const stdout = result.stdout.toString().trim();
|
|
2538
|
-
const stderr = result.stderr.toString().trim();
|
|
2539
|
-
if (result.exitCode !== 0) {
|
|
2540
|
-
ui.write(` ${import_picocolors9.default.yellow("\u26A0")} Configured agent (${agentCfg.command}) exited with code ${result.exitCode}. Falling back to defaults.`);
|
|
2541
|
-
if (stderr)
|
|
2542
|
-
ui.write(` ${import_picocolors9.default.dim("stderr:")}
|
|
2543
|
-
${stderr.slice(0, 800)}`);
|
|
2544
|
-
if (stdout)
|
|
2545
|
-
ui.write(` ${import_picocolors9.default.dim("stdout:")}
|
|
2546
|
-
${stdout.slice(0, 400)}`);
|
|
2547
|
-
return null;
|
|
2548
|
-
}
|
|
2549
|
-
let candidates = [];
|
|
2550
|
-
let jsonMatch = stdout.match(/\{[\s\S]*\}/);
|
|
2551
|
-
if (jsonMatch) {
|
|
2552
|
-
try {
|
|
2553
|
-
candidates.push(JSON.parse(jsonMatch[0]));
|
|
2554
|
-
} catch {}
|
|
2555
|
-
}
|
|
2556
|
-
const allMatches = stdout.match(/\{[\s\S]*?\}(?=\s*(?:\{|$))/g) || [];
|
|
2557
|
-
for (const m of allMatches) {
|
|
2558
|
-
try {
|
|
2559
|
-
const p = JSON.parse(m);
|
|
2560
|
-
candidates.push(p);
|
|
2561
|
-
} catch {}
|
|
2562
|
-
}
|
|
2563
|
-
let parsed = null;
|
|
2564
|
-
for (const c of candidates) {
|
|
2565
|
-
if (c && typeof c === "object" && (c.title || c.rationale)) {
|
|
2566
|
-
parsed = c;
|
|
2567
|
-
break;
|
|
2568
|
-
}
|
|
2569
|
-
}
|
|
2570
|
-
if (!parsed) {
|
|
2571
|
-
for (const c of candidates) {
|
|
2572
|
-
if (c && typeof c === "object" && c.result) {
|
|
2573
|
-
let inner = c.result;
|
|
2574
|
-
if (typeof inner === "string") {
|
|
2575
|
-
try {
|
|
2576
|
-
inner = JSON.parse(inner);
|
|
2577
|
-
} catch {}
|
|
2578
|
-
}
|
|
2579
|
-
if (inner && typeof inner === "object" && (inner.title || inner.rationale)) {
|
|
2580
|
-
parsed = inner;
|
|
2581
|
-
break;
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
if (!parsed) {
|
|
2587
|
-
parsed = candidates[0] || null;
|
|
2588
|
-
}
|
|
2589
|
-
if (!parsed || typeof parsed !== "object") {
|
|
2590
|
-
ui.write(` ${import_picocolors9.default.yellow("\u26A0")} Agent produced output but no usable JSON was found. Falling back.`);
|
|
2591
|
-
ui.write(` ${import_picocolors9.default.dim("stdout (first 700 chars):")}
|
|
2592
|
-
${stdout.slice(0, 700)}`);
|
|
2593
|
-
if (stderr)
|
|
2594
|
-
ui.write(` ${import_picocolors9.default.dim("stderr:")}
|
|
2595
|
-
${stderr.slice(0, 500)}`);
|
|
2596
|
-
return null;
|
|
2597
|
-
}
|
|
2598
|
-
if (!parsed.title && !parsed.rationale) {
|
|
2599
|
-
ui.write(` ${import_picocolors9.default.yellow("\u26A0")} Agent returned JSON without expected fields (title/rationale). Using defaults.`);
|
|
2600
|
-
ui.write(` ${import_picocolors9.default.dim("parsed keys:")} ${Object.keys(parsed).join(", ")}`);
|
|
2601
|
-
ui.write(` ${import_picocolors9.default.dim("stdout (truncated):")}
|
|
2602
|
-
${stdout.slice(0, 600)}`);
|
|
2603
|
-
return null;
|
|
2604
|
-
}
|
|
2605
|
-
return parsed;
|
|
2606
|
-
} catch (e) {
|
|
2607
|
-
ui.write(` ${import_picocolors9.default.yellow("\u26A0")} Failed to invoke configured agent (${agentCfg.command}): ${e.message}. Using defaults.`);
|
|
2608
|
-
return null;
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
3141
|
+
import { existsSync as existsSync10 } from "fs";
|
|
3142
|
+
import { join as join9 } from "path";
|
|
2611
3143
|
function slugify(title) {
|
|
2612
3144
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "untitled";
|
|
2613
3145
|
}
|
|
2614
|
-
var
|
|
3146
|
+
var import_picocolors12, add_default;
|
|
2615
3147
|
var init_add = __esm(() => {
|
|
2616
3148
|
init_dist();
|
|
2617
3149
|
init_out();
|
|
2618
3150
|
init_journal_config();
|
|
2619
3151
|
init_journal_validate();
|
|
2620
|
-
|
|
3152
|
+
init_agent_invoke();
|
|
3153
|
+
import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
2621
3154
|
add_default = defineCommand({
|
|
2622
3155
|
meta: {
|
|
2623
3156
|
name: "add",
|
|
@@ -2692,9 +3225,9 @@ var init_add = __esm(() => {
|
|
|
2692
3225
|
project = sanitizeProjectName(project);
|
|
2693
3226
|
}
|
|
2694
3227
|
if (!project) {
|
|
2695
|
-
ui.write(`${
|
|
3228
|
+
ui.write(`${import_picocolors12.default.yellow("\u26A0")} No project mapping found.
|
|
2696
3229
|
|
|
2697
|
-
` + `Run ${
|
|
3230
|
+
` + `Run ${import_picocolors12.default.dim("dora init")} (or ${import_picocolors12.default.dim("doraval journal init")}) first, or pass ${import_picocolors12.default.dim("--project <name>")}.`);
|
|
2698
3231
|
process.exit(1);
|
|
2699
3232
|
}
|
|
2700
3233
|
let title;
|
|
@@ -2732,7 +3265,7 @@ var init_add = __esm(() => {
|
|
|
2732
3265
|
if (parsed.date)
|
|
2733
3266
|
date = String(parsed.date);
|
|
2734
3267
|
} catch (e) {
|
|
2735
|
-
ui.write(`${
|
|
3268
|
+
ui.write(`${import_picocolors12.default.red("\u2717")} Failed to parse --json input: ${e.message}`);
|
|
2736
3269
|
process.exit(1);
|
|
2737
3270
|
}
|
|
2738
3271
|
}
|
|
@@ -2741,7 +3274,7 @@ var init_add = __esm(() => {
|
|
|
2741
3274
|
if (rawMdArg && !jsonInput) {
|
|
2742
3275
|
if (rawMdArg === "-" || rawMdArg === "") {
|
|
2743
3276
|
rawBody = (await new Response(Bun.stdin.stream()).text()).trim();
|
|
2744
|
-
} else if (
|
|
3277
|
+
} else if (existsSync10(rawMdArg)) {
|
|
2745
3278
|
rawBody = (await Bun.file(rawMdArg).text()).trim();
|
|
2746
3279
|
} else {
|
|
2747
3280
|
rawBody = rawMdArg.trim();
|
|
@@ -2756,7 +3289,7 @@ var init_add = __esm(() => {
|
|
|
2756
3289
|
title = (headingMatch[1] ?? "").trim();
|
|
2757
3290
|
rawBody = rawBody.replace(/^#+\s+(.+?)(?:\r?\n|$)/m, "").trimStart();
|
|
2758
3291
|
} else {
|
|
2759
|
-
ui.write(`${
|
|
3292
|
+
ui.write(`${import_picocolors12.default.red("\u2717")} --raw-markdown provided without a TITLE and without a leading '# Heading' in the markdown.`);
|
|
2760
3293
|
process.exit(1);
|
|
2761
3294
|
}
|
|
2762
3295
|
}
|
|
@@ -2796,8 +3329,24 @@ var init_add = __esm(() => {
|
|
|
2796
3329
|
agentCfg = fullConfigForAgent?.agent;
|
|
2797
3330
|
if (agentCfg) {
|
|
2798
3331
|
attemptedAgent = true;
|
|
2799
|
-
ui.write(` ${
|
|
2800
|
-
const
|
|
3332
|
+
ui.write(` ${import_picocolors12.default.dim(import_picocolors12.default.gray("(querying your configured coding agent...)"))}`);
|
|
3333
|
+
const scaffold = `Raw user capture (a decision, observation, or useful note that just happened): "${title}"
|
|
3334
|
+
|
|
3335
|
+
Turn this into a clean journal entry. Infer the core decision or note even if the input is phrased as a todo or reminder. Be professional and concise.
|
|
3336
|
+
|
|
3337
|
+
**CRITICAL INSTRUCTIONS (follow exactly):**
|
|
3338
|
+
- Output *ONLY* a single valid JSON object. Nothing before it, nothing after it, no markdown fences, no explanations, no extra text.
|
|
3339
|
+
- The JSON must have exactly these keys (use the suggested values as starting point but improve them):
|
|
3340
|
+
{
|
|
3341
|
+
"title": "Short, scannable, professional title (past tense or present perfect, max ~80 chars)",
|
|
3342
|
+
"pushback": 4,
|
|
3343
|
+
"tags": ["cli", "ux"],
|
|
3344
|
+
"rationale": "2-5 sentences explaining context and implications (or the note content).",
|
|
3345
|
+
"author": "agent:claude-code"
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
If you cannot produce exactly this, output the JSON with the best you can and set "author" to "agent:claude-code" anyway.`;
|
|
3349
|
+
const agentResult = await invokeAgent(scaffold, agentCfg, ["title", "rationale"]);
|
|
2801
3350
|
if (agentResult) {
|
|
2802
3351
|
if (agentResult.title)
|
|
2803
3352
|
title = String(agentResult.title).trim();
|
|
@@ -2829,18 +3378,18 @@ var init_add = __esm(() => {
|
|
|
2829
3378
|
};
|
|
2830
3379
|
const validation = validateEntry(entry);
|
|
2831
3380
|
if (!validation.valid) {
|
|
2832
|
-
ui.write(`${
|
|
3381
|
+
ui.write(`${import_picocolors12.default.red("\u2717")} Invalid entry:
|
|
2833
3382
|
`);
|
|
2834
3383
|
for (const err of validation.errors) {
|
|
2835
|
-
ui.write(` ${
|
|
3384
|
+
ui.write(` ${import_picocolors12.default.red("\u2022")} ${err}`);
|
|
2836
3385
|
}
|
|
2837
3386
|
process.exit(1);
|
|
2838
3387
|
}
|
|
2839
3388
|
for (const warn of validation.warnings) {
|
|
2840
3389
|
if ((warn.includes("not supplied") || warn.includes("empty")) && attemptedAgent) {} else if (warn.includes("not supplied") || warn.includes("empty")) {
|
|
2841
|
-
ui.write(`${
|
|
3390
|
+
ui.write(`${import_picocolors12.default.dim("\xB7")} ${warn}`);
|
|
2842
3391
|
} else {
|
|
2843
|
-
ui.write(`${
|
|
3392
|
+
ui.write(`${import_picocolors12.default.yellow("\u26A0")} ${warn}`);
|
|
2844
3393
|
}
|
|
2845
3394
|
}
|
|
2846
3395
|
if (!rationale) {
|
|
@@ -2860,31 +3409,31 @@ ${rationale}
|
|
|
2860
3409
|
`;
|
|
2861
3410
|
ensureDoravalDirs();
|
|
2862
3411
|
const pendingDir = getPendingProjectDir(project);
|
|
2863
|
-
if (!
|
|
2864
|
-
await Bun.write(
|
|
3412
|
+
if (!existsSync10(pendingDir)) {
|
|
3413
|
+
await Bun.write(join9(pendingDir, ".gitkeep"), "");
|
|
2865
3414
|
}
|
|
2866
3415
|
const slug = slugify(title);
|
|
2867
3416
|
const filename = `${date}-${slug}.md`;
|
|
2868
|
-
const filePath =
|
|
3417
|
+
const filePath = join9(pendingDir, filename);
|
|
2869
3418
|
await Bun.write(filePath, content);
|
|
2870
3419
|
ui.write(`
|
|
2871
|
-
${
|
|
2872
|
-
ui.write(` Project: ${
|
|
3420
|
+
${import_picocolors12.default.green("\u2713")} ${import_picocolors12.default.bold(import_picocolors12.default.white(title))}`);
|
|
3421
|
+
ui.write(` Project: ${import_picocolors12.default.white(project)} \xB7 run ${import_picocolors12.default.dim(import_picocolors12.default.gray("dora journal sync"))} to publish
|
|
2873
3422
|
`);
|
|
2874
3423
|
if (args.verbose) {
|
|
2875
|
-
const authorDisplay = author.startsWith("agent:") ?
|
|
2876
|
-
ui.write(` Pushback: ${
|
|
2877
|
-
ui.write(` Tags: ${
|
|
3424
|
+
const authorDisplay = author.startsWith("agent:") ? import_picocolors12.default.cyan(author) : author;
|
|
3425
|
+
ui.write(` Pushback: ${import_picocolors12.default.white(String(pushback))}`);
|
|
3426
|
+
ui.write(` Tags: ${import_picocolors12.default.gray(tags.join(", ") || import_picocolors12.default.dim("(none)"))}`);
|
|
2878
3427
|
ui.write(` Author: ${authorDisplay}`);
|
|
2879
|
-
ui.write(` File: ${
|
|
3428
|
+
ui.write(` File: ${import_picocolors12.default.dim(import_picocolors12.default.gray(filePath))}
|
|
2880
3429
|
`);
|
|
2881
3430
|
}
|
|
2882
3431
|
if (isThinInput && !author.startsWith("agent:")) {
|
|
2883
3432
|
if (attemptedAgent) {
|
|
2884
|
-
ui.write(` ${
|
|
3433
|
+
ui.write(` ${import_picocolors12.default.dim(import_picocolors12.default.gray("Note: agent was called but returned no usable enrichment. Edit the pending file or re-run dora init."))}
|
|
2885
3434
|
`);
|
|
2886
3435
|
} else {
|
|
2887
|
-
ui.write(` ${
|
|
3436
|
+
ui.write(` ${import_picocolors12.default.dim(import_picocolors12.default.gray("Tip: run dora init to configure an agent for auto-enrichment."))}
|
|
2888
3437
|
`);
|
|
2889
3438
|
}
|
|
2890
3439
|
}
|
|
@@ -2898,8 +3447,8 @@ var exports_sync = {};
|
|
|
2898
3447
|
__export(exports_sync, {
|
|
2899
3448
|
default: () => sync_default
|
|
2900
3449
|
});
|
|
2901
|
-
import { readdirSync as
|
|
2902
|
-
import { join as
|
|
3450
|
+
import { readdirSync as readdirSync4, existsSync as existsSync11 } from "fs";
|
|
3451
|
+
import { join as join10 } from "path";
|
|
2903
3452
|
var {spawnSync: spawnSync3 } = globalThis.Bun;
|
|
2904
3453
|
function updateGitHubFile(repo, path, content, message, sha) {
|
|
2905
3454
|
const payload = {
|
|
@@ -2928,18 +3477,18 @@ function updateGitHubFile(repo, path, content, message, sha) {
|
|
|
2928
3477
|
stderr: "pipe"
|
|
2929
3478
|
});
|
|
2930
3479
|
if (result.exitCode !== 0) {
|
|
2931
|
-
ui.write(
|
|
3480
|
+
ui.write(import_picocolors13.default.red(`Failed to update ${path} on ${repo}:`));
|
|
2932
3481
|
ui.write(result.stderr.toString());
|
|
2933
3482
|
process.exit(1);
|
|
2934
3483
|
}
|
|
2935
3484
|
}
|
|
2936
|
-
var
|
|
3485
|
+
var import_picocolors13, sync_default;
|
|
2937
3486
|
var init_sync = __esm(() => {
|
|
2938
3487
|
init_dist();
|
|
2939
3488
|
init_out();
|
|
2940
3489
|
init_journal_config();
|
|
2941
3490
|
init_journal_remote();
|
|
2942
|
-
|
|
3491
|
+
import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
2943
3492
|
sync_default = defineCommand({
|
|
2944
3493
|
meta: {
|
|
2945
3494
|
name: "sync",
|
|
@@ -2973,70 +3522,70 @@ var init_sync = __esm(() => {
|
|
|
2973
3522
|
project = sanitizeProjectName(project);
|
|
2974
3523
|
}
|
|
2975
3524
|
if (!project) {
|
|
2976
|
-
ui.write(`${
|
|
3525
|
+
ui.write(`${import_picocolors13.default.yellow("\u26A0")} No project mapping found.
|
|
2977
3526
|
|
|
2978
|
-
` + `Run ${
|
|
3527
|
+
` + `Run ${import_picocolors13.default.dim("dora init")} (or ${import_picocolors13.default.dim("doraval journal init")}) first, or pass ${import_picocolors13.default.dim("--project <name>")}.`);
|
|
2979
3528
|
process.exit(1);
|
|
2980
3529
|
}
|
|
2981
3530
|
if (!config?.journal.repo) {
|
|
2982
|
-
ui.write(`${
|
|
3531
|
+
ui.write(`${import_picocolors13.default.red("\u2717")} No journal repo configured. Run ${import_picocolors13.default.dim("dora init")} (or ${import_picocolors13.default.dim("doraval journal init")}) first.`);
|
|
2983
3532
|
process.exit(1);
|
|
2984
3533
|
}
|
|
2985
3534
|
const ghCheck = ensureGhCli();
|
|
2986
3535
|
if (!ghCheck.ok) {
|
|
2987
|
-
ui.write(` ${
|
|
3536
|
+
ui.write(` ${import_picocolors13.default.red("\u2717")} ${import_picocolors13.default.white("The GitHub CLI (")}${import_picocolors13.default.bold("gh")}${import_picocolors13.default.white(") is not installed.")}
|
|
2988
3537
|
`);
|
|
2989
|
-
ui.write(` doraval uses ${
|
|
3538
|
+
ui.write(` doraval uses ${import_picocolors13.default.bold("gh")} to fetch and sync journal files with GitHub.
|
|
2990
3539
|
`);
|
|
2991
3540
|
ui.write(` Install it:
|
|
2992
3541
|
`);
|
|
2993
|
-
ui.write(` macOS: ${
|
|
2994
|
-
ui.write(` Linux: ${
|
|
2995
|
-
ui.write(` Windows: ${
|
|
3542
|
+
ui.write(` macOS: ${import_picocolors13.default.dim("brew install gh")}`);
|
|
3543
|
+
ui.write(` Linux: ${import_picocolors13.default.dim("https://github.com/cli/cli/blob/trunk/docs/install_linux.md")}`);
|
|
3544
|
+
ui.write(` Windows: ${import_picocolors13.default.dim("winget install --id GitHub.cli")}
|
|
2996
3545
|
`);
|
|
2997
|
-
ui.write(` Then authenticate: ${
|
|
3546
|
+
ui.write(` Then authenticate: ${import_picocolors13.default.dim("gh auth login")}
|
|
2998
3547
|
`);
|
|
2999
3548
|
process.exit(1);
|
|
3000
3549
|
}
|
|
3001
3550
|
const journalRepo = config.journal.repo;
|
|
3002
3551
|
const pendingDir = getPendingProjectDir(project);
|
|
3003
3552
|
ui.write(`
|
|
3004
|
-
${
|
|
3553
|
+
${import_picocolors13.default.bold(import_picocolors13.default.white("dora journal sync"))} \u2014 ${import_picocolors13.default.white(project)}
|
|
3005
3554
|
`);
|
|
3006
|
-
ui.write(` Journal repo: ${
|
|
3555
|
+
ui.write(` Journal repo: ${import_picocolors13.default.dim(import_picocolors13.default.gray(journalRepo))}`);
|
|
3007
3556
|
ensureDoravalDirs();
|
|
3008
3557
|
const journalsDir = getJournalsDir();
|
|
3009
3558
|
const remoteProjectPath = `projects/${project}.md`;
|
|
3010
|
-
const localProjectPath =
|
|
3011
|
-
ui.write(` ${
|
|
3012
|
-
const refreshGlobalRes = await refreshLocalJournalFile(journalRepo, "global.md",
|
|
3559
|
+
const localProjectPath = join10(journalsDir, `${project}.md`);
|
|
3560
|
+
ui.write(` ${import_picocolors13.default.dim(import_picocolors13.default.gray("Refreshing local cache from remote..."))}`);
|
|
3561
|
+
const refreshGlobalRes = await refreshLocalJournalFile(journalRepo, "global.md", join10(journalsDir, "global.md"));
|
|
3013
3562
|
if (!refreshGlobalRes.ok) {
|
|
3014
3563
|
if (!refreshGlobalRes.isNotFound) {
|
|
3015
|
-
ui.write(
|
|
3564
|
+
ui.write(import_picocolors13.default.red(`Failed to fetch global.md from ${journalRepo}:`));
|
|
3016
3565
|
ui.write(refreshGlobalRes.error);
|
|
3017
3566
|
process.exit(1);
|
|
3018
3567
|
}
|
|
3019
3568
|
}
|
|
3020
3569
|
const gotGlobal = refreshGlobalRes.ok && refreshGlobalRes.value;
|
|
3021
3570
|
if (gotGlobal) {
|
|
3022
|
-
ui.write(` ${
|
|
3571
|
+
ui.write(` ${import_picocolors13.default.dim(import_picocolors13.default.gray("\u2713 global.md"))}`);
|
|
3023
3572
|
}
|
|
3024
3573
|
const refreshProjectCacheRes = await refreshLocalJournalFile(journalRepo, remoteProjectPath, localProjectPath);
|
|
3025
3574
|
if (!refreshProjectCacheRes.ok) {
|
|
3026
3575
|
if (!refreshProjectCacheRes.isNotFound) {
|
|
3027
|
-
ui.write(
|
|
3576
|
+
ui.write(import_picocolors13.default.red(`Failed to fetch ${remoteProjectPath} from ${journalRepo}:`));
|
|
3028
3577
|
ui.write(refreshProjectCacheRes.error);
|
|
3029
3578
|
process.exit(1);
|
|
3030
3579
|
}
|
|
3031
3580
|
}
|
|
3032
3581
|
const gotProjectCache = refreshProjectCacheRes.ok && refreshProjectCacheRes.value;
|
|
3033
3582
|
if (gotProjectCache) {
|
|
3034
|
-
ui.write(` ${
|
|
3583
|
+
ui.write(` ${import_picocolors13.default.dim(import_picocolors13.default.gray(`\u2713 ${remoteProjectPath}`))}`);
|
|
3035
3584
|
}
|
|
3036
|
-
const pendingFiles =
|
|
3585
|
+
const pendingFiles = existsSync11(pendingDir) ? readdirSync4(pendingDir).filter((f) => f.endsWith(".md") && f !== ".gitkeep").sort() : [];
|
|
3037
3586
|
if (pendingFiles.length === 0) {
|
|
3038
3587
|
ui.write(`
|
|
3039
|
-
${
|
|
3588
|
+
${import_picocolors13.default.yellow("\u26A0")} No pending entries. Local cache is now up to date.
|
|
3040
3589
|
`);
|
|
3041
3590
|
process.exit(0);
|
|
3042
3591
|
}
|
|
@@ -3049,7 +3598,7 @@ var init_sync = __esm(() => {
|
|
|
3049
3598
|
let currentFile = null;
|
|
3050
3599
|
if (!metaRes.ok) {
|
|
3051
3600
|
if (!metaRes.isNotFound) {
|
|
3052
|
-
ui.write(
|
|
3601
|
+
ui.write(import_picocolors13.default.red(`Failed to fetch ${remotePath} from ${journalRepo}:`));
|
|
3053
3602
|
ui.write(metaRes.error);
|
|
3054
3603
|
process.exit(1);
|
|
3055
3604
|
}
|
|
@@ -3060,14 +3609,14 @@ var init_sync = __esm(() => {
|
|
|
3060
3609
|
existingContent = Buffer.from(currentFile.content, "base64").toString("utf8");
|
|
3061
3610
|
currentSha = currentFile.sha;
|
|
3062
3611
|
if (args.verbose)
|
|
3063
|
-
ui.write(` ${
|
|
3612
|
+
ui.write(` ${import_picocolors13.default.dim(import_picocolors13.default.gray("Found existing remote file (sha: " + (currentSha?.slice(0, 7) ?? "") + "...)"))}`);
|
|
3064
3613
|
} else {
|
|
3065
3614
|
if (args.verbose)
|
|
3066
|
-
ui.write(` ${
|
|
3615
|
+
ui.write(` ${import_picocolors13.default.dim(import_picocolors13.default.gray("No existing file on remote \u2014 will create it"))}`);
|
|
3067
3616
|
}
|
|
3068
3617
|
let newEntries = "";
|
|
3069
3618
|
for (const file of pendingFiles) {
|
|
3070
|
-
const fullPath =
|
|
3619
|
+
const fullPath = join10(pendingDir, file);
|
|
3071
3620
|
const entryContent = await Bun.file(fullPath).text();
|
|
3072
3621
|
newEntries += `
|
|
3073
3622
|
` + entryContent.trim() + `
|
|
@@ -3087,37 +3636,215 @@ var init_sync = __esm(() => {
|
|
|
3087
3636
|
const commitMessage = args.message || `journal: add ${pendingFiles.length} entr${pendingFiles.length === 1 ? "y" : "ies"} for ${project}`;
|
|
3088
3637
|
if (args.verbose)
|
|
3089
3638
|
ui.write(`
|
|
3090
|
-
${
|
|
3639
|
+
${import_picocolors13.default.dim(import_picocolors13.default.gray("Pushing to remote..."))}`);
|
|
3091
3640
|
try {
|
|
3092
3641
|
updateGitHubFile(journalRepo, remotePath, newContent, commitMessage, currentSha);
|
|
3093
|
-
ui.write(` ${
|
|
3642
|
+
ui.write(` ${import_picocolors13.default.green("\u2713")} ${import_picocolors13.default.white("Successfully pushed to")} ${import_picocolors13.default.white(remotePath)}`);
|
|
3094
3643
|
} catch (err) {
|
|
3095
|
-
ui.write(`${
|
|
3644
|
+
ui.write(`${import_picocolors13.default.red("\u2717")} ${import_picocolors13.default.white("Failed to push to GitHub.")}`);
|
|
3096
3645
|
process.exit(1);
|
|
3097
3646
|
}
|
|
3098
3647
|
for (const file of pendingFiles) {
|
|
3099
|
-
const fullPath =
|
|
3648
|
+
const fullPath = join10(pendingDir, file);
|
|
3100
3649
|
try {
|
|
3101
3650
|
await Bun.file(fullPath).unlink();
|
|
3102
3651
|
} catch {}
|
|
3103
3652
|
}
|
|
3104
|
-
ui.write(` ${
|
|
3653
|
+
ui.write(` ${import_picocolors13.default.green("\u2713")} ${import_picocolors13.default.white("Cleared local pending entries")}`);
|
|
3105
3654
|
try {
|
|
3106
3655
|
const refreshRes = await refreshLocalJournalFile(journalRepo, remotePath, localProjectPath);
|
|
3107
3656
|
if (!refreshRes.ok) {
|
|
3108
3657
|
if (!refreshRes.isNotFound) {
|
|
3109
|
-
ui.write(` ${
|
|
3658
|
+
ui.write(` ${import_picocolors13.default.yellow("\u26A0")} Could not re-fetch updated file (you can run sync again later)`);
|
|
3110
3659
|
}
|
|
3111
3660
|
} else if (refreshRes.value) {
|
|
3112
3661
|
if (args.verbose)
|
|
3113
|
-
ui.write(` ${
|
|
3662
|
+
ui.write(` ${import_picocolors13.default.green("\u2713")} ${import_picocolors13.default.white("Re-fetched")} ${import_picocolors13.default.white(project)}.md ${import_picocolors13.default.white("into local cache")}`);
|
|
3114
3663
|
}
|
|
3115
3664
|
} catch {
|
|
3116
|
-
ui.write(` ${
|
|
3665
|
+
ui.write(` ${import_picocolors13.default.yellow("\u26A0")} Could not re-fetch updated file (you can run sync again later)`);
|
|
3117
3666
|
}
|
|
3118
3667
|
ui.write(`
|
|
3119
|
-
${
|
|
3668
|
+
${import_picocolors13.default.green("Done!")} ${import_picocolors13.default.white(pendingFiles.length + " entr" + (pendingFiles.length === 1 ? "y" : "ies") + " published.")}
|
|
3669
|
+
`);
|
|
3670
|
+
process.exit(0);
|
|
3671
|
+
}
|
|
3672
|
+
});
|
|
3673
|
+
});
|
|
3674
|
+
|
|
3675
|
+
// src/cli/commands/eval-history.ts
|
|
3676
|
+
var exports_eval_history = {};
|
|
3677
|
+
__export(exports_eval_history, {
|
|
3678
|
+
default: () => eval_history_default
|
|
3679
|
+
});
|
|
3680
|
+
import { existsSync as existsSync12, readdirSync as readdirSync5 } from "fs";
|
|
3681
|
+
import { join as join11 } from "path";
|
|
3682
|
+
var import_picocolors14, eval_history_default;
|
|
3683
|
+
var init_eval_history = __esm(() => {
|
|
3684
|
+
init_dist();
|
|
3685
|
+
init_out();
|
|
3686
|
+
init_journal_config();
|
|
3687
|
+
import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
3688
|
+
eval_history_default = defineCommand({
|
|
3689
|
+
meta: {
|
|
3690
|
+
name: "history",
|
|
3691
|
+
description: "List stored eval results"
|
|
3692
|
+
},
|
|
3693
|
+
args: {
|
|
3694
|
+
limit: {
|
|
3695
|
+
type: "string",
|
|
3696
|
+
description: "Maximum number of results to show (default: 20)",
|
|
3697
|
+
default: "20"
|
|
3698
|
+
},
|
|
3699
|
+
skill: {
|
|
3700
|
+
type: "string",
|
|
3701
|
+
description: "Filter by skill name"
|
|
3702
|
+
},
|
|
3703
|
+
format: {
|
|
3704
|
+
type: "string",
|
|
3705
|
+
alias: "f",
|
|
3706
|
+
description: "Output format: table (default) or json",
|
|
3707
|
+
default: "table"
|
|
3708
|
+
}
|
|
3709
|
+
},
|
|
3710
|
+
async run({ args }) {
|
|
3711
|
+
const evalsDir = getEvalsDir();
|
|
3712
|
+
if (!existsSync12(evalsDir)) {
|
|
3713
|
+
ui.info("No eval history yet. Run: doraval eval");
|
|
3714
|
+
process.exit(0);
|
|
3715
|
+
}
|
|
3716
|
+
const files = readdirSync5(evalsDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
3717
|
+
const limit = parseInt(String(args.limit), 10) || 20;
|
|
3718
|
+
const results = [];
|
|
3719
|
+
for (const file of files) {
|
|
3720
|
+
if (results.length >= limit)
|
|
3721
|
+
break;
|
|
3722
|
+
try {
|
|
3723
|
+
const raw = await Bun.file(join11(evalsDir, file)).text();
|
|
3724
|
+
const parsed = JSON.parse(raw);
|
|
3725
|
+
if (parsed.schemaVersion !== 1)
|
|
3726
|
+
continue;
|
|
3727
|
+
if (args.skill && !parsed.skill.includes(args.skill))
|
|
3728
|
+
continue;
|
|
3729
|
+
results.push(parsed);
|
|
3730
|
+
} catch {}
|
|
3731
|
+
}
|
|
3732
|
+
if (results.length === 0) {
|
|
3733
|
+
ui.info("No eval results found.");
|
|
3734
|
+
process.exit(0);
|
|
3735
|
+
}
|
|
3736
|
+
if (args.format === "json") {
|
|
3737
|
+
process.stdout.write(JSON.stringify(results, null, 2) + `
|
|
3738
|
+
`);
|
|
3739
|
+
} else {
|
|
3740
|
+
ui.heading("doraval eval history");
|
|
3741
|
+
ui.write(` ${"DATE".padEnd(20)} ${"SESSION TITLE".padEnd(35)} ${"SKILL".padEnd(35)} RESULT`);
|
|
3742
|
+
ui.write(` ${"-".repeat(100)}`);
|
|
3743
|
+
for (const r of results) {
|
|
3744
|
+
const date = r.timestamp.slice(0, 10);
|
|
3745
|
+
const title = (r.sessionTitle ?? r.sessionId.slice(0, 8)).slice(0, 33).padEnd(35);
|
|
3746
|
+
const skill = r.skill.slice(0, 33).padEnd(35);
|
|
3747
|
+
const verdictColor = r.verdict === "PASS" ? import_picocolors14.default.green : r.verdict === "FAIL" ? import_picocolors14.default.red : import_picocolors14.default.yellow;
|
|
3748
|
+
ui.write(` ${date.padEnd(20)} ${title} ${skill} ${verdictColor(r.verdict)}`);
|
|
3749
|
+
}
|
|
3750
|
+
ui.blank();
|
|
3751
|
+
}
|
|
3752
|
+
process.exit(0);
|
|
3753
|
+
}
|
|
3754
|
+
});
|
|
3755
|
+
});
|
|
3756
|
+
|
|
3757
|
+
// src/cli/commands/config.ts
|
|
3758
|
+
var exports_config = {};
|
|
3759
|
+
__export(exports_config, {
|
|
3760
|
+
default: () => config_default
|
|
3761
|
+
});
|
|
3762
|
+
var {YAML: YAML4 } = globalThis.Bun;
|
|
3763
|
+
function getNestedValue(obj, keyPath) {
|
|
3764
|
+
const parts = keyPath.split(".");
|
|
3765
|
+
let current = obj;
|
|
3766
|
+
for (const part of parts) {
|
|
3767
|
+
if (current === null || typeof current !== "object")
|
|
3768
|
+
return;
|
|
3769
|
+
current = current[part];
|
|
3770
|
+
}
|
|
3771
|
+
return current;
|
|
3772
|
+
}
|
|
3773
|
+
function setNestedValue(obj, keyPath, value) {
|
|
3774
|
+
const parts = keyPath.split(".");
|
|
3775
|
+
let current = obj;
|
|
3776
|
+
for (let i = 0;i < parts.length - 1; i++) {
|
|
3777
|
+
const part = parts[i];
|
|
3778
|
+
if (typeof current[part] !== "object" || current[part] === null) {
|
|
3779
|
+
current[part] = {};
|
|
3780
|
+
}
|
|
3781
|
+
current = current[part];
|
|
3782
|
+
}
|
|
3783
|
+
current[parts[parts.length - 1]] = value;
|
|
3784
|
+
}
|
|
3785
|
+
function coerceValue(raw) {
|
|
3786
|
+
if (raw === "true")
|
|
3787
|
+
return true;
|
|
3788
|
+
if (raw === "false")
|
|
3789
|
+
return false;
|
|
3790
|
+
const num = Number(raw);
|
|
3791
|
+
if (!isNaN(num) && raw.trim() !== "")
|
|
3792
|
+
return num;
|
|
3793
|
+
return raw;
|
|
3794
|
+
}
|
|
3795
|
+
var configSet, configGet, config_default;
|
|
3796
|
+
var init_config = __esm(() => {
|
|
3797
|
+
init_dist();
|
|
3798
|
+
init_out();
|
|
3799
|
+
init_journal_config();
|
|
3800
|
+
configSet = defineCommand({
|
|
3801
|
+
meta: { name: "set", description: "Set a config value" },
|
|
3802
|
+
args: {
|
|
3803
|
+
key: { type: "positional", description: "Dot-notation key (e.g. eval.model)", required: true },
|
|
3804
|
+
value: { type: "positional", description: "Value to set", required: true }
|
|
3805
|
+
},
|
|
3806
|
+
async run({ args }) {
|
|
3807
|
+
ensureDoravalDirs();
|
|
3808
|
+
const config = await readConfig() ?? {
|
|
3809
|
+
journal: { repo: "", projects: {} }
|
|
3810
|
+
};
|
|
3811
|
+
const coerced = coerceValue(String(args.value));
|
|
3812
|
+
setNestedValue(config, String(args.key), coerced);
|
|
3813
|
+
await writeConfig(config);
|
|
3814
|
+
ui.success(`${args.key} = ${JSON.stringify(coerced)}`);
|
|
3815
|
+
process.exit(0);
|
|
3816
|
+
}
|
|
3817
|
+
});
|
|
3818
|
+
configGet = defineCommand({
|
|
3819
|
+
meta: { name: "get", description: "Get a config value (omit key to print all)" },
|
|
3820
|
+
args: {
|
|
3821
|
+
key: { type: "positional", description: "Dot-notation key (omit to print all)", required: false }
|
|
3822
|
+
},
|
|
3823
|
+
async run({ args }) {
|
|
3824
|
+
const config = await readConfig();
|
|
3825
|
+
if (!config) {
|
|
3826
|
+
ui.info("No config found. Run: doraval init");
|
|
3827
|
+
process.exit(0);
|
|
3828
|
+
}
|
|
3829
|
+
if (!args.key) {
|
|
3830
|
+
process.stdout.write(YAML4.stringify(config));
|
|
3831
|
+
process.exit(0);
|
|
3832
|
+
}
|
|
3833
|
+
const value = getNestedValue(config, String(args.key));
|
|
3834
|
+
if (value === undefined) {
|
|
3835
|
+
ui.info(`${args.key}: (not set)`);
|
|
3836
|
+
} else {
|
|
3837
|
+
process.stdout.write(`${JSON.stringify(value)}
|
|
3120
3838
|
`);
|
|
3839
|
+
}
|
|
3840
|
+
process.exit(0);
|
|
3841
|
+
}
|
|
3842
|
+
});
|
|
3843
|
+
config_default = defineCommand({
|
|
3844
|
+
meta: { name: "config", description: "Get or set doraval configuration (dot-notation keys)" },
|
|
3845
|
+
subCommands: { set: configSet, get: configGet },
|
|
3846
|
+
run() {
|
|
3847
|
+
ui.info("Usage: doraval config set <key> <value> | doraval config get [key]");
|
|
3121
3848
|
process.exit(0);
|
|
3122
3849
|
}
|
|
3123
3850
|
});
|
|
@@ -3175,15 +3902,15 @@ var init_spec = __esm(() => {
|
|
|
3175
3902
|
});
|
|
3176
3903
|
|
|
3177
3904
|
// src/cli/commands/claude/context.ts
|
|
3178
|
-
import { existsSync as
|
|
3179
|
-
import { join as
|
|
3905
|
+
import { existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
|
|
3906
|
+
import { join as join12 } from "path";
|
|
3180
3907
|
function detectContext(cwd = process.cwd()) {
|
|
3181
3908
|
const claudeSpec = getProviderSpec("claude");
|
|
3182
|
-
const hasClaudeDir =
|
|
3183
|
-
const hasPluginManifest =
|
|
3909
|
+
const hasClaudeDir = existsSync13(join12(cwd, ".claude"));
|
|
3910
|
+
const hasPluginManifest = existsSync13(join12(cwd, claudeSpec.manifestPath));
|
|
3184
3911
|
let looseSkillFiles = [];
|
|
3185
3912
|
try {
|
|
3186
|
-
const files =
|
|
3913
|
+
const files = readdirSync6(cwd);
|
|
3187
3914
|
looseSkillFiles = files.filter((f) => {
|
|
3188
3915
|
if (!f.endsWith(".md") || f.startsWith("."))
|
|
3189
3916
|
return false;
|
|
@@ -3213,22 +3940,22 @@ __export(exports_new, {
|
|
|
3213
3940
|
default: () => new_default,
|
|
3214
3941
|
decidePath: () => decidePath
|
|
3215
3942
|
});
|
|
3216
|
-
import { join as
|
|
3217
|
-
import { mkdirSync as mkdirSync3, writeFileSync, existsSync as
|
|
3943
|
+
import { join as join13, basename as basename3, dirname as dirname2 } from "path";
|
|
3944
|
+
import { mkdirSync as mkdirSync3, writeFileSync, existsSync as existsSync14 } from "fs";
|
|
3218
3945
|
function decidePath(ctx, intent, providedName) {
|
|
3219
3946
|
const rawName = providedName || "";
|
|
3220
3947
|
let decisionPath = "standalone";
|
|
3221
3948
|
let targetDir = ctx.cwd;
|
|
3222
3949
|
let shouldCreateDir = false;
|
|
3223
3950
|
let migrateExisting = false;
|
|
3224
|
-
const useCurrentDirAsRoot = rawName === "." || rawName ===
|
|
3951
|
+
const useCurrentDirAsRoot = rawName === "." || rawName === basename3(ctx.cwd) || !rawName;
|
|
3225
3952
|
if (intent === "distribute" || intent === "self-later" && ctx.looseSkillFiles.length > 0 && !ctx.hasClaudeDir) {
|
|
3226
3953
|
decisionPath = "plugin";
|
|
3227
3954
|
if (useCurrentDirAsRoot) {
|
|
3228
3955
|
targetDir = ctx.cwd;
|
|
3229
3956
|
shouldCreateDir = false;
|
|
3230
3957
|
} else {
|
|
3231
|
-
targetDir =
|
|
3958
|
+
targetDir = join13(ctx.cwd, rawName);
|
|
3232
3959
|
shouldCreateDir = true;
|
|
3233
3960
|
}
|
|
3234
3961
|
migrateExisting = ctx.looseSkillFiles.length > 0;
|
|
@@ -3238,7 +3965,7 @@ function decidePath(ctx, intent, providedName) {
|
|
|
3238
3965
|
targetDir = ctx.cwd;
|
|
3239
3966
|
shouldCreateDir = false;
|
|
3240
3967
|
} else {
|
|
3241
|
-
targetDir =
|
|
3968
|
+
targetDir = join13(ctx.cwd, rawName);
|
|
3242
3969
|
shouldCreateDir = true;
|
|
3243
3970
|
}
|
|
3244
3971
|
} else if (decisionPath === "standalone") {
|
|
@@ -3246,7 +3973,7 @@ function decidePath(ctx, intent, providedName) {
|
|
|
3246
3973
|
targetDir = ctx.cwd;
|
|
3247
3974
|
shouldCreateDir = false;
|
|
3248
3975
|
} else {
|
|
3249
|
-
targetDir =
|
|
3976
|
+
targetDir = join13(ctx.cwd, rawName);
|
|
3250
3977
|
shouldCreateDir = true;
|
|
3251
3978
|
}
|
|
3252
3979
|
}
|
|
@@ -3254,7 +3981,7 @@ function decidePath(ctx, intent, providedName) {
|
|
|
3254
3981
|
}
|
|
3255
3982
|
function scaffold(decision, ctx, migrateContent) {
|
|
3256
3983
|
const { targetDir, path, shouldCreateDir } = decision;
|
|
3257
|
-
if (
|
|
3984
|
+
if (existsSync14(targetDir) && shouldCreateDir) {
|
|
3258
3985
|
ui.fail("Target already exists");
|
|
3259
3986
|
process.exit(1);
|
|
3260
3987
|
}
|
|
@@ -3262,7 +3989,7 @@ function scaffold(decision, ctx, migrateContent) {
|
|
|
3262
3989
|
mkdirSync3(targetDir, { recursive: true });
|
|
3263
3990
|
}
|
|
3264
3991
|
if (path === "plugin") {
|
|
3265
|
-
const pluginName =
|
|
3992
|
+
const pluginName = basename3(targetDir);
|
|
3266
3993
|
const claudeSpec = getProviderSpec("claude");
|
|
3267
3994
|
const claudeManifestDir = dirname2(claudeSpec.manifestPath);
|
|
3268
3995
|
const pluginJson = {
|
|
@@ -3271,8 +3998,8 @@ function scaffold(decision, ctx, migrateContent) {
|
|
|
3271
3998
|
version: "0.1.0",
|
|
3272
3999
|
keywords: ["example-keyword", "another-keyword"]
|
|
3273
4000
|
};
|
|
3274
|
-
mkdirSync3(
|
|
3275
|
-
writeFileSync(
|
|
4001
|
+
mkdirSync3(join13(targetDir, claudeManifestDir), { recursive: true });
|
|
4002
|
+
writeFileSync(join13(targetDir, claudeSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
|
|
3276
4003
|
const marketplaceJson = {
|
|
3277
4004
|
name: pluginName,
|
|
3278
4005
|
version: "0.1.0",
|
|
@@ -3283,9 +4010,9 @@ function scaffold(decision, ctx, migrateContent) {
|
|
|
3283
4010
|
license: "MIT",
|
|
3284
4011
|
keywords: ["claude-code", "skills", "plugin"]
|
|
3285
4012
|
};
|
|
3286
|
-
writeFileSync(
|
|
4013
|
+
writeFileSync(join13(targetDir, "marketplace.json"), JSON.stringify(marketplaceJson, null, 2));
|
|
3287
4014
|
const demoSkillName = "doraval";
|
|
3288
|
-
mkdirSync3(
|
|
4015
|
+
mkdirSync3(join13(targetDir, "skills", demoSkillName), { recursive: true });
|
|
3289
4016
|
let skillContent;
|
|
3290
4017
|
if (migrateContent) {
|
|
3291
4018
|
skillContent = migrateContent;
|
|
@@ -3309,19 +4036,19 @@ When you need to check a skill or plugin:
|
|
|
3309
4036
|
|
|
3310
4037
|
Always run \`doraval validate\` before sharing or publishing a plugin. This skill demonstrates a complete, self-referential example of using doraval inside a generated plugin.`;
|
|
3311
4038
|
}
|
|
3312
|
-
writeFileSync(
|
|
3313
|
-
const readmePath =
|
|
3314
|
-
if (!
|
|
4039
|
+
writeFileSync(join13(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
|
|
4040
|
+
const readmePath = join13(targetDir, "README.md");
|
|
4041
|
+
if (!existsSync14(readmePath)) {
|
|
3315
4042
|
writeFileSync(readmePath, "# " + pluginName + `
|
|
3316
4043
|
|
|
3317
4044
|
Claude Code plugin scaffolded by doraval.`);
|
|
3318
4045
|
}
|
|
3319
4046
|
} else {
|
|
3320
|
-
mkdirSync3(
|
|
4047
|
+
mkdirSync3(join13(targetDir, ".claude", "skills", "my-skill"), { recursive: true });
|
|
3321
4048
|
const skillBody = migrateContent || `# My Skill
|
|
3322
4049
|
|
|
3323
4050
|
Basic starter.`;
|
|
3324
|
-
writeFileSync(
|
|
4051
|
+
writeFileSync(join13(targetDir, ".claude", "skills", "my-skill", "SKILL.md"), `---
|
|
3325
4052
|
name: my-skill
|
|
3326
4053
|
description: Starter
|
|
3327
4054
|
---
|
|
@@ -3329,14 +4056,14 @@ description: Starter
|
|
|
3329
4056
|
${skillBody}`);
|
|
3330
4057
|
}
|
|
3331
4058
|
}
|
|
3332
|
-
var
|
|
4059
|
+
var import_picocolors15, new_default;
|
|
3333
4060
|
var init_new = __esm(() => {
|
|
3334
4061
|
init_dist();
|
|
3335
4062
|
init_out();
|
|
3336
4063
|
init_context2();
|
|
3337
4064
|
init_prompt();
|
|
3338
4065
|
init_spec();
|
|
3339
|
-
|
|
4066
|
+
import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
3340
4067
|
new_default = defineCommand({
|
|
3341
4068
|
meta: {
|
|
3342
4069
|
name: "new",
|
|
@@ -3375,8 +4102,8 @@ var init_new = __esm(() => {
|
|
|
3375
4102
|
}
|
|
3376
4103
|
scaffold(decision, ctx, migrateContent);
|
|
3377
4104
|
ui.write(`
|
|
3378
|
-
${
|
|
3379
|
-
const cmdName = decision.path === "plugin" ? `/${
|
|
4105
|
+
${import_picocolors15.default.green("\u2713")} Created ${decision.path} at ${import_picocolors15.default.bold(decision.targetDir)}`);
|
|
4106
|
+
const cmdName = decision.path === "plugin" ? `/${basename3(decision.targetDir)}:doraval` : "/my-skill";
|
|
3380
4107
|
ui.info(` Command: ${cmdName}`);
|
|
3381
4108
|
if (decision.path === "plugin") {
|
|
3382
4109
|
const claudeSpec = getProviderSpec("claude");
|
|
@@ -3401,8 +4128,8 @@ var exports_bump = {};
|
|
|
3401
4128
|
__export(exports_bump, {
|
|
3402
4129
|
default: () => bump_default
|
|
3403
4130
|
});
|
|
3404
|
-
import { resolve as resolve4, join as
|
|
3405
|
-
import { existsSync as
|
|
4131
|
+
import { resolve as resolve4, join as join14, dirname as dirname3, relative } from "path";
|
|
4132
|
+
import { existsSync as existsSync15, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync as readdirSync7, statSync as statSync2 } from "fs";
|
|
3406
4133
|
function bumpVersion(current, type) {
|
|
3407
4134
|
if (/^\d+\.\d+\.\d+$/.test(type))
|
|
3408
4135
|
return type;
|
|
@@ -3422,7 +4149,7 @@ function bumpVersion(current, type) {
|
|
|
3422
4149
|
}
|
|
3423
4150
|
function readJson2(p) {
|
|
3424
4151
|
try {
|
|
3425
|
-
const content =
|
|
4152
|
+
const content = readFileSync2(p, "utf8");
|
|
3426
4153
|
return JSON.parse(content);
|
|
3427
4154
|
} catch {
|
|
3428
4155
|
return null;
|
|
@@ -3480,15 +4207,15 @@ function walkForTargets(dir, maxDepth = 6, currentDepth = 0) {
|
|
|
3480
4207
|
return results;
|
|
3481
4208
|
let entries;
|
|
3482
4209
|
try {
|
|
3483
|
-
entries =
|
|
4210
|
+
entries = readdirSync7(dir);
|
|
3484
4211
|
} catch {
|
|
3485
4212
|
return results;
|
|
3486
4213
|
}
|
|
3487
4214
|
for (const entry of entries) {
|
|
3488
|
-
const full =
|
|
4215
|
+
const full = join14(dir, entry);
|
|
3489
4216
|
let st;
|
|
3490
4217
|
try {
|
|
3491
|
-
st =
|
|
4218
|
+
st = statSync2(full);
|
|
3492
4219
|
} catch {
|
|
3493
4220
|
continue;
|
|
3494
4221
|
}
|
|
@@ -3520,11 +4247,11 @@ function walkForTargets(dir, maxDepth = 6, currentDepth = 0) {
|
|
|
3520
4247
|
}
|
|
3521
4248
|
return results;
|
|
3522
4249
|
}
|
|
3523
|
-
var
|
|
4250
|
+
var import_picocolors16, bump_default;
|
|
3524
4251
|
var init_bump = __esm(() => {
|
|
3525
4252
|
init_dist();
|
|
3526
4253
|
init_out();
|
|
3527
|
-
|
|
4254
|
+
import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
3528
4255
|
bump_default = defineCommand({
|
|
3529
4256
|
meta: {
|
|
3530
4257
|
name: "bump",
|
|
@@ -3558,7 +4285,7 @@ var init_bump = __esm(() => {
|
|
|
3558
4285
|
}
|
|
3559
4286
|
const isKnownType = ["patch", "minor", "major"].includes(rawType) || /^\d+\.\d+\.\d+$/.test(rawType);
|
|
3560
4287
|
const maybePath = resolve4(rawType);
|
|
3561
|
-
const looksLikeDir =
|
|
4288
|
+
const looksLikeDir = existsSync15(maybePath) || rawType === "." || rawType.startsWith("./") || rawType.startsWith("../");
|
|
3562
4289
|
if (!isKnownType && looksLikeDir) {
|
|
3563
4290
|
targetPath = rawType;
|
|
3564
4291
|
rawType = "patch";
|
|
@@ -3567,7 +4294,7 @@ var init_bump = __esm(() => {
|
|
|
3567
4294
|
process.exit(1);
|
|
3568
4295
|
}
|
|
3569
4296
|
const root = resolve4(targetPath);
|
|
3570
|
-
if (!
|
|
4297
|
+
if (!existsSync15(root)) {
|
|
3571
4298
|
ui.fail(`Path does not exist: ${root}`);
|
|
3572
4299
|
process.exit(1);
|
|
3573
4300
|
}
|
|
@@ -3633,9 +4360,9 @@ var init_bump = __esm(() => {
|
|
|
3633
4360
|
}
|
|
3634
4361
|
writeJson2(t.file, json);
|
|
3635
4362
|
if (didRootUpdate && current) {
|
|
3636
|
-
ui.success(`${t.label}: ${
|
|
4363
|
+
ui.success(`${t.label}: ${import_picocolors16.default.dim(current)} \u2192 ${import_picocolors16.default.green(next)}`);
|
|
3637
4364
|
} else if (didRootUpdate) {
|
|
3638
|
-
ui.success(`${t.label}: ${
|
|
4365
|
+
ui.success(`${t.label}: ${import_picocolors16.default.green(next)}`);
|
|
3639
4366
|
} else {
|
|
3640
4367
|
ui.success(`${t.label} (no root version)`);
|
|
3641
4368
|
}
|
|
@@ -3658,16 +4385,16 @@ var init_bump = __esm(() => {
|
|
|
3658
4385
|
});
|
|
3659
4386
|
|
|
3660
4387
|
// src/cli/commands/codex/context.ts
|
|
3661
|
-
import { existsSync as
|
|
3662
|
-
import { join as
|
|
4388
|
+
import { existsSync as existsSync16, readdirSync as readdirSync8 } from "fs";
|
|
4389
|
+
import { join as join15 } from "path";
|
|
3663
4390
|
function detectContext2(cwd = process.cwd()) {
|
|
3664
4391
|
const codexSpec = getProviderSpec("codex");
|
|
3665
|
-
const hasCodexDir =
|
|
3666
|
-
const hasPluginManifest =
|
|
3667
|
-
const hasMarketplace =
|
|
4392
|
+
const hasCodexDir = existsSync16(join15(cwd, ".codex"));
|
|
4393
|
+
const hasPluginManifest = existsSync16(join15(cwd, codexSpec.manifestPath));
|
|
4394
|
+
const hasMarketplace = existsSync16(join15(cwd, ".agents", "plugins", "marketplace.json")) || existsSync16(join15(cwd, codexSpec.manifestPath));
|
|
3668
4395
|
let looseSkillFiles = [];
|
|
3669
4396
|
try {
|
|
3670
|
-
const files =
|
|
4397
|
+
const files = readdirSync8(cwd);
|
|
3671
4398
|
looseSkillFiles = files.filter((f) => {
|
|
3672
4399
|
if (!f.endsWith(".md") || f.startsWith("."))
|
|
3673
4400
|
return false;
|
|
@@ -3698,22 +4425,22 @@ __export(exports_new2, {
|
|
|
3698
4425
|
default: () => new_default2,
|
|
3699
4426
|
decidePath: () => decidePath2
|
|
3700
4427
|
});
|
|
3701
|
-
import { join as
|
|
3702
|
-
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, existsSync as
|
|
4428
|
+
import { join as join16, basename as basename4, dirname as dirname4 } from "path";
|
|
4429
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, existsSync as existsSync17 } from "fs";
|
|
3703
4430
|
function decidePath2(ctx, intent, providedName) {
|
|
3704
4431
|
const rawName = providedName || "";
|
|
3705
4432
|
let decisionPath = "standalone";
|
|
3706
4433
|
let targetDir = ctx.cwd;
|
|
3707
4434
|
let shouldCreateDir = false;
|
|
3708
4435
|
let migrateExisting = false;
|
|
3709
|
-
const useCurrentDirAsRoot = rawName === "." || rawName ===
|
|
4436
|
+
const useCurrentDirAsRoot = rawName === "." || rawName === basename4(ctx.cwd) || !rawName;
|
|
3710
4437
|
if (intent === "distribute" || intent === "self-later" && ctx.looseSkillFiles.length > 0 && !ctx.hasPluginManifest) {
|
|
3711
4438
|
decisionPath = "plugin";
|
|
3712
4439
|
if (useCurrentDirAsRoot) {
|
|
3713
4440
|
targetDir = ctx.cwd;
|
|
3714
4441
|
shouldCreateDir = false;
|
|
3715
4442
|
} else {
|
|
3716
|
-
targetDir =
|
|
4443
|
+
targetDir = join16(ctx.cwd, rawName);
|
|
3717
4444
|
shouldCreateDir = true;
|
|
3718
4445
|
}
|
|
3719
4446
|
migrateExisting = ctx.looseSkillFiles.length > 0;
|
|
@@ -3723,7 +4450,7 @@ function decidePath2(ctx, intent, providedName) {
|
|
|
3723
4450
|
targetDir = ctx.cwd;
|
|
3724
4451
|
shouldCreateDir = false;
|
|
3725
4452
|
} else {
|
|
3726
|
-
targetDir =
|
|
4453
|
+
targetDir = join16(ctx.cwd, rawName);
|
|
3727
4454
|
shouldCreateDir = true;
|
|
3728
4455
|
}
|
|
3729
4456
|
} else if (decisionPath === "standalone") {
|
|
@@ -3731,7 +4458,7 @@ function decidePath2(ctx, intent, providedName) {
|
|
|
3731
4458
|
targetDir = ctx.cwd;
|
|
3732
4459
|
shouldCreateDir = false;
|
|
3733
4460
|
} else {
|
|
3734
|
-
targetDir =
|
|
4461
|
+
targetDir = join16(ctx.cwd, rawName);
|
|
3735
4462
|
shouldCreateDir = true;
|
|
3736
4463
|
}
|
|
3737
4464
|
}
|
|
@@ -3739,7 +4466,7 @@ function decidePath2(ctx, intent, providedName) {
|
|
|
3739
4466
|
}
|
|
3740
4467
|
function scaffold2(decision, ctx, migrateContent) {
|
|
3741
4468
|
const { targetDir, path, shouldCreateDir } = decision;
|
|
3742
|
-
if (
|
|
4469
|
+
if (existsSync17(targetDir) && shouldCreateDir) {
|
|
3743
4470
|
ui.fail("Target already exists");
|
|
3744
4471
|
process.exit(1);
|
|
3745
4472
|
}
|
|
@@ -3747,7 +4474,7 @@ function scaffold2(decision, ctx, migrateContent) {
|
|
|
3747
4474
|
mkdirSync4(targetDir, { recursive: true });
|
|
3748
4475
|
}
|
|
3749
4476
|
if (path === "plugin") {
|
|
3750
|
-
const pluginName =
|
|
4477
|
+
const pluginName = basename4(targetDir);
|
|
3751
4478
|
const codexSpec = getProviderSpec("codex");
|
|
3752
4479
|
const codexManifestDir = dirname4(codexSpec.manifestPath);
|
|
3753
4480
|
const pluginJson = {
|
|
@@ -3762,10 +4489,10 @@ function scaffold2(decision, ctx, migrateContent) {
|
|
|
3762
4489
|
},
|
|
3763
4490
|
keywords: ["example-keyword", "another-keyword"]
|
|
3764
4491
|
};
|
|
3765
|
-
mkdirSync4(
|
|
3766
|
-
writeFileSync3(
|
|
4492
|
+
mkdirSync4(join16(targetDir, codexManifestDir), { recursive: true });
|
|
4493
|
+
writeFileSync3(join16(targetDir, codexSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
|
|
3767
4494
|
const marketplaceDir = dirname4(codexSpec.marketplacePath);
|
|
3768
|
-
mkdirSync4(
|
|
4495
|
+
mkdirSync4(join16(targetDir, marketplaceDir), { recursive: true });
|
|
3769
4496
|
const marketplaceJson = {
|
|
3770
4497
|
name: "local",
|
|
3771
4498
|
interface: {
|
|
@@ -3786,9 +4513,9 @@ function scaffold2(decision, ctx, migrateContent) {
|
|
|
3786
4513
|
}
|
|
3787
4514
|
]
|
|
3788
4515
|
};
|
|
3789
|
-
writeFileSync3(
|
|
4516
|
+
writeFileSync3(join16(targetDir, codexSpec.marketplacePath), JSON.stringify(marketplaceJson, null, 2));
|
|
3790
4517
|
const demoSkillName = "doraval";
|
|
3791
|
-
mkdirSync4(
|
|
4518
|
+
mkdirSync4(join16(targetDir, "skills", demoSkillName), { recursive: true });
|
|
3792
4519
|
let skillContent;
|
|
3793
4520
|
if (migrateContent) {
|
|
3794
4521
|
skillContent = migrateContent;
|
|
@@ -3819,19 +4546,19 @@ To test in Codex:
|
|
|
3819
4546
|
3. Open the plugin directory, select your local marketplace, and enable the plugin.
|
|
3820
4547
|
4. Invoke the demo with /${pluginName}:doraval`;
|
|
3821
4548
|
}
|
|
3822
|
-
writeFileSync3(
|
|
3823
|
-
const readmePath =
|
|
3824
|
-
if (!
|
|
4549
|
+
writeFileSync3(join16(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
|
|
4550
|
+
const readmePath = join16(targetDir, "README.md");
|
|
4551
|
+
if (!existsSync17(readmePath)) {
|
|
3825
4552
|
writeFileSync3(readmePath, "# " + pluginName + `
|
|
3826
4553
|
|
|
3827
4554
|
Codex plugin scaffolded by doraval.`);
|
|
3828
4555
|
}
|
|
3829
4556
|
} else {
|
|
3830
|
-
mkdirSync4(
|
|
4557
|
+
mkdirSync4(join16(targetDir, "skills", "doraval"), { recursive: true });
|
|
3831
4558
|
const skillBody = migrateContent || `# My Skill
|
|
3832
4559
|
|
|
3833
4560
|
Basic starter for Codex.`;
|
|
3834
|
-
writeFileSync3(
|
|
4561
|
+
writeFileSync3(join16(targetDir, "skills", "doraval", "SKILL.md"), `---
|
|
3835
4562
|
name: doraval
|
|
3836
4563
|
description: Starter (local skill)
|
|
3837
4564
|
---
|
|
@@ -3839,14 +4566,14 @@ description: Starter (local skill)
|
|
|
3839
4566
|
${skillBody}`);
|
|
3840
4567
|
}
|
|
3841
4568
|
}
|
|
3842
|
-
var
|
|
4569
|
+
var import_picocolors17, new_default2;
|
|
3843
4570
|
var init_new2 = __esm(() => {
|
|
3844
4571
|
init_dist();
|
|
3845
4572
|
init_out();
|
|
3846
4573
|
init_context3();
|
|
3847
4574
|
init_prompt();
|
|
3848
4575
|
init_spec();
|
|
3849
|
-
|
|
4576
|
+
import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
3850
4577
|
new_default2 = defineCommand({
|
|
3851
4578
|
meta: {
|
|
3852
4579
|
name: "new",
|
|
@@ -3885,8 +4612,8 @@ var init_new2 = __esm(() => {
|
|
|
3885
4612
|
}
|
|
3886
4613
|
scaffold2(decision, ctx, migrateContent);
|
|
3887
4614
|
ui.write(`
|
|
3888
|
-
${
|
|
3889
|
-
const cmdName = decision.path === "plugin" ? `/${
|
|
4615
|
+
${import_picocolors17.default.green("\u2713")} Created ${decision.path} at ${import_picocolors17.default.bold(decision.targetDir)}`);
|
|
4616
|
+
const cmdName = decision.path === "plugin" ? `/${basename4(decision.targetDir)}:doraval` : "/doraval (local skill)";
|
|
3890
4617
|
ui.info(` Command: ${cmdName}`);
|
|
3891
4618
|
if (decision.path === "plugin") {
|
|
3892
4619
|
ui.info(` Codex manifest: .codex-plugin/plugin.json`);
|
|
@@ -3907,14 +4634,14 @@ var init_new2 = __esm(() => {
|
|
|
3907
4634
|
});
|
|
3908
4635
|
|
|
3909
4636
|
// src/cli/commands/cursor/context.ts
|
|
3910
|
-
import { existsSync as
|
|
3911
|
-
import { join as
|
|
4637
|
+
import { existsSync as existsSync18, readdirSync as readdirSync9 } from "fs";
|
|
4638
|
+
import { join as join17 } from "path";
|
|
3912
4639
|
function detectContext3(cwd = process.cwd()) {
|
|
3913
|
-
const hasCursorDir =
|
|
3914
|
-
const hasPluginManifest =
|
|
4640
|
+
const hasCursorDir = existsSync18(join17(cwd, ".cursor"));
|
|
4641
|
+
const hasPluginManifest = existsSync18(join17(cwd, ".cursor-plugin", "plugin.json"));
|
|
3915
4642
|
let looseSkillFiles = [];
|
|
3916
4643
|
try {
|
|
3917
|
-
const files =
|
|
4644
|
+
const files = readdirSync9(cwd);
|
|
3918
4645
|
looseSkillFiles = files.filter((f) => {
|
|
3919
4646
|
if (!f.endsWith(".md") || f.startsWith("."))
|
|
3920
4647
|
return false;
|
|
@@ -3942,22 +4669,22 @@ __export(exports_new3, {
|
|
|
3942
4669
|
default: () => new_default3,
|
|
3943
4670
|
decidePath: () => decidePath3
|
|
3944
4671
|
});
|
|
3945
|
-
import { join as
|
|
3946
|
-
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4, existsSync as
|
|
4672
|
+
import { join as join18, basename as basename5, dirname as dirname5 } from "path";
|
|
4673
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4, existsSync as existsSync19 } from "fs";
|
|
3947
4674
|
function decidePath3(ctx, intent, providedName) {
|
|
3948
4675
|
const rawName = providedName || "";
|
|
3949
4676
|
let decisionPath = "standalone";
|
|
3950
4677
|
let targetDir = ctx.cwd;
|
|
3951
4678
|
let shouldCreateDir = false;
|
|
3952
4679
|
let migrateExisting = false;
|
|
3953
|
-
const useCurrentDirAsRoot = rawName === "." || rawName ===
|
|
4680
|
+
const useCurrentDirAsRoot = rawName === "." || rawName === basename5(ctx.cwd) || !rawName;
|
|
3954
4681
|
if (intent === "distribute" || intent === "self-later" && ctx.looseSkillFiles.length > 0 && !ctx.hasPluginManifest) {
|
|
3955
4682
|
decisionPath = "plugin";
|
|
3956
4683
|
if (useCurrentDirAsRoot) {
|
|
3957
4684
|
targetDir = ctx.cwd;
|
|
3958
4685
|
shouldCreateDir = false;
|
|
3959
4686
|
} else {
|
|
3960
|
-
targetDir =
|
|
4687
|
+
targetDir = join18(ctx.cwd, rawName);
|
|
3961
4688
|
shouldCreateDir = true;
|
|
3962
4689
|
}
|
|
3963
4690
|
migrateExisting = ctx.looseSkillFiles.length > 0;
|
|
@@ -3967,7 +4694,7 @@ function decidePath3(ctx, intent, providedName) {
|
|
|
3967
4694
|
targetDir = ctx.cwd;
|
|
3968
4695
|
shouldCreateDir = false;
|
|
3969
4696
|
} else {
|
|
3970
|
-
targetDir =
|
|
4697
|
+
targetDir = join18(ctx.cwd, rawName);
|
|
3971
4698
|
shouldCreateDir = true;
|
|
3972
4699
|
}
|
|
3973
4700
|
} else if (decisionPath === "standalone") {
|
|
@@ -3975,7 +4702,7 @@ function decidePath3(ctx, intent, providedName) {
|
|
|
3975
4702
|
targetDir = ctx.cwd;
|
|
3976
4703
|
shouldCreateDir = false;
|
|
3977
4704
|
} else {
|
|
3978
|
-
targetDir =
|
|
4705
|
+
targetDir = join18(ctx.cwd, rawName);
|
|
3979
4706
|
shouldCreateDir = true;
|
|
3980
4707
|
}
|
|
3981
4708
|
}
|
|
@@ -3983,7 +4710,7 @@ function decidePath3(ctx, intent, providedName) {
|
|
|
3983
4710
|
}
|
|
3984
4711
|
function scaffold3(decision, ctx, migrateContent) {
|
|
3985
4712
|
const { targetDir, path, shouldCreateDir } = decision;
|
|
3986
|
-
if (
|
|
4713
|
+
if (existsSync19(targetDir) && shouldCreateDir) {
|
|
3987
4714
|
ui.fail("Target already exists");
|
|
3988
4715
|
process.exit(1);
|
|
3989
4716
|
}
|
|
@@ -3991,7 +4718,7 @@ function scaffold3(decision, ctx, migrateContent) {
|
|
|
3991
4718
|
mkdirSync5(targetDir, { recursive: true });
|
|
3992
4719
|
}
|
|
3993
4720
|
if (path === "plugin") {
|
|
3994
|
-
const pluginName =
|
|
4721
|
+
const pluginName = basename5(targetDir);
|
|
3995
4722
|
const cursorSpec = getProviderSpec("cursor");
|
|
3996
4723
|
const cursorManifestDir = dirname5(cursorSpec.manifestPath);
|
|
3997
4724
|
const pluginJson = {
|
|
@@ -4002,10 +4729,10 @@ function scaffold3(decision, ctx, migrateContent) {
|
|
|
4002
4729
|
displayName: pluginName,
|
|
4003
4730
|
keywords: ["example-keyword", "another-keyword"]
|
|
4004
4731
|
};
|
|
4005
|
-
mkdirSync5(
|
|
4006
|
-
writeFileSync4(
|
|
4732
|
+
mkdirSync5(join18(targetDir, cursorManifestDir), { recursive: true });
|
|
4733
|
+
writeFileSync4(join18(targetDir, cursorSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
|
|
4007
4734
|
const marketplaceDir = dirname5(cursorSpec.marketplacePath);
|
|
4008
|
-
mkdirSync5(
|
|
4735
|
+
mkdirSync5(join18(targetDir, marketplaceDir), { recursive: true });
|
|
4009
4736
|
const marketplaceJson = {
|
|
4010
4737
|
name: pluginName,
|
|
4011
4738
|
version: "0.1.0",
|
|
@@ -4016,9 +4743,9 @@ function scaffold3(decision, ctx, migrateContent) {
|
|
|
4016
4743
|
license: "MIT",
|
|
4017
4744
|
keywords: ["cursor", "skills", "plugin"]
|
|
4018
4745
|
};
|
|
4019
|
-
writeFileSync4(
|
|
4746
|
+
writeFileSync4(join18(targetDir, cursorSpec.marketplacePath), JSON.stringify(marketplaceJson, null, 2));
|
|
4020
4747
|
const demoSkillName = "doraval";
|
|
4021
|
-
mkdirSync5(
|
|
4748
|
+
mkdirSync5(join18(targetDir, "skills", demoSkillName), { recursive: true });
|
|
4022
4749
|
let skillContent;
|
|
4023
4750
|
if (migrateContent) {
|
|
4024
4751
|
skillContent = migrateContent;
|
|
@@ -4047,19 +4774,19 @@ To test in Cursor:
|
|
|
4047
4774
|
1. Open the plugin directory or add via marketplace.
|
|
4048
4775
|
2. The demo skill will be available.`;
|
|
4049
4776
|
}
|
|
4050
|
-
writeFileSync4(
|
|
4051
|
-
const readmePath =
|
|
4052
|
-
if (!
|
|
4777
|
+
writeFileSync4(join18(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
|
|
4778
|
+
const readmePath = join18(targetDir, "README.md");
|
|
4779
|
+
if (!existsSync19(readmePath)) {
|
|
4053
4780
|
writeFileSync4(readmePath, "# " + pluginName + `
|
|
4054
4781
|
|
|
4055
4782
|
Cursor plugin scaffolded by doraval.`);
|
|
4056
4783
|
}
|
|
4057
4784
|
} else {
|
|
4058
|
-
mkdirSync5(
|
|
4785
|
+
mkdirSync5(join18(targetDir, "skills", "doraval"), { recursive: true });
|
|
4059
4786
|
const skillBody = migrateContent || `# My Skill
|
|
4060
4787
|
|
|
4061
4788
|
Basic starter for Cursor.`;
|
|
4062
|
-
writeFileSync4(
|
|
4789
|
+
writeFileSync4(join18(targetDir, "skills", "doraval", "SKILL.md"), `---
|
|
4063
4790
|
name: doraval
|
|
4064
4791
|
description: Starter (local skill)
|
|
4065
4792
|
---
|
|
@@ -4067,14 +4794,14 @@ description: Starter (local skill)
|
|
|
4067
4794
|
${skillBody}`);
|
|
4068
4795
|
}
|
|
4069
4796
|
}
|
|
4070
|
-
var
|
|
4797
|
+
var import_picocolors18, new_default3;
|
|
4071
4798
|
var init_new3 = __esm(() => {
|
|
4072
4799
|
init_dist();
|
|
4073
4800
|
init_out();
|
|
4074
4801
|
init_context4();
|
|
4075
4802
|
init_prompt();
|
|
4076
4803
|
init_spec();
|
|
4077
|
-
|
|
4804
|
+
import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
4078
4805
|
new_default3 = defineCommand({
|
|
4079
4806
|
meta: {
|
|
4080
4807
|
name: "new",
|
|
@@ -4113,8 +4840,8 @@ var init_new3 = __esm(() => {
|
|
|
4113
4840
|
}
|
|
4114
4841
|
scaffold3(decision, ctx, migrateContent);
|
|
4115
4842
|
ui.write(`
|
|
4116
|
-
${
|
|
4117
|
-
const cmdName = decision.path === "plugin" ? `/${
|
|
4843
|
+
${import_picocolors18.default.green("\u2713")} Created ${decision.path} at ${import_picocolors18.default.bold(decision.targetDir)}`);
|
|
4844
|
+
const cmdName = decision.path === "plugin" ? `/${basename5(decision.targetDir)}:doraval` : "/doraval (local skill)";
|
|
4118
4845
|
ui.info(` Command: ${cmdName}`);
|
|
4119
4846
|
if (decision.path === "plugin") {
|
|
4120
4847
|
ui.info(` Cursor manifest: .cursor-plugin/plugin.json`);
|
|
@@ -4134,14 +4861,14 @@ var init_new3 = __esm(() => {
|
|
|
4134
4861
|
});
|
|
4135
4862
|
|
|
4136
4863
|
// src/cli/commands/copilot/context.ts
|
|
4137
|
-
import { existsSync as
|
|
4138
|
-
import { join as
|
|
4864
|
+
import { existsSync as existsSync20, readdirSync as readdirSync10 } from "fs";
|
|
4865
|
+
import { join as join19 } from "path";
|
|
4139
4866
|
function detectContext4(cwd = process.cwd()) {
|
|
4140
|
-
const hasGithubDir =
|
|
4141
|
-
const hasPluginManifest =
|
|
4867
|
+
const hasGithubDir = existsSync20(join19(cwd, ".github"));
|
|
4868
|
+
const hasPluginManifest = existsSync20(join19(cwd, ".github", "plugin", "plugin.json"));
|
|
4142
4869
|
let looseSkillFiles = [];
|
|
4143
4870
|
try {
|
|
4144
|
-
const files =
|
|
4871
|
+
const files = readdirSync10(cwd);
|
|
4145
4872
|
looseSkillFiles = files.filter((f) => {
|
|
4146
4873
|
if (!f.endsWith(".md") || f.startsWith("."))
|
|
4147
4874
|
return false;
|
|
@@ -4169,22 +4896,22 @@ __export(exports_new4, {
|
|
|
4169
4896
|
default: () => new_default4,
|
|
4170
4897
|
decidePath: () => decidePath4
|
|
4171
4898
|
});
|
|
4172
|
-
import { join as
|
|
4173
|
-
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5, existsSync as
|
|
4899
|
+
import { join as join20, basename as basename6, dirname as dirname6 } from "path";
|
|
4900
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5, existsSync as existsSync21 } from "fs";
|
|
4174
4901
|
function decidePath4(ctx, intent, providedName) {
|
|
4175
4902
|
const rawName = providedName || "";
|
|
4176
4903
|
let decisionPath = "standalone";
|
|
4177
4904
|
let targetDir = ctx.cwd;
|
|
4178
4905
|
let shouldCreateDir = false;
|
|
4179
4906
|
let migrateExisting = false;
|
|
4180
|
-
const useCurrentDirAsRoot = rawName === "." || rawName ===
|
|
4907
|
+
const useCurrentDirAsRoot = rawName === "." || rawName === basename6(ctx.cwd) || !rawName;
|
|
4181
4908
|
if (intent === "distribute" || intent === "self-later" && ctx.looseSkillFiles.length > 0 && !ctx.hasPluginManifest) {
|
|
4182
4909
|
decisionPath = "plugin";
|
|
4183
4910
|
if (useCurrentDirAsRoot) {
|
|
4184
4911
|
targetDir = ctx.cwd;
|
|
4185
4912
|
shouldCreateDir = false;
|
|
4186
4913
|
} else {
|
|
4187
|
-
targetDir =
|
|
4914
|
+
targetDir = join20(ctx.cwd, rawName);
|
|
4188
4915
|
shouldCreateDir = true;
|
|
4189
4916
|
}
|
|
4190
4917
|
migrateExisting = ctx.looseSkillFiles.length > 0;
|
|
@@ -4194,7 +4921,7 @@ function decidePath4(ctx, intent, providedName) {
|
|
|
4194
4921
|
targetDir = ctx.cwd;
|
|
4195
4922
|
shouldCreateDir = false;
|
|
4196
4923
|
} else {
|
|
4197
|
-
targetDir =
|
|
4924
|
+
targetDir = join20(ctx.cwd, rawName);
|
|
4198
4925
|
shouldCreateDir = true;
|
|
4199
4926
|
}
|
|
4200
4927
|
} else if (decisionPath === "standalone") {
|
|
@@ -4202,7 +4929,7 @@ function decidePath4(ctx, intent, providedName) {
|
|
|
4202
4929
|
targetDir = ctx.cwd;
|
|
4203
4930
|
shouldCreateDir = false;
|
|
4204
4931
|
} else {
|
|
4205
|
-
targetDir =
|
|
4932
|
+
targetDir = join20(ctx.cwd, rawName);
|
|
4206
4933
|
shouldCreateDir = true;
|
|
4207
4934
|
}
|
|
4208
4935
|
}
|
|
@@ -4210,7 +4937,7 @@ function decidePath4(ctx, intent, providedName) {
|
|
|
4210
4937
|
}
|
|
4211
4938
|
function scaffold4(decision, ctx, migrateContent) {
|
|
4212
4939
|
const { targetDir, path, shouldCreateDir } = decision;
|
|
4213
|
-
if (
|
|
4940
|
+
if (existsSync21(targetDir) && shouldCreateDir) {
|
|
4214
4941
|
ui.fail("Target already exists");
|
|
4215
4942
|
process.exit(1);
|
|
4216
4943
|
}
|
|
@@ -4218,7 +4945,7 @@ function scaffold4(decision, ctx, migrateContent) {
|
|
|
4218
4945
|
mkdirSync6(targetDir, { recursive: true });
|
|
4219
4946
|
}
|
|
4220
4947
|
if (path === "plugin") {
|
|
4221
|
-
const pluginName =
|
|
4948
|
+
const pluginName = basename6(targetDir);
|
|
4222
4949
|
const copilotSpec = getProviderSpec("copilot");
|
|
4223
4950
|
const copilotManifestDir = dirname6(copilotSpec.manifestPath);
|
|
4224
4951
|
const pluginJson = {
|
|
@@ -4229,10 +4956,10 @@ function scaffold4(decision, ctx, migrateContent) {
|
|
|
4229
4956
|
displayName: pluginName,
|
|
4230
4957
|
keywords: ["example-keyword", "another-keyword"]
|
|
4231
4958
|
};
|
|
4232
|
-
mkdirSync6(
|
|
4233
|
-
writeFileSync5(
|
|
4959
|
+
mkdirSync6(join20(targetDir, copilotManifestDir), { recursive: true });
|
|
4960
|
+
writeFileSync5(join20(targetDir, copilotSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
|
|
4234
4961
|
const marketplaceDir = dirname6(copilotSpec.marketplacePath);
|
|
4235
|
-
mkdirSync6(
|
|
4962
|
+
mkdirSync6(join20(targetDir, marketplaceDir), { recursive: true });
|
|
4236
4963
|
const marketplaceJson = {
|
|
4237
4964
|
name: "local",
|
|
4238
4965
|
plugins: [
|
|
@@ -4245,9 +4972,9 @@ function scaffold4(decision, ctx, migrateContent) {
|
|
|
4245
4972
|
}
|
|
4246
4973
|
]
|
|
4247
4974
|
};
|
|
4248
|
-
writeFileSync5(
|
|
4975
|
+
writeFileSync5(join20(targetDir, copilotSpec.marketplacePath), JSON.stringify(marketplaceJson, null, 2));
|
|
4249
4976
|
const demoSkillName = "doraval";
|
|
4250
|
-
mkdirSync6(
|
|
4977
|
+
mkdirSync6(join20(targetDir, "skills", demoSkillName), { recursive: true });
|
|
4251
4978
|
let skillContent;
|
|
4252
4979
|
if (migrateContent) {
|
|
4253
4980
|
skillContent = migrateContent;
|
|
@@ -4276,19 +5003,19 @@ To test in Copilot:
|
|
|
4276
5003
|
1. Configure the .github/plugin as local source.
|
|
4277
5004
|
2. Restart/reload and invoke the skill.`;
|
|
4278
5005
|
}
|
|
4279
|
-
writeFileSync5(
|
|
4280
|
-
const readmePath =
|
|
4281
|
-
if (!
|
|
5006
|
+
writeFileSync5(join20(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
|
|
5007
|
+
const readmePath = join20(targetDir, "README.md");
|
|
5008
|
+
if (!existsSync21(readmePath)) {
|
|
4282
5009
|
writeFileSync5(readmePath, "# " + pluginName + `
|
|
4283
5010
|
|
|
4284
5011
|
Copilot plugin scaffolded by doraval.`);
|
|
4285
5012
|
}
|
|
4286
5013
|
} else {
|
|
4287
|
-
mkdirSync6(
|
|
5014
|
+
mkdirSync6(join20(targetDir, "skills", "doraval"), { recursive: true });
|
|
4288
5015
|
const skillBody = migrateContent || `# My Skill
|
|
4289
5016
|
|
|
4290
5017
|
Basic starter for Copilot.`;
|
|
4291
|
-
writeFileSync5(
|
|
5018
|
+
writeFileSync5(join20(targetDir, "skills", "doraval", "SKILL.md"), `---
|
|
4292
5019
|
name: doraval
|
|
4293
5020
|
description: Starter (local skill)
|
|
4294
5021
|
---
|
|
@@ -4296,14 +5023,14 @@ description: Starter (local skill)
|
|
|
4296
5023
|
${skillBody}`);
|
|
4297
5024
|
}
|
|
4298
5025
|
}
|
|
4299
|
-
var
|
|
5026
|
+
var import_picocolors19, new_default4;
|
|
4300
5027
|
var init_new4 = __esm(() => {
|
|
4301
5028
|
init_dist();
|
|
4302
5029
|
init_out();
|
|
4303
5030
|
init_context5();
|
|
4304
5031
|
init_prompt();
|
|
4305
5032
|
init_spec();
|
|
4306
|
-
|
|
5033
|
+
import_picocolors19 = __toESM(require_picocolors(), 1);
|
|
4307
5034
|
new_default4 = defineCommand({
|
|
4308
5035
|
meta: {
|
|
4309
5036
|
name: "new",
|
|
@@ -4342,8 +5069,8 @@ var init_new4 = __esm(() => {
|
|
|
4342
5069
|
}
|
|
4343
5070
|
scaffold4(decision, ctx, migrateContent);
|
|
4344
5071
|
ui.write(`
|
|
4345
|
-
${
|
|
4346
|
-
const cmdName = decision.path === "plugin" ? `/${
|
|
5072
|
+
${import_picocolors19.default.green("\u2713")} Created ${decision.path} at ${import_picocolors19.default.bold(decision.targetDir)}`);
|
|
5073
|
+
const cmdName = decision.path === "plugin" ? `/${basename6(decision.targetDir)}:doraval` : "/doraval (local skill)";
|
|
4347
5074
|
ui.info(` Command: ${cmdName}`);
|
|
4348
5075
|
if (decision.path === "plugin") {
|
|
4349
5076
|
ui.info(` Copilot manifest: .github/plugin/plugin.json`);
|
|
@@ -4367,8 +5094,8 @@ var exports_ui = {};
|
|
|
4367
5094
|
__export(exports_ui, {
|
|
4368
5095
|
default: () => ui_default
|
|
4369
5096
|
});
|
|
4370
|
-
import { existsSync as
|
|
4371
|
-
import { join as
|
|
5097
|
+
import { existsSync as existsSync22, readdirSync as readdirSync11, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3 } from "fs";
|
|
5098
|
+
import { join as join21 } from "path";
|
|
4372
5099
|
import { spawn } from "child_process";
|
|
4373
5100
|
function slugify2(title) {
|
|
4374
5101
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "untitled";
|
|
@@ -4376,8 +5103,8 @@ function slugify2(title) {
|
|
|
4376
5103
|
async function loadAllEntries(project) {
|
|
4377
5104
|
const journalsDir = getJournalsDir();
|
|
4378
5105
|
const entries = [];
|
|
4379
|
-
const globalPath =
|
|
4380
|
-
if (
|
|
5106
|
+
const globalPath = join21(journalsDir, "global.md");
|
|
5107
|
+
if (existsSync22(globalPath)) {
|
|
4381
5108
|
try {
|
|
4382
5109
|
const raw = await Bun.file(globalPath).text();
|
|
4383
5110
|
const parsed = parseJournalEntries(raw);
|
|
@@ -4385,8 +5112,8 @@ async function loadAllEntries(project) {
|
|
|
4385
5112
|
} catch {}
|
|
4386
5113
|
}
|
|
4387
5114
|
if (project) {
|
|
4388
|
-
const projPath =
|
|
4389
|
-
if (
|
|
5115
|
+
const projPath = join21(journalsDir, `${project}.md`);
|
|
5116
|
+
if (existsSync22(projPath)) {
|
|
4390
5117
|
try {
|
|
4391
5118
|
const raw = await Bun.file(projPath).text();
|
|
4392
5119
|
const parsed = parseJournalEntries(raw);
|
|
@@ -4397,10 +5124,10 @@ async function loadAllEntries(project) {
|
|
|
4397
5124
|
const staged = [];
|
|
4398
5125
|
try {
|
|
4399
5126
|
const pdir = project ? getPendingProjectDir(project) : null;
|
|
4400
|
-
if (pdir &&
|
|
4401
|
-
const files =
|
|
5127
|
+
if (pdir && existsSync22(pdir)) {
|
|
5128
|
+
const files = readdirSync11(pdir).filter((f) => f.endsWith(".md") && f !== ".gitkeep");
|
|
4402
5129
|
for (const f of files) {
|
|
4403
|
-
const txt = await Bun.file(
|
|
5130
|
+
const txt = await Bun.file(join21(pdir, f)).text();
|
|
4404
5131
|
const parsed = parseJournalEntries(txt);
|
|
4405
5132
|
parsed.forEach((e) => {
|
|
4406
5133
|
e._staged = true;
|
|
@@ -4416,13 +5143,13 @@ async function loadAllEntries(project) {
|
|
|
4416
5143
|
async function writePendingEntry(project, input) {
|
|
4417
5144
|
ensureDoravalDirs();
|
|
4418
5145
|
const pendingDir = getPendingProjectDir(project);
|
|
4419
|
-
if (!
|
|
4420
|
-
await Bun.write(
|
|
5146
|
+
if (!existsSync22(pendingDir)) {
|
|
5147
|
+
await Bun.write(join21(pendingDir, ".gitkeep"), "");
|
|
4421
5148
|
}
|
|
4422
5149
|
const date = new Date().toISOString().split("T")[0];
|
|
4423
5150
|
const slug = slugify2(input.title);
|
|
4424
5151
|
const filename = `${date}-${slug}.md`;
|
|
4425
|
-
const filePath =
|
|
5152
|
+
const filePath = join21(pendingDir, filename);
|
|
4426
5153
|
const content = `## ${input.title}
|
|
4427
5154
|
|
|
4428
5155
|
\`\`\`yaml
|
|
@@ -4438,6 +5165,25 @@ ${input.rationale}
|
|
|
4438
5165
|
await Bun.write(filePath, content);
|
|
4439
5166
|
return { filePath, filename };
|
|
4440
5167
|
}
|
|
5168
|
+
async function loadEvals(limit = 30) {
|
|
5169
|
+
const dir = getEvalsDir();
|
|
5170
|
+
if (!existsSync22(dir))
|
|
5171
|
+
return [];
|
|
5172
|
+
let files = readdirSync11(dir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, path: join21(dir, f) }));
|
|
5173
|
+
files.sort((a, b) => b.name.localeCompare(a.name));
|
|
5174
|
+
const results = [];
|
|
5175
|
+
for (const f of files.slice(0, limit)) {
|
|
5176
|
+
try {
|
|
5177
|
+
const raw = await Bun.file(f.path).text();
|
|
5178
|
+
const parsed = JSON.parse(raw);
|
|
5179
|
+
if (parsed && (parsed.schemaVersion === 1 || parsed.verdict || parsed.skill)) {
|
|
5180
|
+
results.push({ ...parsed, _filename: f.name });
|
|
5181
|
+
}
|
|
5182
|
+
} catch {}
|
|
5183
|
+
}
|
|
5184
|
+
results.sort((a, b) => (b.timestamp || "").localeCompare(a.timestamp || ""));
|
|
5185
|
+
return results.slice(0, limit);
|
|
5186
|
+
}
|
|
4441
5187
|
async function killPort(port) {
|
|
4442
5188
|
if (process.platform === "win32") {
|
|
4443
5189
|
return;
|
|
@@ -4459,10 +5205,10 @@ async function killPort(port) {
|
|
|
4459
5205
|
}
|
|
4460
5206
|
function readPid(p) {
|
|
4461
5207
|
const file = getPidFile(p);
|
|
4462
|
-
if (!
|
|
5208
|
+
if (!existsSync22(file))
|
|
4463
5209
|
return null;
|
|
4464
5210
|
try {
|
|
4465
|
-
const raw =
|
|
5211
|
+
const raw = readFileSync3(file, "utf8").trim();
|
|
4466
5212
|
const pid = parseInt(raw, 10);
|
|
4467
5213
|
if (isNaN(pid))
|
|
4468
5214
|
return null;
|
|
@@ -4495,13 +5241,13 @@ async function getDashboardHtml() {
|
|
|
4495
5241
|
return `<!doctype html><meta charset="utf-8"><body style="font-family:monospace;background:#111;color:#ddd;padding:2rem"><h1>doraval ui</h1><p>Dashboard HTML missing.</p><pre>${String(err)}</pre></body>`;
|
|
4496
5242
|
}
|
|
4497
5243
|
}
|
|
4498
|
-
var
|
|
5244
|
+
var import_picocolors20, DEFAULT_PORT = 3737, getPidFile = (p) => join21(getDoravalDir(), `ui.${p}.pid`), ui_default;
|
|
4499
5245
|
var init_ui = __esm(() => {
|
|
4500
5246
|
init_journal_config();
|
|
4501
5247
|
init_journal_parse();
|
|
4502
5248
|
init_context();
|
|
4503
5249
|
init_hook();
|
|
4504
|
-
|
|
5250
|
+
import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
4505
5251
|
ui_default = {
|
|
4506
5252
|
async run({ args }) {
|
|
4507
5253
|
const port = Number(args.port) || DEFAULT_PORT;
|
|
@@ -4515,7 +5261,7 @@ var init_ui = __esm(() => {
|
|
|
4515
5261
|
if (existingPid) {
|
|
4516
5262
|
const url2 = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
|
|
4517
5263
|
console.error(` Dashboard running (pid ${existingPid})`);
|
|
4518
|
-
console.error(` URL: ${
|
|
5264
|
+
console.error(` URL: ${import_picocolors20.default.underline(import_picocolors20.default.cyan(url2))}`);
|
|
4519
5265
|
} else {
|
|
4520
5266
|
console.error(` No dashboard running.`);
|
|
4521
5267
|
}
|
|
@@ -4524,7 +5270,7 @@ var init_ui = __esm(() => {
|
|
|
4524
5270
|
if (existingPid && !force) {
|
|
4525
5271
|
const url2 = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
|
|
4526
5272
|
console.error(` Dashboard already running (pid ${existingPid}).`);
|
|
4527
|
-
console.error(` URL: ${
|
|
5273
|
+
console.error(` URL: ${import_picocolors20.default.underline(import_picocolors20.default.cyan(url2))}`);
|
|
4528
5274
|
if (shouldOpen && process.stdout.isTTY) {
|
|
4529
5275
|
try {
|
|
4530
5276
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
@@ -4568,6 +5314,7 @@ var init_ui = __esm(() => {
|
|
|
4568
5314
|
if (url2.pathname === "/api/status") {
|
|
4569
5315
|
return Response.json({
|
|
4570
5316
|
project: project || null,
|
|
5317
|
+
doravalRoot: getDoravalDir(),
|
|
4571
5318
|
doravalDir: getJournalsDir(),
|
|
4572
5319
|
hasConfig: !!config,
|
|
4573
5320
|
repo: config?.journal?.repo ?? null
|
|
@@ -4637,8 +5384,8 @@ var init_ui = __esm(() => {
|
|
|
4637
5384
|
return Response.json({ error: "filename required" }, { status: 400 });
|
|
4638
5385
|
}
|
|
4639
5386
|
const pdir = getPendingProjectDir(project);
|
|
4640
|
-
const filePath =
|
|
4641
|
-
if (
|
|
5387
|
+
const filePath = join21(pdir, filename);
|
|
5388
|
+
if (existsSync22(filePath)) {
|
|
4642
5389
|
try {
|
|
4643
5390
|
await Bun.file(filePath).unlink();
|
|
4644
5391
|
} catch {}
|
|
@@ -4646,6 +5393,10 @@ var init_ui = __esm(() => {
|
|
|
4646
5393
|
}
|
|
4647
5394
|
return Response.json({ error: "not found" }, { status: 404 });
|
|
4648
5395
|
}
|
|
5396
|
+
if (url2.pathname === "/api/evals") {
|
|
5397
|
+
const evals = await loadEvals(25);
|
|
5398
|
+
return Response.json({ evals });
|
|
5399
|
+
}
|
|
4649
5400
|
if (url2.pathname.startsWith("/api/")) {
|
|
4650
5401
|
return Response.json({ error: "Not found" }, { status: 404 });
|
|
4651
5402
|
}
|
|
@@ -4660,19 +5411,21 @@ var init_ui = __esm(() => {
|
|
|
4660
5411
|
const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${server.port}`;
|
|
4661
5412
|
writePid(process.pid, port);
|
|
4662
5413
|
const msg = `
|
|
4663
|
-
${
|
|
4664
|
-
${
|
|
4665
|
-
${
|
|
5414
|
+
${import_picocolors20.default.blue("\u25C9")} dora local dashboard
|
|
5415
|
+
${import_picocolors20.default.dim("Project:")} ${project ? import_picocolors20.default.white(project) : import_picocolors20.default.yellow("none (run dora init)")}
|
|
5416
|
+
${import_picocolors20.default.dim("Data dir:")} ${getDoravalDir()}
|
|
5417
|
+
${import_picocolors20.default.dim("URL:")} ${import_picocolors20.default.underline(import_picocolors20.default.cyan(url))}
|
|
4666
5418
|
|
|
4667
|
-
${
|
|
5419
|
+
${import_picocolors20.default.dim("Press Ctrl+C to stop")}
|
|
4668
5420
|
`;
|
|
4669
5421
|
console.error(msg);
|
|
5422
|
+
console.error(` ${import_picocolors20.default.dim("Tip:")} data location = ${getDoravalDir()} (set DORAVAL_HOME to change)`);
|
|
4670
5423
|
if (shouldOpen && process.stdout.isTTY) {
|
|
4671
5424
|
try {
|
|
4672
5425
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
4673
5426
|
spawn(opener, [url], { stdio: "ignore", detached: true }).unref();
|
|
4674
5427
|
} catch {
|
|
4675
|
-
console.error(
|
|
5428
|
+
console.error(import_picocolors20.default.dim(` Could not auto-open. Visit ${url}`));
|
|
4676
5429
|
}
|
|
4677
5430
|
}
|
|
4678
5431
|
const cleanup = () => {
|
|
@@ -4689,7 +5442,7 @@ var init_ui = __esm(() => {
|
|
|
4689
5442
|
});
|
|
4690
5443
|
|
|
4691
5444
|
// src/validators/claude/skill.ts
|
|
4692
|
-
import { existsSync as
|
|
5445
|
+
import { existsSync as existsSync23 } from "fs";
|
|
4693
5446
|
import { resolve as resolve5 } from "path";
|
|
4694
5447
|
var claudeSkillValidator;
|
|
4695
5448
|
var init_skill = __esm(() => {
|
|
@@ -4700,7 +5453,7 @@ var init_skill = __esm(() => {
|
|
|
4700
5453
|
name: "Claude Skill",
|
|
4701
5454
|
description: "Validates SKILL.md per current Claude Code spec: frontmatter (name/description relaxed to recommended; directory name usually provides the /command), body, supporting files, dynamic injection (!`cmd`), substitutions ($ARGUMENTS, ${CLAUDE_*}), and advanced fields (allowed-tools, context, disable-model-invocation, when_to_use, etc.)",
|
|
4702
5455
|
detect(dir) {
|
|
4703
|
-
return
|
|
5456
|
+
return existsSync23(resolve5(dir, "SKILL.md"));
|
|
4704
5457
|
},
|
|
4705
5458
|
async validate(dir, _opts) {
|
|
4706
5459
|
const loaded = await loadSkill(dir);
|
|
@@ -4718,8 +5471,8 @@ var init_skill = __esm(() => {
|
|
|
4718
5471
|
});
|
|
4719
5472
|
|
|
4720
5473
|
// src/validators/claude/plugin.ts
|
|
4721
|
-
import { existsSync as
|
|
4722
|
-
import { resolve as resolve6, join as
|
|
5474
|
+
import { existsSync as existsSync24, readdirSync as readdirSync12 } from "fs";
|
|
5475
|
+
import { resolve as resolve6, join as join22 } from "path";
|
|
4723
5476
|
function levenshtein(a, b) {
|
|
4724
5477
|
if (a === b)
|
|
4725
5478
|
return 0;
|
|
@@ -4811,7 +5564,7 @@ var init_plugin = __esm(() => {
|
|
|
4811
5564
|
name: "Claude Plugin",
|
|
4812
5565
|
description: "Validates .claude-plugin/plugin.json manifest (complete schema per Plugins reference), component path rules (replace vs augment), .claude-plugin/ purity, default dirs, single-root-skill layout, unrecognized fields + suggestions, and structure",
|
|
4813
5566
|
detect(dir) {
|
|
4814
|
-
return
|
|
5567
|
+
return existsSync24(resolve6(dir, ".claude-plugin", "plugin.json"));
|
|
4815
5568
|
},
|
|
4816
5569
|
async validate(dir, _opts) {
|
|
4817
5570
|
const errors = [];
|
|
@@ -4825,7 +5578,7 @@ var init_plugin = __esm(() => {
|
|
|
4825
5578
|
manifest = JSON.parse(raw);
|
|
4826
5579
|
passes.push(".claude-plugin/plugin.json is valid JSON");
|
|
4827
5580
|
} catch (err) {
|
|
4828
|
-
if (!
|
|
5581
|
+
if (!existsSync24(manifestPath)) {
|
|
4829
5582
|
errors.push(`.claude-plugin/plugin.json is missing (looked for ${manifestPath})`);
|
|
4830
5583
|
warnings.push("Hint: Run `doraval claude new` (or `dora claude new`) to scaffold a new Claude plugin in this directory.");
|
|
4831
5584
|
} else {
|
|
@@ -4834,7 +5587,7 @@ var init_plugin = __esm(() => {
|
|
|
4834
5587
|
return { errors, warnings, passes };
|
|
4835
5588
|
}
|
|
4836
5589
|
try {
|
|
4837
|
-
const entries =
|
|
5590
|
+
const entries = readdirSync12(dotClaudePluginDir);
|
|
4838
5591
|
const unexpected = entries.filter((e) => e !== "plugin.json");
|
|
4839
5592
|
if (unexpected.length > 0) {
|
|
4840
5593
|
for (const e of unexpected) {
|
|
@@ -4921,7 +5674,7 @@ var init_plugin = __esm(() => {
|
|
|
4921
5674
|
errors.push(`${field}: path "${s}" must start with "./"`);
|
|
4922
5675
|
} else if (s.includes("..")) {
|
|
4923
5676
|
errors.push(`${field}: path "${s}" must not use ".." (paths are confined to the plugin tree after cache copy)`);
|
|
4924
|
-
} else if (
|
|
5677
|
+
} else if (existsSync24(resolve6(dir, s))) {
|
|
4925
5678
|
passes.push(`${field}: path "${s}" exists`);
|
|
4926
5679
|
} else {
|
|
4927
5680
|
warnings.push(`${field}: path "${s}" does not exist on disk`);
|
|
@@ -4971,11 +5724,11 @@ var init_plugin = __esm(() => {
|
|
|
4971
5724
|
passes.push(`dependencies: declares ${manifest.dependencies.length} plugin dependency/ies`);
|
|
4972
5725
|
}
|
|
4973
5726
|
const skillsDir = resolve6(dir, "skills");
|
|
4974
|
-
if (
|
|
4975
|
-
const entries =
|
|
5727
|
+
if (existsSync24(skillsDir)) {
|
|
5728
|
+
const entries = readdirSync12(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
4976
5729
|
for (const e of entries) {
|
|
4977
|
-
const md =
|
|
4978
|
-
if (
|
|
5730
|
+
const md = join22(skillsDir, e.name, "SKILL.md");
|
|
5731
|
+
if (existsSync24(md)) {
|
|
4979
5732
|
passes.push(`skills/${e.name}/SKILL.md exists`);
|
|
4980
5733
|
} else {
|
|
4981
5734
|
errors.push(`skills/${e.name}/ is missing SKILL.md`);
|
|
@@ -4986,8 +5739,8 @@ var init_plugin = __esm(() => {
|
|
|
4986
5739
|
}
|
|
4987
5740
|
}
|
|
4988
5741
|
const commandsDir = resolve6(dir, "commands");
|
|
4989
|
-
if (
|
|
4990
|
-
const mds =
|
|
5742
|
+
if (existsSync24(commandsDir)) {
|
|
5743
|
+
const mds = readdirSync12(commandsDir).filter((f) => f.endsWith(".md"));
|
|
4991
5744
|
if (mds.length) {
|
|
4992
5745
|
passes.push(`commands/ has ${mds.length} .md file(s)`);
|
|
4993
5746
|
}
|
|
@@ -4996,8 +5749,8 @@ var init_plugin = __esm(() => {
|
|
|
4996
5749
|
}
|
|
4997
5750
|
}
|
|
4998
5751
|
const agentsDir = resolve6(dir, "agents");
|
|
4999
|
-
if (
|
|
5000
|
-
const mds =
|
|
5752
|
+
if (existsSync24(agentsDir)) {
|
|
5753
|
+
const mds = readdirSync12(agentsDir).filter((f) => f.endsWith(".md"));
|
|
5001
5754
|
if (mds.length) {
|
|
5002
5755
|
passes.push(`agents/ has ${mds.length} .md file(s)`);
|
|
5003
5756
|
}
|
|
@@ -5005,30 +5758,30 @@ var init_plugin = __esm(() => {
|
|
|
5005
5758
|
warnings.push('agents/ co-exists with manifest "agents" \u2014 manifest replaces default (dir ignored)');
|
|
5006
5759
|
}
|
|
5007
5760
|
}
|
|
5008
|
-
if (
|
|
5761
|
+
if (existsSync24(resolve6(dir, "output-styles"))) {
|
|
5009
5762
|
passes.push("output-styles/ directory present");
|
|
5010
5763
|
if (manifest.outputStyles)
|
|
5011
5764
|
warnings.push("output-styles/ co-exists with manifest outputStyles \u2014 manifest wins");
|
|
5012
5765
|
}
|
|
5013
|
-
if (
|
|
5766
|
+
if (existsSync24(resolve6(dir, "themes")))
|
|
5014
5767
|
passes.push("themes/ present (experimental)");
|
|
5015
|
-
if (
|
|
5768
|
+
if (existsSync24(resolve6(dir, "monitors")) || manifest.experimental?.monitors) {
|
|
5016
5769
|
passes.push("monitors config present (experimental)");
|
|
5017
5770
|
}
|
|
5018
|
-
if (
|
|
5771
|
+
if (existsSync24(resolve6(dir, "bin")))
|
|
5019
5772
|
passes.push("bin/ present (adds executables to Bash tool $PATH)");
|
|
5020
|
-
if (
|
|
5773
|
+
if (existsSync24(resolve6(dir, "settings.json")))
|
|
5021
5774
|
passes.push("settings.json present (plugin defaults for agent/statusline)");
|
|
5022
|
-
if (
|
|
5775
|
+
if (existsSync24(resolve6(dir, "README.md")))
|
|
5023
5776
|
passes.push("README.md present");
|
|
5024
|
-
if (
|
|
5777
|
+
if (existsSync24(resolve6(dir, ".mcp.json")))
|
|
5025
5778
|
passes.push(".mcp.json present (validated by claude:mcp)");
|
|
5026
|
-
if (
|
|
5779
|
+
if (existsSync24(resolve6(dir, ".lsp.json")))
|
|
5027
5780
|
passes.push(".lsp.json present (validated by claude:lsp when registered)");
|
|
5028
|
-
if (
|
|
5781
|
+
if (existsSync24(resolve6(dir, "hooks/hooks.json")) || existsSync24(resolve6(dir, "hooks.json"))) {
|
|
5029
5782
|
passes.push("hooks config present (validated by claude:hooks)");
|
|
5030
5783
|
}
|
|
5031
|
-
if (
|
|
5784
|
+
if (existsSync24(resolve6(dir, "SKILL.md")) && !existsSync24(skillsDir) && manifest.skills === undefined) {
|
|
5032
5785
|
passes.push('Root SKILL.md detected \u2014 plugin will be treated as a single-skill plugin (prefer frontmatter "name" for stable /command)');
|
|
5033
5786
|
}
|
|
5034
5787
|
return { errors, warnings, passes };
|
|
@@ -5037,8 +5790,8 @@ var init_plugin = __esm(() => {
|
|
|
5037
5790
|
});
|
|
5038
5791
|
|
|
5039
5792
|
// src/validators/claude/marketplace.ts
|
|
5040
|
-
import { existsSync as
|
|
5041
|
-
import { resolve as resolve7, join as
|
|
5793
|
+
import { existsSync as existsSync25, readdirSync as readdirSync13 } from "fs";
|
|
5794
|
+
import { resolve as resolve7, join as join23 } from "path";
|
|
5042
5795
|
var claudeMarketplaceValidator;
|
|
5043
5796
|
var init_marketplace = __esm(() => {
|
|
5044
5797
|
claudeMarketplaceValidator = {
|
|
@@ -5047,18 +5800,18 @@ var init_marketplace = __esm(() => {
|
|
|
5047
5800
|
name: "Claude Plugin Marketplace",
|
|
5048
5801
|
description: "Validates .claude-plugin/marketplace.json or plugins/ marketplace layouts (plugins array with sources)",
|
|
5049
5802
|
detect(dir) {
|
|
5050
|
-
if (
|
|
5803
|
+
if (existsSync25(resolve7(dir, ".claude-plugin", "marketplace.json")))
|
|
5051
5804
|
return true;
|
|
5052
5805
|
const pluginsDir = resolve7(dir, "plugins");
|
|
5053
|
-
if (!
|
|
5806
|
+
if (!existsSync25(pluginsDir))
|
|
5054
5807
|
return false;
|
|
5055
5808
|
try {
|
|
5056
|
-
const entries =
|
|
5809
|
+
const entries = readdirSync13(pluginsDir, { withFileTypes: true });
|
|
5057
5810
|
for (const entry of entries) {
|
|
5058
5811
|
if (!entry.isDirectory())
|
|
5059
5812
|
continue;
|
|
5060
|
-
const hasSkills =
|
|
5061
|
-
const hasManifest =
|
|
5813
|
+
const hasSkills = existsSync25(join23(pluginsDir, entry.name, "skills"));
|
|
5814
|
+
const hasManifest = existsSync25(join23(pluginsDir, entry.name, ".claude-plugin", "plugin.json"));
|
|
5062
5815
|
if (hasSkills || hasManifest)
|
|
5063
5816
|
return true;
|
|
5064
5817
|
}
|
|
@@ -5070,9 +5823,9 @@ var init_marketplace = __esm(() => {
|
|
|
5070
5823
|
const warnings = [];
|
|
5071
5824
|
const passes = [];
|
|
5072
5825
|
const claudeMktPath = resolve7(dir, ".claude-plugin", "marketplace.json");
|
|
5073
|
-
const hasClaudeMkt =
|
|
5826
|
+
const hasClaudeMkt = existsSync25(claudeMktPath);
|
|
5074
5827
|
const pluginsDir = resolve7(dir, "plugins");
|
|
5075
|
-
const hasPluginsDirLayout =
|
|
5828
|
+
const hasPluginsDirLayout = existsSync25(pluginsDir);
|
|
5076
5829
|
if (!hasClaudeMkt && !hasPluginsDirLayout) {
|
|
5077
5830
|
errors.push("Missing .claude-plugin/marketplace.json or plugins/ directory");
|
|
5078
5831
|
return { errors, warnings, passes };
|
|
@@ -5117,9 +5870,9 @@ var init_marketplace = __esm(() => {
|
|
|
5117
5870
|
const src = String(p.source);
|
|
5118
5871
|
passes.push(`plugins[${i}].source: "${src}"`);
|
|
5119
5872
|
const srcDir = resolve7(dir, src);
|
|
5120
|
-
if (
|
|
5121
|
-
const hasManifest =
|
|
5122
|
-
const hasSkills =
|
|
5873
|
+
if (existsSync25(srcDir)) {
|
|
5874
|
+
const hasManifest = existsSync25(resolve7(srcDir, ".claude-plugin", "plugin.json"));
|
|
5875
|
+
const hasSkills = existsSync25(resolve7(srcDir, "skills"));
|
|
5123
5876
|
if (hasManifest || hasSkills) {
|
|
5124
5877
|
passes.push(`plugins[${i}]: source exists (${hasManifest ? "manifest" : "skills/"})`);
|
|
5125
5878
|
} else {
|
|
@@ -5135,12 +5888,12 @@ var init_marketplace = __esm(() => {
|
|
|
5135
5888
|
passes.push(`plugins[${i}].category: "${p.category}"`);
|
|
5136
5889
|
}
|
|
5137
5890
|
}
|
|
5138
|
-
if (
|
|
5891
|
+
if (existsSync25(resolve7(dir, "README.md"))) {
|
|
5139
5892
|
passes.push("README.md exists at marketplace root");
|
|
5140
5893
|
} else {
|
|
5141
5894
|
warnings.push("No README.md at marketplace root \u2014 recommended for discoverability");
|
|
5142
5895
|
}
|
|
5143
|
-
if (
|
|
5896
|
+
if (existsSync25(resolve7(dir, "LICENSE"))) {
|
|
5144
5897
|
passes.push("LICENSE exists at marketplace root");
|
|
5145
5898
|
} else {
|
|
5146
5899
|
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
@@ -5149,27 +5902,27 @@ var init_marketplace = __esm(() => {
|
|
|
5149
5902
|
}
|
|
5150
5903
|
if (hasPluginsDirLayout) {
|
|
5151
5904
|
passes.push("plugins/ directory exists");
|
|
5152
|
-
const pluginEntries =
|
|
5905
|
+
const pluginEntries = readdirSync13(pluginsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
5153
5906
|
if (pluginEntries.length === 0) {
|
|
5154
5907
|
errors.push("plugins/ directory is empty \u2014 expected at least one plugin");
|
|
5155
5908
|
return { errors, warnings, passes };
|
|
5156
5909
|
}
|
|
5157
5910
|
passes.push(`${pluginEntries.length} plugin(s) found`);
|
|
5158
|
-
if (
|
|
5911
|
+
if (existsSync25(resolve7(dir, "README.md"))) {
|
|
5159
5912
|
passes.push("README.md exists at marketplace root");
|
|
5160
5913
|
} else {
|
|
5161
5914
|
warnings.push("No README.md at marketplace root \u2014 recommended for discoverability");
|
|
5162
5915
|
}
|
|
5163
|
-
if (
|
|
5916
|
+
if (existsSync25(resolve7(dir, "LICENSE"))) {
|
|
5164
5917
|
passes.push("LICENSE exists at marketplace root");
|
|
5165
5918
|
} else {
|
|
5166
5919
|
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
5167
5920
|
}
|
|
5168
5921
|
for (const plugin of pluginEntries) {
|
|
5169
|
-
const pluginPath =
|
|
5170
|
-
const hasSkills =
|
|
5171
|
-
const hasManifest =
|
|
5172
|
-
const hasReadme =
|
|
5922
|
+
const pluginPath = join23(pluginsDir, plugin.name);
|
|
5923
|
+
const hasSkills = existsSync25(join23(pluginPath, "skills"));
|
|
5924
|
+
const hasManifest = existsSync25(join23(pluginPath, ".claude-plugin", "plugin.json"));
|
|
5925
|
+
const hasReadme = existsSync25(join23(pluginPath, "README.md"));
|
|
5173
5926
|
if (hasManifest || hasSkills) {
|
|
5174
5927
|
passes.push(`Plugin "${plugin.name}" has ${hasManifest ? "manifest" : "skills/"}`);
|
|
5175
5928
|
} else {
|
|
@@ -5187,7 +5940,7 @@ var init_marketplace = __esm(() => {
|
|
|
5187
5940
|
});
|
|
5188
5941
|
|
|
5189
5942
|
// src/validators/claude/hooks.ts
|
|
5190
|
-
import { existsSync as
|
|
5943
|
+
import { existsSync as existsSync26 } from "fs";
|
|
5191
5944
|
import { resolve as resolve8 } from "path";
|
|
5192
5945
|
var KNOWN_EVENTS, claudeHooksValidator;
|
|
5193
5946
|
var init_hooks = __esm(() => {
|
|
@@ -5229,13 +5982,13 @@ var init_hooks = __esm(() => {
|
|
|
5229
5982
|
name: "Claude Hooks",
|
|
5230
5983
|
description: "Validates hooks/hooks.json (or root hooks.json): all lifecycle events per Plugins reference, hook group structure (matcher + hooks[]), supported hook types (command, http, mcp_tool, prompt, agent)",
|
|
5231
5984
|
detect(dir) {
|
|
5232
|
-
return
|
|
5985
|
+
return existsSync26(resolve8(dir, "hooks", "hooks.json")) || existsSync26(resolve8(dir, "hooks.json"));
|
|
5233
5986
|
},
|
|
5234
5987
|
async validate(dir, _opts) {
|
|
5235
5988
|
const errors = [];
|
|
5236
5989
|
const warnings = [];
|
|
5237
5990
|
const passes = [];
|
|
5238
|
-
const hooksPath =
|
|
5991
|
+
const hooksPath = existsSync26(resolve8(dir, "hooks", "hooks.json")) ? resolve8(dir, "hooks", "hooks.json") : resolve8(dir, "hooks.json");
|
|
5239
5992
|
let config;
|
|
5240
5993
|
try {
|
|
5241
5994
|
const raw = await Bun.file(hooksPath).text();
|
|
@@ -5301,7 +6054,7 @@ var init_hooks = __esm(() => {
|
|
|
5301
6054
|
});
|
|
5302
6055
|
|
|
5303
6056
|
// src/validators/claude/mcp.ts
|
|
5304
|
-
import { existsSync as
|
|
6057
|
+
import { existsSync as existsSync27 } from "fs";
|
|
5305
6058
|
import { resolve as resolve9 } from "path";
|
|
5306
6059
|
var claudeMcpValidator;
|
|
5307
6060
|
var init_mcp = __esm(() => {
|
|
@@ -5311,7 +6064,7 @@ var init_mcp = __esm(() => {
|
|
|
5311
6064
|
name: "Claude MCP Config",
|
|
5312
6065
|
description: "Validates .mcp.json (or inline via plugin.json mcpServers): server entries (stdio: command+args, or url), env, cwd, ${CLAUDE_PLUGIN_ROOT} etc. substitutions per Plugins reference",
|
|
5313
6066
|
detect(dir) {
|
|
5314
|
-
return
|
|
6067
|
+
return existsSync27(resolve9(dir, ".mcp.json"));
|
|
5315
6068
|
},
|
|
5316
6069
|
async validate(dir, _opts) {
|
|
5317
6070
|
const errors = [];
|
|
@@ -5371,8 +6124,8 @@ var init_mcp = __esm(() => {
|
|
|
5371
6124
|
});
|
|
5372
6125
|
|
|
5373
6126
|
// src/validators/claude/subagent.ts
|
|
5374
|
-
import { existsSync as
|
|
5375
|
-
import { resolve as resolve10, join as
|
|
6127
|
+
import { existsSync as existsSync28, readdirSync as readdirSync14 } from "fs";
|
|
6128
|
+
import { resolve as resolve10, join as join24 } from "path";
|
|
5376
6129
|
var claudeSubagentValidator;
|
|
5377
6130
|
var init_subagent = __esm(() => {
|
|
5378
6131
|
init_frontmatter();
|
|
@@ -5383,10 +6136,10 @@ var init_subagent = __esm(() => {
|
|
|
5383
6136
|
description: "Validates agents/*.md (plugin subagents): frontmatter per spec (name, description, model, effort, maxTurns, tools, disallowedTools, skills, memory, background, isolation=worktree), body; warns on disallowed fields (hooks, mcpServers, permissionMode) for security",
|
|
5384
6137
|
detect(dir) {
|
|
5385
6138
|
const agentsDir = resolve10(dir, "agents");
|
|
5386
|
-
if (!
|
|
6139
|
+
if (!existsSync28(agentsDir))
|
|
5387
6140
|
return false;
|
|
5388
6141
|
try {
|
|
5389
|
-
return
|
|
6142
|
+
return readdirSync14(agentsDir).some((f) => f.endsWith(".md"));
|
|
5390
6143
|
} catch {
|
|
5391
6144
|
return false;
|
|
5392
6145
|
}
|
|
@@ -5396,7 +6149,7 @@ var init_subagent = __esm(() => {
|
|
|
5396
6149
|
const warnings = [];
|
|
5397
6150
|
const passes = [];
|
|
5398
6151
|
const agentsDir = resolve10(dir, "agents");
|
|
5399
|
-
const mdFiles =
|
|
6152
|
+
const mdFiles = readdirSync14(agentsDir).filter((f) => f.endsWith(".md"));
|
|
5400
6153
|
if (mdFiles.length === 0) {
|
|
5401
6154
|
errors.push("agents/ directory has no .md files");
|
|
5402
6155
|
return { errors, warnings, passes };
|
|
@@ -5417,7 +6170,7 @@ var init_subagent = __esm(() => {
|
|
|
5417
6170
|
]);
|
|
5418
6171
|
const DISALLOWED = new Set(["hooks", "mcpServers", "permissionMode"]);
|
|
5419
6172
|
for (const file of mdFiles) {
|
|
5420
|
-
const filePath =
|
|
6173
|
+
const filePath = join24(agentsDir, file);
|
|
5421
6174
|
const raw = await Bun.file(filePath).text();
|
|
5422
6175
|
try {
|
|
5423
6176
|
const parsed = parseFrontmatter(raw);
|
|
@@ -5463,8 +6216,8 @@ var init_subagent = __esm(() => {
|
|
|
5463
6216
|
});
|
|
5464
6217
|
|
|
5465
6218
|
// src/validators/claude/command.ts
|
|
5466
|
-
import { existsSync as
|
|
5467
|
-
import { resolve as resolve11, join as
|
|
6219
|
+
import { existsSync as existsSync29, readdirSync as readdirSync15 } from "fs";
|
|
6220
|
+
import { resolve as resolve11, join as join25 } from "path";
|
|
5468
6221
|
var claudeCommandValidator;
|
|
5469
6222
|
var init_command = __esm(() => {
|
|
5470
6223
|
init_frontmatter();
|
|
@@ -5475,10 +6228,10 @@ var init_command = __esm(() => {
|
|
|
5475
6228
|
description: "Validates commands/ (or legacy .claude/commands/) .md files: frontmatter (including rich skill fields), description, body",
|
|
5476
6229
|
detect(dir) {
|
|
5477
6230
|
const commandsDir = resolve11(dir, "commands");
|
|
5478
|
-
if (!
|
|
6231
|
+
if (!existsSync29(commandsDir))
|
|
5479
6232
|
return false;
|
|
5480
6233
|
try {
|
|
5481
|
-
return
|
|
6234
|
+
return readdirSync15(commandsDir).some((f) => f.endsWith(".md"));
|
|
5482
6235
|
} catch {
|
|
5483
6236
|
return false;
|
|
5484
6237
|
}
|
|
@@ -5488,14 +6241,14 @@ var init_command = __esm(() => {
|
|
|
5488
6241
|
const warnings = [];
|
|
5489
6242
|
const passes = [];
|
|
5490
6243
|
const commandsDir = resolve11(dir, "commands");
|
|
5491
|
-
const mdFiles =
|
|
6244
|
+
const mdFiles = readdirSync15(commandsDir).filter((f) => f.endsWith(".md"));
|
|
5492
6245
|
if (mdFiles.length === 0) {
|
|
5493
6246
|
errors.push("commands/ directory has no .md files");
|
|
5494
6247
|
return { errors, warnings, passes };
|
|
5495
6248
|
}
|
|
5496
6249
|
passes.push(`${mdFiles.length} command definition(s) found`);
|
|
5497
6250
|
for (const file of mdFiles) {
|
|
5498
|
-
const filePath =
|
|
6251
|
+
const filePath = join25(commandsDir, file);
|
|
5499
6252
|
const raw = await Bun.file(filePath).text();
|
|
5500
6253
|
try {
|
|
5501
6254
|
const parsed = parseFrontmatter(raw);
|
|
@@ -5524,7 +6277,7 @@ var init_command = __esm(() => {
|
|
|
5524
6277
|
});
|
|
5525
6278
|
|
|
5526
6279
|
// src/validators/claude/memory.ts
|
|
5527
|
-
import { existsSync as
|
|
6280
|
+
import { existsSync as existsSync30 } from "fs";
|
|
5528
6281
|
import { resolve as resolve12 } from "path";
|
|
5529
6282
|
var claudeMemoryValidator;
|
|
5530
6283
|
var init_memory = __esm(() => {
|
|
@@ -5534,7 +6287,7 @@ var init_memory = __esm(() => {
|
|
|
5534
6287
|
name: "Claude CLAUDE.md",
|
|
5535
6288
|
description: "Validates CLAUDE.md: non-empty, length recommendations, @path imports",
|
|
5536
6289
|
detect(dir) {
|
|
5537
|
-
return
|
|
6290
|
+
return existsSync30(resolve12(dir, "CLAUDE.md"));
|
|
5538
6291
|
},
|
|
5539
6292
|
async validate(dir, _opts) {
|
|
5540
6293
|
const errors = [];
|
|
@@ -5559,7 +6312,7 @@ var init_memory = __esm(() => {
|
|
|
5559
6312
|
while ((match = importRegex.exec(raw)) !== null) {
|
|
5560
6313
|
const importPath = match[1];
|
|
5561
6314
|
const resolvedImport = resolve12(dir, importPath);
|
|
5562
|
-
if (
|
|
6315
|
+
if (existsSync30(resolvedImport)) {
|
|
5563
6316
|
passes.push(`@import "${importPath}" exists`);
|
|
5564
6317
|
} else {
|
|
5565
6318
|
warnings.push(`@import "${importPath}" \u2014 file not found at ${resolvedImport}`);
|
|
@@ -5571,7 +6324,7 @@ var init_memory = __esm(() => {
|
|
|
5571
6324
|
});
|
|
5572
6325
|
|
|
5573
6326
|
// src/validators/claude/lsp.ts
|
|
5574
|
-
import { existsSync as
|
|
6327
|
+
import { existsSync as existsSync31 } from "fs";
|
|
5575
6328
|
import { resolve as resolve13 } from "path";
|
|
5576
6329
|
var claudeLspValidator;
|
|
5577
6330
|
var init_lsp = __esm(() => {
|
|
@@ -5581,7 +6334,7 @@ var init_lsp = __esm(() => {
|
|
|
5581
6334
|
name: "Claude LSP Servers",
|
|
5582
6335
|
description: "Validates .lsp.json (or plugin.json lspServers): language server configs with required command + extensionToLanguage; optional transport, env, settings, diagnostics etc. (binaries installed separately)",
|
|
5583
6336
|
detect(dir) {
|
|
5584
|
-
return
|
|
6337
|
+
return existsSync31(resolve13(dir, ".lsp.json")) || existsSync31(resolve13(dir, ".claude-plugin", "plugin.json"));
|
|
5585
6338
|
},
|
|
5586
6339
|
async validate(dir, _opts) {
|
|
5587
6340
|
const errors = [];
|
|
@@ -5589,7 +6342,7 @@ var init_lsp = __esm(() => {
|
|
|
5589
6342
|
const passes = [];
|
|
5590
6343
|
let cfg = null;
|
|
5591
6344
|
const lspPath = resolve13(dir, ".lsp.json");
|
|
5592
|
-
if (
|
|
6345
|
+
if (existsSync31(lspPath)) {
|
|
5593
6346
|
try {
|
|
5594
6347
|
cfg = JSON.parse(await Bun.file(lspPath).text());
|
|
5595
6348
|
passes.push(".lsp.json is valid JSON");
|
|
@@ -5599,7 +6352,7 @@ var init_lsp = __esm(() => {
|
|
|
5599
6352
|
}
|
|
5600
6353
|
} else {
|
|
5601
6354
|
const manifestPath = resolve13(dir, ".claude-plugin", "plugin.json");
|
|
5602
|
-
if (
|
|
6355
|
+
if (existsSync31(manifestPath)) {
|
|
5603
6356
|
try {
|
|
5604
6357
|
const m = JSON.parse(await Bun.file(manifestPath).text());
|
|
5605
6358
|
if (m && m.lspServers && typeof m.lspServers === "object") {
|
|
@@ -5610,7 +6363,7 @@ var init_lsp = __esm(() => {
|
|
|
5610
6363
|
}
|
|
5611
6364
|
}
|
|
5612
6365
|
if (!cfg) {
|
|
5613
|
-
if (!
|
|
6366
|
+
if (!existsSync31(lspPath)) {
|
|
5614
6367
|
return { errors, warnings, passes };
|
|
5615
6368
|
}
|
|
5616
6369
|
}
|
|
@@ -5639,7 +6392,7 @@ var init_lsp = __esm(() => {
|
|
|
5639
6392
|
});
|
|
5640
6393
|
|
|
5641
6394
|
// src/validators/claude/monitors.ts
|
|
5642
|
-
import { existsSync as
|
|
6395
|
+
import { existsSync as existsSync32 } from "fs";
|
|
5643
6396
|
import { resolve as resolve14 } from "path";
|
|
5644
6397
|
var claudeMonitorsValidator;
|
|
5645
6398
|
var init_monitors = __esm(() => {
|
|
@@ -5649,7 +6402,7 @@ var init_monitors = __esm(() => {
|
|
|
5649
6402
|
name: "Claude Monitors (experimental)",
|
|
5650
6403
|
description: "Validates monitors/monitors.json (or experimental.monitors): array of {name, command, description, when?}; commands support ${CLAUDE_PLUGIN_*} subs. Monitors run only in interactive CLI sessions.",
|
|
5651
6404
|
detect(dir) {
|
|
5652
|
-
return
|
|
6405
|
+
return existsSync32(resolve14(dir, "monitors", "monitors.json")) || existsSync32(resolve14(dir, "monitors.json")) || existsSync32(resolve14(dir, ".claude-plugin", "plugin.json"));
|
|
5653
6406
|
},
|
|
5654
6407
|
async validate(dir, _opts) {
|
|
5655
6408
|
const errors = [];
|
|
@@ -5661,7 +6414,7 @@ var init_monitors = __esm(() => {
|
|
|
5661
6414
|
resolve14(dir, "monitors.json")
|
|
5662
6415
|
];
|
|
5663
6416
|
for (const p of candidates) {
|
|
5664
|
-
if (
|
|
6417
|
+
if (existsSync32(p)) {
|
|
5665
6418
|
try {
|
|
5666
6419
|
const parsed = JSON.parse(await Bun.file(p).text());
|
|
5667
6420
|
if (Array.isArray(parsed)) {
|
|
@@ -5677,7 +6430,7 @@ var init_monitors = __esm(() => {
|
|
|
5677
6430
|
}
|
|
5678
6431
|
if (!arr) {
|
|
5679
6432
|
const mp = resolve14(dir, ".claude-plugin", "plugin.json");
|
|
5680
|
-
if (
|
|
6433
|
+
if (existsSync32(mp)) {
|
|
5681
6434
|
try {
|
|
5682
6435
|
const m = JSON.parse(await Bun.file(mp).text());
|
|
5683
6436
|
const exp = m?.experimental;
|
|
@@ -5730,8 +6483,8 @@ var init_monitors = __esm(() => {
|
|
|
5730
6483
|
});
|
|
5731
6484
|
|
|
5732
6485
|
// src/validators/codex/plugin.ts
|
|
5733
|
-
import { existsSync as
|
|
5734
|
-
import { resolve as resolve15, join as
|
|
6486
|
+
import { existsSync as existsSync33, readdirSync as readdirSync16 } from "fs";
|
|
6487
|
+
import { resolve as resolve15, join as join26 } from "path";
|
|
5735
6488
|
var NAME_REGEX3, codexPluginValidator;
|
|
5736
6489
|
var init_plugin2 = __esm(() => {
|
|
5737
6490
|
NAME_REGEX3 = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
@@ -5741,7 +6494,7 @@ var init_plugin2 = __esm(() => {
|
|
|
5741
6494
|
name: "Codex Plugin",
|
|
5742
6495
|
description: "Validates .codex-plugin/plugin.json manifest (requires interface block and skills as directory string per Codex packaging)",
|
|
5743
6496
|
detect(dir) {
|
|
5744
|
-
return
|
|
6497
|
+
return existsSync33(resolve15(dir, ".codex-plugin", "plugin.json"));
|
|
5745
6498
|
},
|
|
5746
6499
|
async validate(dir, _opts) {
|
|
5747
6500
|
const errors = [];
|
|
@@ -5822,11 +6575,11 @@ var init_plugin2 = __esm(() => {
|
|
|
5822
6575
|
warnings.push('Missing "keywords" (recommended \u2014 if users mention any of these, your plugin will get triggered in Codex)');
|
|
5823
6576
|
}
|
|
5824
6577
|
const skillsDir = resolve15(dir, "skills");
|
|
5825
|
-
if (
|
|
5826
|
-
const entries =
|
|
6578
|
+
if (existsSync33(skillsDir)) {
|
|
6579
|
+
const entries = readdirSync16(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
5827
6580
|
for (const e of entries) {
|
|
5828
|
-
const md =
|
|
5829
|
-
if (
|
|
6581
|
+
const md = join26(skillsDir, e.name, "SKILL.md");
|
|
6582
|
+
if (existsSync33(md)) {
|
|
5830
6583
|
passes.push(`skills/${e.name}/SKILL.md exists`);
|
|
5831
6584
|
} else {
|
|
5832
6585
|
errors.push(`skills/${e.name}/ is missing SKILL.md`);
|
|
@@ -5844,7 +6597,7 @@ var init_plugin2 = __esm(() => {
|
|
|
5844
6597
|
});
|
|
5845
6598
|
|
|
5846
6599
|
// src/validators/codex/marketplace.ts
|
|
5847
|
-
import { existsSync as
|
|
6600
|
+
import { existsSync as existsSync34 } from "fs";
|
|
5848
6601
|
import { resolve as resolve16 } from "path";
|
|
5849
6602
|
var codexMarketplaceValidator;
|
|
5850
6603
|
var init_marketplace2 = __esm(() => {
|
|
@@ -5854,9 +6607,9 @@ var init_marketplace2 = __esm(() => {
|
|
|
5854
6607
|
name: "Codex Plugin Marketplace",
|
|
5855
6608
|
description: "Validates .agents/plugins/marketplace.json (Codex convention: object source + policy blocks)",
|
|
5856
6609
|
detect(dir) {
|
|
5857
|
-
if (
|
|
6610
|
+
if (existsSync34(resolve16(dir, ".agents", "plugins", "marketplace.json")))
|
|
5858
6611
|
return true;
|
|
5859
|
-
if (
|
|
6612
|
+
if (existsSync34(resolve16(dir, ".agents", "plugins", "marketplace.json")))
|
|
5860
6613
|
return true;
|
|
5861
6614
|
return false;
|
|
5862
6615
|
},
|
|
@@ -5865,7 +6618,7 @@ var init_marketplace2 = __esm(() => {
|
|
|
5865
6618
|
const warnings = [];
|
|
5866
6619
|
const passes = [];
|
|
5867
6620
|
const marketplacePath = resolve16(dir, ".agents", "plugins", "marketplace.json");
|
|
5868
|
-
if (!
|
|
6621
|
+
if (!existsSync34(marketplacePath)) {
|
|
5869
6622
|
errors.push("Missing .agents/plugins/marketplace.json");
|
|
5870
6623
|
return { errors, warnings, passes };
|
|
5871
6624
|
}
|
|
@@ -5940,12 +6693,12 @@ var init_marketplace2 = __esm(() => {
|
|
|
5940
6693
|
passes.push(`plugins[${i}].category: "${p.category}"`);
|
|
5941
6694
|
}
|
|
5942
6695
|
}
|
|
5943
|
-
if (
|
|
6696
|
+
if (existsSync34(resolve16(dir, "README.md"))) {
|
|
5944
6697
|
passes.push("README.md exists at marketplace root");
|
|
5945
6698
|
} else {
|
|
5946
6699
|
warnings.push("No README.md at marketplace root \u2014 recommended");
|
|
5947
6700
|
}
|
|
5948
|
-
if (
|
|
6701
|
+
if (existsSync34(resolve16(dir, "LICENSE"))) {
|
|
5949
6702
|
passes.push("LICENSE exists at marketplace root");
|
|
5950
6703
|
} else {
|
|
5951
6704
|
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
@@ -5956,7 +6709,7 @@ var init_marketplace2 = __esm(() => {
|
|
|
5956
6709
|
});
|
|
5957
6710
|
|
|
5958
6711
|
// src/validators/codex/mcp.ts
|
|
5959
|
-
import { existsSync as
|
|
6712
|
+
import { existsSync as existsSync35 } from "fs";
|
|
5960
6713
|
import { resolve as resolve17 } from "path";
|
|
5961
6714
|
var codexMcpValidator;
|
|
5962
6715
|
var init_mcp2 = __esm(() => {
|
|
@@ -5966,7 +6719,7 @@ var init_mcp2 = __esm(() => {
|
|
|
5966
6719
|
name: "Codex MCP Config",
|
|
5967
6720
|
description: "Validates .mcp.json (or inline via plugin.json mcpServers): server entries (stdio: command+args, or url), env, cwd, substitutions per Codex MCP support",
|
|
5968
6721
|
detect(dir) {
|
|
5969
|
-
return
|
|
6722
|
+
return existsSync35(resolve17(dir, ".mcp.json"));
|
|
5970
6723
|
},
|
|
5971
6724
|
async validate(dir, _opts) {
|
|
5972
6725
|
const errors = [];
|
|
@@ -6026,7 +6779,7 @@ var init_mcp2 = __esm(() => {
|
|
|
6026
6779
|
});
|
|
6027
6780
|
|
|
6028
6781
|
// src/validators/codex/skill.ts
|
|
6029
|
-
import { existsSync as
|
|
6782
|
+
import { existsSync as existsSync36 } from "fs";
|
|
6030
6783
|
import { resolve as resolve18 } from "path";
|
|
6031
6784
|
var codexSkillValidator;
|
|
6032
6785
|
var init_skill2 = __esm(() => {
|
|
@@ -6037,7 +6790,7 @@ var init_skill2 = __esm(() => {
|
|
|
6037
6790
|
name: "Codex Skill",
|
|
6038
6791
|
description: "Validates SKILL.md (shared format): frontmatter (name/description), body, supporting files, substitutions. Codex uses the same SKILL.md spec as other providers.",
|
|
6039
6792
|
detect(dir) {
|
|
6040
|
-
return
|
|
6793
|
+
return existsSync36(resolve18(dir, "SKILL.md"));
|
|
6041
6794
|
},
|
|
6042
6795
|
async validate(dir, _opts) {
|
|
6043
6796
|
const loaded = await loadSkill(dir);
|
|
@@ -6055,8 +6808,8 @@ var init_skill2 = __esm(() => {
|
|
|
6055
6808
|
});
|
|
6056
6809
|
|
|
6057
6810
|
// src/validators/cursor/plugin.ts
|
|
6058
|
-
import { existsSync as
|
|
6059
|
-
import { resolve as resolve19, join as
|
|
6811
|
+
import { existsSync as existsSync37, readdirSync as readdirSync18 } from "fs";
|
|
6812
|
+
import { resolve as resolve19, join as join28 } from "path";
|
|
6060
6813
|
var NAME_REGEX4, cursorPluginValidator;
|
|
6061
6814
|
var init_plugin3 = __esm(() => {
|
|
6062
6815
|
NAME_REGEX4 = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
@@ -6066,7 +6819,7 @@ var init_plugin3 = __esm(() => {
|
|
|
6066
6819
|
name: "Cursor Plugin",
|
|
6067
6820
|
description: "Validates .cursor-plugin/plugin.json manifest (skills as directory string; mcpServers support)",
|
|
6068
6821
|
detect(dir) {
|
|
6069
|
-
return
|
|
6822
|
+
return existsSync37(resolve19(dir, ".cursor-plugin", "plugin.json"));
|
|
6070
6823
|
},
|
|
6071
6824
|
async validate(dir, _opts) {
|
|
6072
6825
|
const errors = [];
|
|
@@ -6153,11 +6906,11 @@ var init_plugin3 = __esm(() => {
|
|
|
6153
6906
|
warnings.push('Missing "keywords" (recommended \u2014 if users mention any of these, your plugin will get triggered in Cursor)');
|
|
6154
6907
|
}
|
|
6155
6908
|
const skillsDir = resolve19(dir, "skills");
|
|
6156
|
-
if (
|
|
6157
|
-
const entries =
|
|
6909
|
+
if (existsSync37(skillsDir)) {
|
|
6910
|
+
const entries = readdirSync18(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
6158
6911
|
for (const e of entries) {
|
|
6159
|
-
const md =
|
|
6160
|
-
if (
|
|
6912
|
+
const md = join28(skillsDir, e.name, "SKILL.md");
|
|
6913
|
+
if (existsSync37(md)) {
|
|
6161
6914
|
passes.push(`skills/${e.name}/SKILL.md exists`);
|
|
6162
6915
|
} else {
|
|
6163
6916
|
errors.push(`skills/${e.name}/ is missing SKILL.md`);
|
|
@@ -6168,7 +6921,7 @@ var init_plugin3 = __esm(() => {
|
|
|
6168
6921
|
const mcpRef = manifest.mcpServers;
|
|
6169
6922
|
if (mcpRef.startsWith("./") || mcpRef.startsWith("../")) {
|
|
6170
6923
|
const mcpPath = resolve19(dir, mcpRef);
|
|
6171
|
-
if (
|
|
6924
|
+
if (existsSync37(mcpPath)) {
|
|
6172
6925
|
passes.push(`mcpServers file exists at ${mcpRef}`);
|
|
6173
6926
|
} else {
|
|
6174
6927
|
warnings.push(`mcpServers path "${mcpRef}" does not exist on disk`);
|
|
@@ -6198,8 +6951,8 @@ var init_plugin3 = __esm(() => {
|
|
|
6198
6951
|
});
|
|
6199
6952
|
|
|
6200
6953
|
// src/validators/cursor/marketplace.ts
|
|
6201
|
-
import { existsSync as
|
|
6202
|
-
import { resolve as resolve20, join as
|
|
6954
|
+
import { existsSync as existsSync38, readdirSync as readdirSync19 } from "fs";
|
|
6955
|
+
import { resolve as resolve20, join as join29 } from "path";
|
|
6203
6956
|
var cursorMarketplaceValidator;
|
|
6204
6957
|
var init_marketplace3 = __esm(() => {
|
|
6205
6958
|
cursorMarketplaceValidator = {
|
|
@@ -6208,18 +6961,18 @@ var init_marketplace3 = __esm(() => {
|
|
|
6208
6961
|
name: "Cursor Plugin Marketplace",
|
|
6209
6962
|
description: "Validates .cursor-plugin/marketplace.json (string sources + metadata.pluginRoot)",
|
|
6210
6963
|
detect(dir) {
|
|
6211
|
-
if (
|
|
6964
|
+
if (existsSync38(resolve20(dir, ".cursor-plugin", "marketplace.json")))
|
|
6212
6965
|
return true;
|
|
6213
6966
|
const pluginsDir = resolve20(dir, "plugins");
|
|
6214
|
-
if (!
|
|
6967
|
+
if (!existsSync38(pluginsDir))
|
|
6215
6968
|
return false;
|
|
6216
6969
|
try {
|
|
6217
|
-
const entries =
|
|
6970
|
+
const entries = readdirSync19(pluginsDir, { withFileTypes: true });
|
|
6218
6971
|
for (const entry of entries) {
|
|
6219
6972
|
if (!entry.isDirectory())
|
|
6220
6973
|
continue;
|
|
6221
|
-
const hasSkills =
|
|
6222
|
-
const hasManifest =
|
|
6974
|
+
const hasSkills = existsSync38(join29(pluginsDir, entry.name, "skills"));
|
|
6975
|
+
const hasManifest = existsSync38(join29(pluginsDir, entry.name, ".cursor-plugin", "plugin.json"));
|
|
6223
6976
|
if (hasSkills || hasManifest)
|
|
6224
6977
|
return true;
|
|
6225
6978
|
}
|
|
@@ -6231,9 +6984,9 @@ var init_marketplace3 = __esm(() => {
|
|
|
6231
6984
|
const warnings = [];
|
|
6232
6985
|
const passes = [];
|
|
6233
6986
|
const cursorMktPath = resolve20(dir, ".cursor-plugin", "marketplace.json");
|
|
6234
|
-
const hasCursorMkt =
|
|
6987
|
+
const hasCursorMkt = existsSync38(cursorMktPath);
|
|
6235
6988
|
const pluginsDir = resolve20(dir, "plugins");
|
|
6236
|
-
const hasPluginsDirLayout =
|
|
6989
|
+
const hasPluginsDirLayout = existsSync38(pluginsDir);
|
|
6237
6990
|
if (!hasCursorMkt && !hasPluginsDirLayout) {
|
|
6238
6991
|
errors.push("Missing .cursor-plugin/marketplace.json or plugins/ directory");
|
|
6239
6992
|
return { errors, warnings, passes };
|
|
@@ -6287,9 +7040,9 @@ var init_marketplace3 = __esm(() => {
|
|
|
6287
7040
|
const src = String(p.source);
|
|
6288
7041
|
passes.push(`plugins[${i}].source: "${src}"`);
|
|
6289
7042
|
const srcDir = resolve20(dir, pluginRoot, src);
|
|
6290
|
-
if (
|
|
6291
|
-
const hasManifest =
|
|
6292
|
-
const hasSkills =
|
|
7043
|
+
if (existsSync38(srcDir)) {
|
|
7044
|
+
const hasManifest = existsSync38(resolve20(srcDir, ".cursor-plugin", "plugin.json"));
|
|
7045
|
+
const hasSkills = existsSync38(resolve20(srcDir, "skills"));
|
|
6293
7046
|
if (hasManifest || hasSkills) {
|
|
6294
7047
|
passes.push(`plugins[${i}]: source exists (${hasManifest ? "manifest" : "skills/"})`);
|
|
6295
7048
|
} else {
|
|
@@ -6300,7 +7053,7 @@ var init_marketplace3 = __esm(() => {
|
|
|
6300
7053
|
}
|
|
6301
7054
|
} else {
|
|
6302
7055
|
const implicitSrc = resolve20(dir, pluginRoot, p.name || "");
|
|
6303
|
-
if (p.name &&
|
|
7056
|
+
if (p.name && existsSync38(implicitSrc)) {
|
|
6304
7057
|
passes.push(`plugins[${i}]: implicit source via name under ${pluginRoot}`);
|
|
6305
7058
|
} else {
|
|
6306
7059
|
warnings.push(`plugins[${i}]: missing "source" (and no implicit dir)`);
|
|
@@ -6315,12 +7068,12 @@ var init_marketplace3 = __esm(() => {
|
|
|
6315
7068
|
passes.push(`plugins[${i}].homepage present`);
|
|
6316
7069
|
}
|
|
6317
7070
|
}
|
|
6318
|
-
if (
|
|
7071
|
+
if (existsSync38(resolve20(dir, "README.md"))) {
|
|
6319
7072
|
passes.push("README.md exists at marketplace root");
|
|
6320
7073
|
} else {
|
|
6321
7074
|
warnings.push("No README.md at marketplace root \u2014 recommended for discoverability");
|
|
6322
7075
|
}
|
|
6323
|
-
if (
|
|
7076
|
+
if (existsSync38(resolve20(dir, "LICENSE"))) {
|
|
6324
7077
|
passes.push("LICENSE exists at marketplace root");
|
|
6325
7078
|
} else {
|
|
6326
7079
|
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
@@ -6329,21 +7082,21 @@ var init_marketplace3 = __esm(() => {
|
|
|
6329
7082
|
}
|
|
6330
7083
|
if (hasPluginsDirLayout) {
|
|
6331
7084
|
passes.push("plugins/ directory exists");
|
|
6332
|
-
const pluginEntries =
|
|
7085
|
+
const pluginEntries = readdirSync19(pluginsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
6333
7086
|
if (pluginEntries.length === 0) {
|
|
6334
7087
|
errors.push("plugins/ directory is empty \u2014 expected at least one plugin");
|
|
6335
7088
|
return { errors, warnings, passes };
|
|
6336
7089
|
}
|
|
6337
7090
|
passes.push(`${pluginEntries.length} plugin(s) found`);
|
|
6338
|
-
if (
|
|
7091
|
+
if (existsSync38(resolve20(dir, "README.md"))) {
|
|
6339
7092
|
passes.push("README.md exists at marketplace root");
|
|
6340
7093
|
} else {
|
|
6341
7094
|
warnings.push("No README.md at marketplace root \u2014 recommended");
|
|
6342
7095
|
}
|
|
6343
7096
|
for (const plugin of pluginEntries) {
|
|
6344
|
-
const pluginPath =
|
|
6345
|
-
const hasSkills =
|
|
6346
|
-
const hasManifest =
|
|
7097
|
+
const pluginPath = join29(pluginsDir, plugin.name);
|
|
7098
|
+
const hasSkills = existsSync38(join29(pluginPath, "skills"));
|
|
7099
|
+
const hasManifest = existsSync38(join29(pluginPath, ".cursor-plugin", "plugin.json"));
|
|
6347
7100
|
if (hasManifest || hasSkills) {
|
|
6348
7101
|
passes.push(`Plugin "${plugin.name}" has ${hasManifest ? "manifest" : "skills/"}`);
|
|
6349
7102
|
}
|
|
@@ -6356,7 +7109,7 @@ var init_marketplace3 = __esm(() => {
|
|
|
6356
7109
|
});
|
|
6357
7110
|
|
|
6358
7111
|
// src/validators/cursor/mcp.ts
|
|
6359
|
-
import { existsSync as
|
|
7112
|
+
import { existsSync as existsSync39 } from "fs";
|
|
6360
7113
|
import { resolve as resolve21 } from "path";
|
|
6361
7114
|
var cursorMcpValidator;
|
|
6362
7115
|
var init_mcp3 = __esm(() => {
|
|
@@ -6366,7 +7119,7 @@ var init_mcp3 = __esm(() => {
|
|
|
6366
7119
|
name: "Cursor MCP Config",
|
|
6367
7120
|
description: "Validates mcp.json (Cursor uses no leading dot; supports mcpServers wrapper or direct server map)",
|
|
6368
7121
|
detect(dir) {
|
|
6369
|
-
return
|
|
7122
|
+
return existsSync39(resolve21(dir, "mcp.json"));
|
|
6370
7123
|
},
|
|
6371
7124
|
async validate(dir, _opts) {
|
|
6372
7125
|
const errors = [];
|
|
@@ -6432,7 +7185,7 @@ var init_mcp3 = __esm(() => {
|
|
|
6432
7185
|
});
|
|
6433
7186
|
|
|
6434
7187
|
// src/validators/cursor/skill.ts
|
|
6435
|
-
import { existsSync as
|
|
7188
|
+
import { existsSync as existsSync40 } from "fs";
|
|
6436
7189
|
import { resolve as resolve22 } from "path";
|
|
6437
7190
|
var cursorSkillValidator;
|
|
6438
7191
|
var init_skill3 = __esm(() => {
|
|
@@ -6443,7 +7196,7 @@ var init_skill3 = __esm(() => {
|
|
|
6443
7196
|
name: "Cursor Skill",
|
|
6444
7197
|
description: "Validates SKILL.md (shared format): frontmatter (name/description), body, supporting files, substitutions. Cursor uses the same SKILL.md spec as other providers.",
|
|
6445
7198
|
detect(dir) {
|
|
6446
|
-
return
|
|
7199
|
+
return existsSync40(resolve22(dir, "SKILL.md"));
|
|
6447
7200
|
},
|
|
6448
7201
|
async validate(dir, _opts) {
|
|
6449
7202
|
const loaded = await loadSkill(dir);
|
|
@@ -6461,7 +7214,7 @@ var init_skill3 = __esm(() => {
|
|
|
6461
7214
|
});
|
|
6462
7215
|
|
|
6463
7216
|
// src/validators/copilot/plugin.ts
|
|
6464
|
-
import { existsSync as
|
|
7217
|
+
import { existsSync as existsSync41 } from "fs";
|
|
6465
7218
|
import { resolve as resolve23 } from "path";
|
|
6466
7219
|
var NAME_REGEX5, copilotPluginValidator;
|
|
6467
7220
|
var init_plugin4 = __esm(() => {
|
|
@@ -6472,7 +7225,7 @@ var init_plugin4 = __esm(() => {
|
|
|
6472
7225
|
name: "Copilot Plugin",
|
|
6473
7226
|
description: "Validates .github/plugin/plugin.json (skills as array of paths, mcpServers support)",
|
|
6474
7227
|
detect(dir) {
|
|
6475
|
-
return
|
|
7228
|
+
return existsSync41(resolve23(dir, ".github", "plugin", "plugin.json"));
|
|
6476
7229
|
},
|
|
6477
7230
|
async validate(dir, _opts) {
|
|
6478
7231
|
const errors = [];
|
|
@@ -6515,9 +7268,9 @@ var init_plugin4 = __esm(() => {
|
|
|
6515
7268
|
}
|
|
6516
7269
|
const skillDir = resolve23(dir, p);
|
|
6517
7270
|
const skillMd = resolve23(skillDir, "SKILL.md");
|
|
6518
|
-
if (
|
|
7271
|
+
if (existsSync41(skillMd)) {
|
|
6519
7272
|
passes.push(`skills[${i}]: ${p}/SKILL.md exists`);
|
|
6520
|
-
} else if (
|
|
7273
|
+
} else if (existsSync41(skillDir)) {
|
|
6521
7274
|
warnings.push(`skills[${i}]: directory exists but no SKILL.md inside`);
|
|
6522
7275
|
} else {
|
|
6523
7276
|
warnings.push(`skills[${i}]: path "${p}" does not exist`);
|
|
@@ -6529,7 +7282,7 @@ var init_plugin4 = __esm(() => {
|
|
|
6529
7282
|
passes.push(`mcpServers: "${manifest.mcpServers}"`);
|
|
6530
7283
|
const mcpRef = String(manifest.mcpServers);
|
|
6531
7284
|
const mcpPath = resolve23(dir, mcpRef);
|
|
6532
|
-
if (
|
|
7285
|
+
if (existsSync41(mcpPath)) {
|
|
6533
7286
|
passes.push(`mcpServers file exists at ${mcpRef}`);
|
|
6534
7287
|
} else {
|
|
6535
7288
|
warnings.push(`mcpServers path "${mcpRef}" does not exist on disk`);
|
|
@@ -6589,7 +7342,7 @@ var init_plugin4 = __esm(() => {
|
|
|
6589
7342
|
});
|
|
6590
7343
|
|
|
6591
7344
|
// src/validators/copilot/marketplace.ts
|
|
6592
|
-
import { existsSync as
|
|
7345
|
+
import { existsSync as existsSync42 } from "fs";
|
|
6593
7346
|
import { resolve as resolve24 } from "path";
|
|
6594
7347
|
var copilotMarketplaceValidator;
|
|
6595
7348
|
var init_marketplace4 = __esm(() => {
|
|
@@ -6599,7 +7352,7 @@ var init_marketplace4 = __esm(() => {
|
|
|
6599
7352
|
name: "Copilot Plugin Marketplace",
|
|
6600
7353
|
description: "Validates .github/plugin/marketplace.json (string sources)",
|
|
6601
7354
|
detect(dir) {
|
|
6602
|
-
return
|
|
7355
|
+
return existsSync42(resolve24(dir, ".github", "plugin", "marketplace.json"));
|
|
6603
7356
|
},
|
|
6604
7357
|
async validate(dir, _opts) {
|
|
6605
7358
|
const errors = [];
|
|
@@ -6649,9 +7402,9 @@ var init_marketplace4 = __esm(() => {
|
|
|
6649
7402
|
const src = String(p.source);
|
|
6650
7403
|
passes.push(`plugins[${i}].source: "${src}"`);
|
|
6651
7404
|
const srcDir = resolve24(dir, src);
|
|
6652
|
-
if (
|
|
6653
|
-
const hasManifest =
|
|
6654
|
-
const hasSkills =
|
|
7405
|
+
if (existsSync42(srcDir)) {
|
|
7406
|
+
const hasManifest = existsSync42(resolve24(srcDir, ".github", "plugin", "plugin.json"));
|
|
7407
|
+
const hasSkills = existsSync42(resolve24(srcDir, "skills"));
|
|
6655
7408
|
if (hasManifest || hasSkills) {
|
|
6656
7409
|
passes.push(`plugins[${i}]: source exists (${hasManifest ? "manifest" : "skills/"})`);
|
|
6657
7410
|
} else {
|
|
@@ -6668,12 +7421,12 @@ var init_marketplace4 = __esm(() => {
|
|
|
6668
7421
|
if (p.version)
|
|
6669
7422
|
passes.push(`plugins[${i}].version: "${p.version}"`);
|
|
6670
7423
|
}
|
|
6671
|
-
if (
|
|
7424
|
+
if (existsSync42(resolve24(dir, "README.md"))) {
|
|
6672
7425
|
passes.push("README.md exists at marketplace root");
|
|
6673
7426
|
} else {
|
|
6674
7427
|
warnings.push("No README.md at marketplace root \u2014 recommended");
|
|
6675
7428
|
}
|
|
6676
|
-
if (
|
|
7429
|
+
if (existsSync42(resolve24(dir, "LICENSE"))) {
|
|
6677
7430
|
passes.push("LICENSE exists at marketplace root");
|
|
6678
7431
|
} else {
|
|
6679
7432
|
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
@@ -6684,7 +7437,7 @@ var init_marketplace4 = __esm(() => {
|
|
|
6684
7437
|
});
|
|
6685
7438
|
|
|
6686
7439
|
// src/validators/copilot/mcp.ts
|
|
6687
|
-
import { existsSync as
|
|
7440
|
+
import { existsSync as existsSync43 } from "fs";
|
|
6688
7441
|
import { resolve as resolve25 } from "path";
|
|
6689
7442
|
var copilotMcpValidator;
|
|
6690
7443
|
var init_mcp4 = __esm(() => {
|
|
@@ -6694,7 +7447,7 @@ var init_mcp4 = __esm(() => {
|
|
|
6694
7447
|
name: "Copilot MCP Config",
|
|
6695
7448
|
description: "Validates .mcp.json (referenced via mcpServers in manifest). Supports stdio and http servers.",
|
|
6696
7449
|
detect(dir) {
|
|
6697
|
-
return
|
|
7450
|
+
return existsSync43(resolve25(dir, ".mcp.json"));
|
|
6698
7451
|
},
|
|
6699
7452
|
async validate(dir, _opts) {
|
|
6700
7453
|
const errors = [];
|
|
@@ -6760,7 +7513,7 @@ var init_mcp4 = __esm(() => {
|
|
|
6760
7513
|
});
|
|
6761
7514
|
|
|
6762
7515
|
// src/validators/copilot/skill.ts
|
|
6763
|
-
import { existsSync as
|
|
7516
|
+
import { existsSync as existsSync44 } from "fs";
|
|
6764
7517
|
import { resolve as resolve26 } from "path";
|
|
6765
7518
|
var copilotSkillValidator;
|
|
6766
7519
|
var init_skill4 = __esm(() => {
|
|
@@ -6771,7 +7524,7 @@ var init_skill4 = __esm(() => {
|
|
|
6771
7524
|
name: "Copilot Skill",
|
|
6772
7525
|
description: "Validates SKILL.md (shared format). Copilot supports skills referenced via array paths in the manifest.",
|
|
6773
7526
|
detect(dir) {
|
|
6774
|
-
return
|
|
7527
|
+
return existsSync44(resolve26(dir, "SKILL.md"));
|
|
6775
7528
|
},
|
|
6776
7529
|
async validate(dir, _opts) {
|
|
6777
7530
|
const loaded = await loadSkill(dir);
|
|
@@ -6939,7 +7692,7 @@ var init_validators = __esm(() => {
|
|
|
6939
7692
|
// src/core/remote.ts
|
|
6940
7693
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
6941
7694
|
import { mkdtempSync, rmSync } from "fs";
|
|
6942
|
-
import { join as
|
|
7695
|
+
import { join as join31 } from "path";
|
|
6943
7696
|
import { tmpdir } from "os";
|
|
6944
7697
|
function parseRemoteUrl(input) {
|
|
6945
7698
|
if (input.startsWith(".") || input.startsWith("/") || input.startsWith("~")) {
|
|
@@ -6976,7 +7729,7 @@ function isGhAvailable() {
|
|
|
6976
7729
|
return ghAvailable;
|
|
6977
7730
|
}
|
|
6978
7731
|
async function cloneToTemp(parsed) {
|
|
6979
|
-
const tmpDir = mkdtempSync(
|
|
7732
|
+
const tmpDir = mkdtempSync(join31(tmpdir(), "dora-"));
|
|
6980
7733
|
const cleanup = () => {
|
|
6981
7734
|
try {
|
|
6982
7735
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -7024,15 +7777,15 @@ var exports_validate_top = {};
|
|
|
7024
7777
|
__export(exports_validate_top, {
|
|
7025
7778
|
default: () => validate_top_default
|
|
7026
7779
|
});
|
|
7027
|
-
import { existsSync as
|
|
7780
|
+
import { existsSync as existsSync46 } from "fs";
|
|
7028
7781
|
import { resolve as resolve27 } from "path";
|
|
7029
|
-
var
|
|
7782
|
+
var import_picocolors21, validate_top_default;
|
|
7030
7783
|
var init_validate_top = __esm(() => {
|
|
7031
7784
|
init_dist();
|
|
7032
7785
|
init_out();
|
|
7033
7786
|
init_validators();
|
|
7034
7787
|
init_remote();
|
|
7035
|
-
|
|
7788
|
+
import_picocolors21 = __toESM(require_picocolors(), 1);
|
|
7036
7789
|
validate_top_default = defineCommand({
|
|
7037
7790
|
meta: {
|
|
7038
7791
|
name: "validate",
|
|
@@ -7072,7 +7825,7 @@ var init_validate_top = __esm(() => {
|
|
|
7072
7825
|
let cleanup;
|
|
7073
7826
|
if (remote) {
|
|
7074
7827
|
ui.info(`
|
|
7075
|
-
Cloning ${
|
|
7828
|
+
Cloning ${import_picocolors21.default.dim(args.path)}...`);
|
|
7076
7829
|
try {
|
|
7077
7830
|
const result = await cloneToTemp(remote);
|
|
7078
7831
|
fullPath = remote.subpath ? resolve27(result.dir, remote.subpath) : result.dir;
|
|
@@ -7082,14 +7835,14 @@ var init_validate_top = __esm(() => {
|
|
|
7082
7835
|
ui.fail(msg);
|
|
7083
7836
|
process.exit(1);
|
|
7084
7837
|
}
|
|
7085
|
-
if (!
|
|
7838
|
+
if (!existsSync46(fullPath)) {
|
|
7086
7839
|
cleanup();
|
|
7087
7840
|
ui.fail(`Subdirectory not found in repo: ${remote.subpath}`);
|
|
7088
7841
|
process.exit(1);
|
|
7089
7842
|
}
|
|
7090
7843
|
} else {
|
|
7091
7844
|
fullPath = resolve27(args.path);
|
|
7092
|
-
if (!
|
|
7845
|
+
if (!existsSync46(fullPath)) {
|
|
7093
7846
|
ui.fail(`Path not found: ${args.path}
|
|
7094
7847
|
|
|
7095
7848
|
Check that the path is correct and the directory exists.`);
|
|
@@ -7120,13 +7873,13 @@ Check that the path is correct and the directory exists.`);
|
|
|
7120
7873
|
` + `Available providers:
|
|
7121
7874
|
` + providers.map((p) => {
|
|
7122
7875
|
const pvs = validators.filter((v) => v.provider === p);
|
|
7123
|
-
return ` ${
|
|
7124
|
-
` + pvs.map((v) => ` \u2022 ${
|
|
7876
|
+
return ` ${import_picocolors21.default.bold(p)}
|
|
7877
|
+
` + pvs.map((v) => ` \u2022 ${import_picocolors21.default.dim(v.id)} \u2014 ${v.description}`).join(`
|
|
7125
7878
|
`);
|
|
7126
7879
|
}).join(`
|
|
7127
7880
|
`) + `
|
|
7128
7881
|
|
|
7129
|
-
Use ${
|
|
7882
|
+
Use ${import_picocolors21.default.dim("--for <provider>")} or ${import_picocolors21.default.dim("--for <provider:type>")} to target explicitly.`);
|
|
7130
7883
|
process.exit(1);
|
|
7131
7884
|
}
|
|
7132
7885
|
const allResults = [];
|
|
@@ -7147,7 +7900,7 @@ Use ${import_picocolors17.default.dim("--for <provider>")} or ${import_picocolor
|
|
|
7147
7900
|
} else {
|
|
7148
7901
|
for (const { id, name, result } of allResults) {
|
|
7149
7902
|
ui.write(`
|
|
7150
|
-
${
|
|
7903
|
+
${import_picocolors21.default.bold("dora validate")} \u2014 ${import_picocolors21.default.white(name)} ${import_picocolors21.default.dim(`(${id})`)}
|
|
7151
7904
|
`);
|
|
7152
7905
|
ui.info(` Path: ${args.path}
|
|
7153
7906
|
`);
|
|
@@ -7162,7 +7915,7 @@ Use ${import_picocolors17.default.dim("--for <provider>")} or ${import_picocolor
|
|
|
7162
7915
|
}
|
|
7163
7916
|
if (result.errors.length === 0 && result.warnings.length === 0) {
|
|
7164
7917
|
ui.write(`
|
|
7165
|
-
${
|
|
7918
|
+
${import_picocolors21.default.green("\u2713")} ${import_picocolors21.default.white("All checks passed.")}
|
|
7166
7919
|
`);
|
|
7167
7920
|
} else {
|
|
7168
7921
|
ui.info(`
|
|
@@ -7184,16 +7937,16 @@ var exports_init2 = {};
|
|
|
7184
7937
|
__export(exports_init2, {
|
|
7185
7938
|
default: () => init_default2
|
|
7186
7939
|
});
|
|
7187
|
-
import { basename as
|
|
7940
|
+
import { basename as basename7, join as join32 } from "path";
|
|
7188
7941
|
var {spawnSync: spawnSync5 } = globalThis.Bun;
|
|
7189
|
-
var
|
|
7942
|
+
var import_picocolors22, init_default2;
|
|
7190
7943
|
var init_init2 = __esm(() => {
|
|
7191
7944
|
init_dist();
|
|
7192
7945
|
init_out();
|
|
7193
7946
|
init_journal_config();
|
|
7194
7947
|
init_journal_remote();
|
|
7195
7948
|
init_prompt();
|
|
7196
|
-
|
|
7949
|
+
import_picocolors22 = __toESM(require_picocolors(), 1);
|
|
7197
7950
|
init_default2 = defineCommand({
|
|
7198
7951
|
meta: {
|
|
7199
7952
|
name: "init",
|
|
@@ -7220,17 +7973,17 @@ var init_init2 = __esm(() => {
|
|
|
7220
7973
|
ui.heading("dora init \u2014 Set up doraval, your journal, and the coding agent dora should use on the fly");
|
|
7221
7974
|
const ghCheck = ensureGhCli();
|
|
7222
7975
|
if (!ghCheck.ok) {
|
|
7223
|
-
ui.write(` ${
|
|
7976
|
+
ui.write(` ${import_picocolors22.default.red("\u2717")} ${import_picocolors22.default.white("The GitHub CLI (")}${import_picocolors22.default.bold("gh")}${import_picocolors22.default.white(") is not installed.")}
|
|
7224
7977
|
`);
|
|
7225
|
-
ui.info(` doraval uses ${
|
|
7978
|
+
ui.info(` doraval uses ${import_picocolors22.default.bold("gh")} to fetch and sync journal files with GitHub.
|
|
7226
7979
|
`);
|
|
7227
7980
|
ui.info(` Install it:
|
|
7228
7981
|
`);
|
|
7229
|
-
ui.info(` macOS: ${
|
|
7230
|
-
ui.info(` Linux: ${
|
|
7231
|
-
ui.info(` Windows: ${
|
|
7982
|
+
ui.info(` macOS: ${import_picocolors22.default.dim("brew install gh")}`);
|
|
7983
|
+
ui.info(` Linux: ${import_picocolors22.default.dim("https://github.com/cli/cli/blob/trunk/docs/install_linux.md")}`);
|
|
7984
|
+
ui.info(` Windows: ${import_picocolors22.default.dim("winget install --id GitHub.cli")}
|
|
7232
7985
|
`);
|
|
7233
|
-
ui.info(` Then authenticate: ${
|
|
7986
|
+
ui.info(` Then authenticate: ${import_picocolors22.default.dim("gh auth login")}
|
|
7234
7987
|
`);
|
|
7235
7988
|
process.exit(1);
|
|
7236
7989
|
}
|
|
@@ -7243,44 +7996,44 @@ var init_init2 = __esm(() => {
|
|
|
7243
7996
|
if (gitOwner) {
|
|
7244
7997
|
defaultRepo = `${gitOwner}/${gitOwner}.md`;
|
|
7245
7998
|
if (ghLogin && ghLogin !== gitOwner) {
|
|
7246
|
-
sourceNote = ` ${
|
|
7999
|
+
sourceNote = ` ${import_picocolors22.default.dim("(from git remote; your active gh account is " + ghLogin + ")")}
|
|
7247
8000
|
`;
|
|
7248
8001
|
} else {
|
|
7249
|
-
sourceNote = ` ${
|
|
8002
|
+
sourceNote = ` ${import_picocolors22.default.dim("(from git remote)")}
|
|
7250
8003
|
`;
|
|
7251
8004
|
}
|
|
7252
8005
|
} else if (ghLogin) {
|
|
7253
8006
|
defaultRepo = `${ghLogin}/${ghLogin}.md`;
|
|
7254
|
-
sourceNote = ` ${
|
|
8007
|
+
sourceNote = ` ${import_picocolors22.default.dim("(from your active gh account)")}
|
|
7255
8008
|
`;
|
|
7256
8009
|
} else {
|
|
7257
|
-
ui.warn(`Not logged in to GitHub. Run ${
|
|
8010
|
+
ui.warn(`Not logged in to GitHub. Run ${import_picocolors22.default.dim("gh auth login")} first.
|
|
7258
8011
|
`);
|
|
7259
8012
|
process.exit(1);
|
|
7260
8013
|
}
|
|
7261
8014
|
const existingConfig = await readConfig();
|
|
7262
8015
|
if (existingConfig?.journal.repo) {
|
|
7263
8016
|
defaultRepo = existingConfig.journal.repo;
|
|
7264
|
-
sourceNote = ` ${
|
|
8017
|
+
sourceNote = ` ${import_picocolors22.default.dim("(from your previous journal setup)")}
|
|
7265
8018
|
`;
|
|
7266
8019
|
}
|
|
7267
|
-
ui.info(` Journal repo ${
|
|
8020
|
+
ui.info(` Journal repo ${import_picocolors22.default.dim("(owner/name)")}`);
|
|
7268
8021
|
if (sourceNote)
|
|
7269
8022
|
ui.write(sourceNote);
|
|
7270
8023
|
repo = prompt(" >", defaultRepo);
|
|
7271
8024
|
}
|
|
7272
8025
|
let project = args.project || process.env.DORAVAL_PROJECT;
|
|
7273
8026
|
if (!project) {
|
|
7274
|
-
const defaultProject =
|
|
8027
|
+
const defaultProject = basename7(process.cwd());
|
|
7275
8028
|
project = prompt(" Project name", defaultProject);
|
|
7276
8029
|
}
|
|
7277
8030
|
project = sanitizeProjectName(project);
|
|
7278
8031
|
if (!repoExists(repo)) {
|
|
7279
|
-
ui.write(` ${
|
|
8032
|
+
ui.write(` ${import_picocolors22.default.red("\u2717")} ${import_picocolors22.default.white("Repository")} ${import_picocolors22.default.bold(repo)} ${import_picocolors22.default.white("not found on GitHub.")}
|
|
7280
8033
|
`);
|
|
7281
8034
|
ui.info(` Create it first:
|
|
7282
8035
|
`);
|
|
7283
|
-
ui.info(` ${
|
|
8036
|
+
ui.info(` ${import_picocolors22.default.dim(`gh repo create ${repo} --private --description "Personal journal for agent decisions"`)}
|
|
7284
8037
|
`);
|
|
7285
8038
|
process.exit(1);
|
|
7286
8039
|
}
|
|
@@ -7288,16 +8041,16 @@ var init_init2 = __esm(() => {
|
|
|
7288
8041
|
const alreadyRegistered = existing?.journal.projects[project];
|
|
7289
8042
|
const isRefresh = alreadyRegistered && args.refresh;
|
|
7290
8043
|
if (alreadyRegistered && !isRefresh) {
|
|
7291
|
-
ui.write(` ${
|
|
8044
|
+
ui.write(` ${import_picocolors22.default.yellow("\u26A0")} ${import_picocolors22.default.white("Project")} ${import_picocolors22.default.bold(project)} ${import_picocolors22.default.white("is already registered.")}
|
|
7292
8045
|
`);
|
|
7293
8046
|
ui.info(` Repo: ${existing.journal.repo}
|
|
7294
8047
|
`);
|
|
7295
|
-
ui.info(` To refresh journal files, use ${
|
|
8048
|
+
ui.info(` To refresh journal files, use ${import_picocolors22.default.dim("dora journal update")} (or ${import_picocolors22.default.dim("dora init --refresh")}).
|
|
7296
8049
|
`);
|
|
7297
8050
|
}
|
|
7298
8051
|
const journalsDir = getJournalsDir();
|
|
7299
8052
|
const remotePath = `projects/${project}.md`;
|
|
7300
|
-
const localPath =
|
|
8053
|
+
const localPath = join32(journalsDir, `${project}.md`);
|
|
7301
8054
|
const effectiveRepo = isRefresh && !args.repo ? existing.journal.repo : repo;
|
|
7302
8055
|
const config = existing ?? {
|
|
7303
8056
|
journal: { repo: effectiveRepo, projects: {} }
|
|
@@ -7308,9 +8061,9 @@ var init_init2 = __esm(() => {
|
|
|
7308
8061
|
local_path: localPath
|
|
7309
8062
|
};
|
|
7310
8063
|
ensureDoravalDirs();
|
|
7311
|
-
ui.write(` ${
|
|
8064
|
+
ui.write(` ${import_picocolors22.default.dim(import_picocolors22.default.gray("Fetching journal files from"))} ${import_picocolors22.default.gray(effectiveRepo)}${import_picocolors22.default.dim(import_picocolors22.default.gray("..."))}
|
|
7312
8065
|
`);
|
|
7313
|
-
const globalDest =
|
|
8066
|
+
const globalDest = join32(journalsDir, "global.md");
|
|
7314
8067
|
const refreshGlobalRes = await refreshLocalJournalFile(effectiveRepo, "global.md", globalDest);
|
|
7315
8068
|
let wroteGlobal;
|
|
7316
8069
|
if (!refreshGlobalRes.ok) {
|
|
@@ -7327,7 +8080,7 @@ var init_init2 = __esm(() => {
|
|
|
7327
8080
|
if (wroteGlobal) {
|
|
7328
8081
|
ui.success("global.md");
|
|
7329
8082
|
} else {
|
|
7330
|
-
ui.write(` ${
|
|
8083
|
+
ui.write(` ${import_picocolors22.default.dim("\xB7")} global.md ${import_picocolors22.default.dim("(not found \u2014 will be created on first sync)")}`);
|
|
7331
8084
|
await Bun.write(globalDest, `# Global Journal
|
|
7332
8085
|
|
|
7333
8086
|
Cross-project principles.
|
|
@@ -7349,7 +8102,7 @@ Cross-project principles.
|
|
|
7349
8102
|
if (wroteProject) {
|
|
7350
8103
|
ui.success(remotePath);
|
|
7351
8104
|
} else {
|
|
7352
|
-
ui.write(` ${
|
|
8105
|
+
ui.write(` ${import_picocolors22.default.dim("\xB7")} ${remotePath} ${import_picocolors22.default.dim("(not found \u2014 will be created on first sync)")}`);
|
|
7353
8106
|
await Bun.write(localPath, `# ${project} Journal
|
|
7354
8107
|
|
|
7355
8108
|
Project-specific decisions.
|
|
@@ -7357,13 +8110,13 @@ Project-specific decisions.
|
|
|
7357
8110
|
}
|
|
7358
8111
|
await writeConfig(config);
|
|
7359
8112
|
ui.write(`
|
|
7360
|
-
${
|
|
8113
|
+
${import_picocolors22.default.green("\u2713")} ${import_picocolors22.default.white("Journal ready for project")} ${import_picocolors22.default.bold(import_picocolors22.default.white(project))}.
|
|
7361
8114
|
`);
|
|
7362
8115
|
const existingAgent = (await readConfig())?.agent;
|
|
7363
8116
|
if (existingAgent?.command) {
|
|
7364
|
-
ui.write(` ${
|
|
8117
|
+
ui.write(` ${import_picocolors22.default.bold(import_picocolors22.default.white("Coding agent (already configured)"))}
|
|
7365
8118
|
`);
|
|
7366
|
-
ui.write(` Current: ${
|
|
8119
|
+
ui.write(` Current: ${import_picocolors22.default.dim(import_picocolors22.default.gray(existingAgent.command))} template: ${import_picocolors22.default.dim(import_picocolors22.default.gray(existingAgent.prompt_template || "(default)"))}
|
|
7367
8120
|
`);
|
|
7368
8121
|
const change = prompt(" Reconfigure / change the coding agent for on-the-fly enrichment? (y/N)", "n");
|
|
7369
8122
|
if (!/^y/i.test(String(change))) {
|
|
@@ -7373,16 +8126,16 @@ Project-specific decisions.
|
|
|
7373
8126
|
if (existingAgent)
|
|
7374
8127
|
cfg.agent = existingAgent;
|
|
7375
8128
|
await writeConfig(cfg);
|
|
7376
|
-
ui.write(` ${
|
|
8129
|
+
ui.write(` ${import_picocolors22.default.green("\u2713")} ${import_picocolors22.default.white("Try:")} ${import_picocolors22.default.dim(import_picocolors22.default.gray('dora journal add "short decision"'))}
|
|
7377
8130
|
`);
|
|
7378
8131
|
process.exit(0);
|
|
7379
8132
|
return;
|
|
7380
8133
|
}
|
|
7381
8134
|
ui.blank();
|
|
7382
8135
|
} else {
|
|
7383
|
-
ui.write(` ${
|
|
8136
|
+
ui.write(` ${import_picocolors22.default.bold(import_picocolors22.default.white("Coding agent for journal add"))}
|
|
7384
8137
|
`);
|
|
7385
|
-
ui.info(` When configured, ${
|
|
8138
|
+
ui.info(` When configured, ${import_picocolors22.default.dim(import_picocolors22.default.gray('dora journal add ".."'))} will use your agent to enrich entries with tags and rationale automatically.
|
|
7386
8139
|
`);
|
|
7387
8140
|
}
|
|
7388
8141
|
const common = [
|
|
@@ -7401,7 +8154,7 @@ Project-specific decisions.
|
|
|
7401
8154
|
}
|
|
7402
8155
|
}
|
|
7403
8156
|
let agentCmd = detected || "claude";
|
|
7404
|
-
ui.write(` Detected / default agent command: ${
|
|
8157
|
+
ui.write(` Detected / default agent command: ${import_picocolors22.default.dim(import_picocolors22.default.gray(agentCmd))}`);
|
|
7405
8158
|
agentCmd = prompt(" Agent command (the binary you run for prompts)", agentCmd);
|
|
7406
8159
|
let template = detected ? common.find((c) => c.name === detected)?.template || '-p "{{prompt}}" --output-format json' : '-p "{{prompt}}" --output-format json';
|
|
7407
8160
|
ui.info(` Prompt template (use {{prompt}} placeholder):`);
|
|
@@ -7413,11 +8166,36 @@ Project-specific decisions.
|
|
|
7413
8166
|
};
|
|
7414
8167
|
await writeConfig(finalConfig);
|
|
7415
8168
|
ui.write(`
|
|
7416
|
-
${
|
|
8169
|
+
${import_picocolors22.default.green("\u2713")} ${import_picocolors22.default.white("Agent configured.")}
|
|
7417
8170
|
`);
|
|
7418
|
-
ui.info(` Re-run ${
|
|
8171
|
+
ui.info(` Re-run ${import_picocolors22.default.dim(import_picocolors22.default.gray("dora init"))} anytime to change it.
|
|
8172
|
+
`);
|
|
8173
|
+
ui.write(`
|
|
8174
|
+
${import_picocolors22.default.bold("Step 3: Eval configuration (doraval eval)")}
|
|
7419
8175
|
`);
|
|
7420
|
-
|
|
8176
|
+
const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
|
|
8177
|
+
const hasOpenAI = !!process.env.OPENAI_API_KEY;
|
|
8178
|
+
if (hasAnthropic || hasOpenAI) {
|
|
8179
|
+
ui.success("API key found in environment \u2014 will be used for eval.");
|
|
8180
|
+
} else {
|
|
8181
|
+
ui.warn("No ANTHROPIC_API_KEY or OPENAI_API_KEY set. Set the right one before running doraval eval.");
|
|
8182
|
+
}
|
|
8183
|
+
const evalModelAnswer = await prompt(` Which model should doraval eval use? ${import_picocolors22.default.dim("(e.g. claude-sonnet-4-6 or gpt-4o, press Enter to skip)")} `, "");
|
|
8184
|
+
if (evalModelAnswer.trim()) {
|
|
8185
|
+
const updatedConfig2 = await readConfig();
|
|
8186
|
+
if (updatedConfig2) {
|
|
8187
|
+
updatedConfig2.eval = {
|
|
8188
|
+
model: evalModelAnswer.trim(),
|
|
8189
|
+
max_tool_calls: 200,
|
|
8190
|
+
save_history: true
|
|
8191
|
+
};
|
|
8192
|
+
await writeConfig(updatedConfig2);
|
|
8193
|
+
ui.success(`eval.model set to ${evalModelAnswer.trim()}`);
|
|
8194
|
+
}
|
|
8195
|
+
} else {
|
|
8196
|
+
ui.dim(" Skipped. Run: dora config set eval.model <model-name>");
|
|
8197
|
+
}
|
|
8198
|
+
ui.info(` Next: ${import_picocolors22.default.dim(import_picocolors22.default.gray('dora journal add ".."'))}, ${import_picocolors22.default.dim(import_picocolors22.default.gray("dora journal list"))}, or ${import_picocolors22.default.dim(import_picocolors22.default.gray("dora journal update"))}.
|
|
7421
8199
|
`);
|
|
7422
8200
|
process.exit(0);
|
|
7423
8201
|
}
|
|
@@ -7426,7 +8204,7 @@ Project-specific decisions.
|
|
|
7426
8204
|
|
|
7427
8205
|
// src/core/update.ts
|
|
7428
8206
|
import { resolve as resolve28 } from "path";
|
|
7429
|
-
import { homedir as
|
|
8207
|
+
import { homedir as homedir4 } from "os";
|
|
7430
8208
|
function normalizePath(p) {
|
|
7431
8209
|
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
7432
8210
|
}
|
|
@@ -7611,7 +8389,7 @@ async function writeMarker(marker) {
|
|
|
7611
8389
|
}
|
|
7612
8390
|
var MARKER_PATH;
|
|
7613
8391
|
var init_update2 = __esm(() => {
|
|
7614
|
-
MARKER_PATH = resolve28(
|
|
8392
|
+
MARKER_PATH = resolve28(homedir4(), ".doraval", "install.json");
|
|
7615
8393
|
});
|
|
7616
8394
|
|
|
7617
8395
|
// src/cli/commands/update.ts
|
|
@@ -7620,7 +8398,7 @@ __export(exports_update2, {
|
|
|
7620
8398
|
default: () => update_default2
|
|
7621
8399
|
});
|
|
7622
8400
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
7623
|
-
import { homedir as
|
|
8401
|
+
import { homedir as homedir5 } from "os";
|
|
7624
8402
|
import { fileURLToPath } from "url";
|
|
7625
8403
|
import { realpath, access } from "fs/promises";
|
|
7626
8404
|
async function confirmUpdate() {
|
|
@@ -7666,7 +8444,7 @@ var init_update3 = __esm(() => {
|
|
|
7666
8444
|
entrypoint,
|
|
7667
8445
|
argv: process.argv,
|
|
7668
8446
|
env: process.env,
|
|
7669
|
-
homeDir:
|
|
8447
|
+
homeDir: homedir5(),
|
|
7670
8448
|
realpath: (p) => realpath(p),
|
|
7671
8449
|
exists: async (p) => {
|
|
7672
8450
|
try {
|
|
@@ -7757,12 +8535,12 @@ var exports_providers = {};
|
|
|
7757
8535
|
__export(exports_providers, {
|
|
7758
8536
|
default: () => providers_default
|
|
7759
8537
|
});
|
|
7760
|
-
var
|
|
8538
|
+
var import_picocolors23, providers_default;
|
|
7761
8539
|
var init_providers2 = __esm(() => {
|
|
7762
8540
|
init_dist();
|
|
7763
8541
|
init_out();
|
|
7764
8542
|
init_spec();
|
|
7765
|
-
|
|
8543
|
+
import_picocolors23 = __toESM(require_picocolors(), 1);
|
|
7766
8544
|
providers_default = defineCommand({
|
|
7767
8545
|
meta: {
|
|
7768
8546
|
name: "providers",
|
|
@@ -7787,7 +8565,7 @@ var init_providers2 = __esm(() => {
|
|
|
7787
8565
|
for (const id of supportedProviders) {
|
|
7788
8566
|
const spec = getProviderSpec(id);
|
|
7789
8567
|
ui.write(`
|
|
7790
|
-
${
|
|
8568
|
+
${import_picocolors23.default.bold(id)} \u2014 ${spec.name}`);
|
|
7791
8569
|
ui.info(` Manifest: ${spec.manifestPath}`);
|
|
7792
8570
|
ui.info(` Marketplace: ${spec.marketplacePath}`);
|
|
7793
8571
|
ui.info(` MCP: ${spec.mcpFilename}`);
|
|
@@ -7819,6 +8597,8 @@ var init_completion = __esm(() => {
|
|
|
7819
8597
|
"skill",
|
|
7820
8598
|
"journal",
|
|
7821
8599
|
"ui",
|
|
8600
|
+
"eval",
|
|
8601
|
+
"config",
|
|
7822
8602
|
"claude",
|
|
7823
8603
|
"codex",
|
|
7824
8604
|
"cursor",
|
|
@@ -7828,6 +8608,8 @@ var init_completion = __esm(() => {
|
|
|
7828
8608
|
subCommands = {
|
|
7829
8609
|
skill: ["validate", "drift", "judge"],
|
|
7830
8610
|
journal: ["init", "list", "context", "hook", "update", "add", "sync"],
|
|
8611
|
+
eval: ["history"],
|
|
8612
|
+
config: ["set", "get"],
|
|
7831
8613
|
hook: ["enable", "disable", "status"],
|
|
7832
8614
|
claude: ["new", "bump"],
|
|
7833
8615
|
codex: ["new", "bump"],
|
|
@@ -7862,6 +8644,8 @@ _doraval_completions() {
|
|
|
7862
8644
|
case "$prev" in
|
|
7863
8645
|
skill) COMPREPLY=( $(compgen -W "${subCommands.skill.join(" ")}" -- "$cur") ) ;;
|
|
7864
8646
|
journal) COMPREPLY=( $(compgen -W "${subCommands.journal.join(" ")}" -- "$cur") ) ;;
|
|
8647
|
+
eval) COMPREPLY=( $(compgen -W "${subCommands.eval.join(" ")}" -- "$cur") ) ;;
|
|
8648
|
+
config) COMPREPLY=( $(compgen -W "${subCommands.config.join(" ")}" -- "$cur") ) ;;
|
|
7865
8649
|
hook) COMPREPLY=( $(compgen -W "${subCommands.hook.join(" ")}" -- "$cur") ) ;;
|
|
7866
8650
|
ui) COMPREPLY=( $(compgen -W "${uiFlags.join(" ")}" -- "$cur") ) ;;
|
|
7867
8651
|
claude|codex|cursor|copilot) COMPREPLY=( $(compgen -W "${subCommands.claude.join(" ")}" -- "$cur") ) ;;
|
|
@@ -7876,7 +8660,7 @@ complete -F _doraval_completions doraval
|
|
|
7876
8660
|
|
|
7877
8661
|
_doraval() {
|
|
7878
8662
|
local -a commands sub
|
|
7879
|
-
commands=(validate init bump update providers skill journal ui claude codex cursor copilot)
|
|
8663
|
+
commands=(validate init bump update providers skill journal ui eval config claude codex cursor copilot)
|
|
7880
8664
|
_arguments -C \\
|
|
7881
8665
|
'1: :->cmd' \\
|
|
7882
8666
|
'*::arg:->args'
|
|
@@ -7893,6 +8677,12 @@ _doraval() {
|
|
|
7893
8677
|
journal)
|
|
7894
8678
|
_describe 'subcommand' (init list context hook update add sync)
|
|
7895
8679
|
;;
|
|
8680
|
+
eval)
|
|
8681
|
+
_describe 'subcommand' (history)
|
|
8682
|
+
;;
|
|
8683
|
+
config)
|
|
8684
|
+
_describe 'subcommand' (set get)
|
|
8685
|
+
;;
|
|
7896
8686
|
hook)
|
|
7897
8687
|
_describe 'subcommand' (enable disable status)
|
|
7898
8688
|
;;
|
|
@@ -7912,10 +8702,12 @@ _doraval "$@"
|
|
|
7912
8702
|
} else if (shell === "fish") {
|
|
7913
8703
|
console.log(`# doraval fish completion
|
|
7914
8704
|
complete -c doraval -f
|
|
7915
|
-
complete -c doraval -n '__fish_use_subcommand' -a 'validate init bump update providers skill journal ui claude codex cursor copilot'
|
|
8705
|
+
complete -c doraval -n '__fish_use_subcommand' -a 'validate init bump update providers skill journal ui eval config claude codex cursor copilot'
|
|
7916
8706
|
|
|
7917
8707
|
complete -c doraval -n '__fish_seen_subcommand_from skill' -a 'validate drift judge'
|
|
7918
8708
|
complete -c doraval -n '__fish_seen_subcommand_from journal' -a 'init list context hook update add sync'
|
|
8709
|
+
complete -c doraval -n '__fish_seen_subcommand_from eval' -a 'history'
|
|
8710
|
+
complete -c doraval -n '__fish_seen_subcommand_from config' -a 'set get'
|
|
7919
8711
|
complete -c doraval -n '__fish_seen_subcommand_from hook' -a 'enable disable status'
|
|
7920
8712
|
complete -c doraval -n '__fish_seen_subcommand_from ui' -l port -d 'Port'
|
|
7921
8713
|
complete -c doraval -n '__fish_seen_subcommand_from ui' -l open -d 'Open browser'
|
|
@@ -7937,7 +8729,7 @@ complete -c doraval -n '__fish_seen_subcommand_from claude codex cursor copilot'
|
|
|
7937
8729
|
// src/cli/index.ts
|
|
7938
8730
|
init_dist();
|
|
7939
8731
|
var import__package = __toESM(require_package(), 1);
|
|
7940
|
-
var
|
|
8732
|
+
var import_picocolors24 = __toESM(require_picocolors(), 1);
|
|
7941
8733
|
var skill = defineCommand({
|
|
7942
8734
|
meta: {
|
|
7943
8735
|
name: "skill",
|
|
@@ -7976,6 +8768,20 @@ var journal = defineCommand({
|
|
|
7976
8768
|
showUsage(journal);
|
|
7977
8769
|
}
|
|
7978
8770
|
});
|
|
8771
|
+
var evalCmd = defineCommand({
|
|
8772
|
+
meta: {
|
|
8773
|
+
name: "eval",
|
|
8774
|
+
description: "Evaluate a coding agent session against skill instructions"
|
|
8775
|
+
},
|
|
8776
|
+
subCommands: {
|
|
8777
|
+
history: () => Promise.resolve().then(() => (init_eval_history(), exports_eval_history)).then((m) => m.default)
|
|
8778
|
+
},
|
|
8779
|
+
async run(ctx) {
|
|
8780
|
+
const evalMain = await Promise.resolve().then(() => (init_eval(), exports_eval)).then((m) => m.default);
|
|
8781
|
+
return evalMain.run?.(ctx);
|
|
8782
|
+
}
|
|
8783
|
+
});
|
|
8784
|
+
var config = () => Promise.resolve().then(() => (init_config(), exports_config)).then((m) => m.default);
|
|
7979
8785
|
var claude = defineCommand({
|
|
7980
8786
|
meta: {
|
|
7981
8787
|
name: "claude",
|
|
@@ -8103,6 +8909,8 @@ var main = defineCommand({
|
|
|
8103
8909
|
completion: () => Promise.resolve().then(() => (init_completion(), exports_completion)).then((m) => m.default),
|
|
8104
8910
|
skill: () => Promise.resolve(skill),
|
|
8105
8911
|
journal: () => Promise.resolve(journal),
|
|
8912
|
+
eval: () => Promise.resolve(evalCmd),
|
|
8913
|
+
config,
|
|
8106
8914
|
claude: () => Promise.resolve(claude),
|
|
8107
8915
|
codex: () => Promise.resolve(codex),
|
|
8108
8916
|
cursor: () => Promise.resolve(cursor),
|
|
@@ -8115,7 +8923,7 @@ var main = defineCommand({
|
|
|
8115
8923
|
return;
|
|
8116
8924
|
if (process.stdout.isTTY) {
|
|
8117
8925
|
console.error(`
|
|
8118
|
-
` +
|
|
8926
|
+
` + import_picocolors24.default.blue(doraemonArt) + `
|
|
8119
8927
|
`);
|
|
8120
8928
|
}
|
|
8121
8929
|
showUsage(main);
|