juno-code 1.0.44 → 1.0.46
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 +1 -1
- package/dist/bin/cli.js +658 -50
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/cli.mjs +658 -50
- package/dist/bin/cli.mjs.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6 -4
- package/dist/index.mjs.map +1 -1
- package/dist/templates/scripts/__pycache__/attachment_downloader.cpython-38.pyc +0 -0
- package/dist/templates/scripts/__pycache__/github.cpython-38.pyc +0 -0
- package/dist/templates/scripts/__pycache__/slack_fetch.cpython-38.pyc +0 -0
- package/dist/templates/scripts/__pycache__/slack_state.cpython-38.pyc +0 -0
- package/dist/templates/scripts/attachment_downloader.py +405 -0
- package/dist/templates/scripts/github.py +282 -7
- package/dist/templates/scripts/hooks/session_counter.sh +328 -0
- package/dist/templates/scripts/kanban.sh +22 -4
- package/dist/templates/scripts/log_scanner.sh +790 -0
- package/dist/templates/scripts/slack_fetch.py +232 -20
- package/dist/templates/services/claude.py +50 -1
- package/dist/templates/services/codex.py +5 -4
- package/dist/templates/skills/claude/.gitkeep +0 -0
- package/dist/templates/skills/claude/plan-kanban-tasks/SKILL.md +25 -0
- package/dist/templates/skills/claude/ralph-loop/SKILL.md +43 -0
- package/dist/templates/skills/claude/ralph-loop/references/first_check.md +20 -0
- package/dist/templates/skills/claude/ralph-loop/references/implement.md +99 -0
- package/dist/templates/skills/claude/ralph-loop/scripts/kanban.sh +293 -0
- package/dist/templates/skills/claude/understand-project/SKILL.md +39 -0
- package/dist/templates/skills/codex/.gitkeep +0 -0
- package/dist/templates/skills/codex/ralph-loop/SKILL.md +43 -0
- package/dist/templates/skills/codex/ralph-loop/references/first_check.md +20 -0
- package/dist/templates/skills/codex/ralph-loop/references/implement.md +99 -0
- package/dist/templates/skills/codex/ralph-loop/scripts/kanban.sh +293 -0
- package/package.json +3 -2
package/dist/bin/cli.mjs
CHANGED
|
@@ -143,8 +143,8 @@ var init_types = __esm({
|
|
|
143
143
|
};
|
|
144
144
|
FileSystemError = class extends CLIError {
|
|
145
145
|
code = "FILESYSTEM_ERROR";
|
|
146
|
-
constructor(message,
|
|
147
|
-
super(
|
|
146
|
+
constructor(message, path25) {
|
|
147
|
+
super(path25 ? `${message}: ${path25}` : message);
|
|
148
148
|
this.suggestions = [
|
|
149
149
|
"Check file/directory permissions",
|
|
150
150
|
"Verify path exists and is accessible",
|
|
@@ -1819,8 +1819,8 @@ ${helpText}
|
|
|
1819
1819
|
}
|
|
1820
1820
|
}
|
|
1821
1821
|
if (options.cwd) {
|
|
1822
|
-
const
|
|
1823
|
-
if (!await
|
|
1822
|
+
const fs24 = await import('fs-extra');
|
|
1823
|
+
if (!await fs24.pathExists(options.cwd)) {
|
|
1824
1824
|
throw new ValidationError(
|
|
1825
1825
|
`Working directory does not exist: ${options.cwd}`,
|
|
1826
1826
|
["Verify the path exists", "Use absolute paths to avoid ambiguity"]
|
|
@@ -3647,31 +3647,103 @@ function parseResetTime(message) {
|
|
|
3647
3647
|
}
|
|
3648
3648
|
return { resetTime, timezone };
|
|
3649
3649
|
}
|
|
3650
|
+
function parseCodexResetTime(message) {
|
|
3651
|
+
const resetPattern = /try again at\s+(\w+)\s+(\d{1,2})(?:st|nd|rd|th)?,?\s*(\d{4})\s+(\d{1,2}):(\d{2})\s*(AM|PM)/i;
|
|
3652
|
+
const match = message.match(resetPattern);
|
|
3653
|
+
if (!match) {
|
|
3654
|
+
return null;
|
|
3655
|
+
}
|
|
3656
|
+
const monthStr = match[1];
|
|
3657
|
+
const day = parseInt(match[2], 10);
|
|
3658
|
+
const year = parseInt(match[3], 10);
|
|
3659
|
+
let hours = parseInt(match[4], 10);
|
|
3660
|
+
const minutes = parseInt(match[5], 10);
|
|
3661
|
+
const ampm = match[6].toUpperCase();
|
|
3662
|
+
const MONTH_MAP = {
|
|
3663
|
+
"jan": 0,
|
|
3664
|
+
"january": 0,
|
|
3665
|
+
"feb": 1,
|
|
3666
|
+
"february": 1,
|
|
3667
|
+
"mar": 2,
|
|
3668
|
+
"march": 2,
|
|
3669
|
+
"apr": 3,
|
|
3670
|
+
"april": 3,
|
|
3671
|
+
"may": 4,
|
|
3672
|
+
"jun": 5,
|
|
3673
|
+
"june": 5,
|
|
3674
|
+
"jul": 6,
|
|
3675
|
+
"july": 6,
|
|
3676
|
+
"aug": 7,
|
|
3677
|
+
"august": 7,
|
|
3678
|
+
"sep": 8,
|
|
3679
|
+
"september": 8,
|
|
3680
|
+
"oct": 9,
|
|
3681
|
+
"october": 9,
|
|
3682
|
+
"nov": 10,
|
|
3683
|
+
"november": 10,
|
|
3684
|
+
"dec": 11,
|
|
3685
|
+
"december": 11
|
|
3686
|
+
};
|
|
3687
|
+
const month = MONTH_MAP[monthStr.toLowerCase()];
|
|
3688
|
+
if (month === void 0) {
|
|
3689
|
+
return null;
|
|
3690
|
+
}
|
|
3691
|
+
if (ampm === "PM" && hours !== 12) {
|
|
3692
|
+
hours += 12;
|
|
3693
|
+
} else if (ampm === "AM" && hours === 12) {
|
|
3694
|
+
hours = 0;
|
|
3695
|
+
}
|
|
3696
|
+
const resetTime = new Date(year, month, day, hours, minutes, 0, 0);
|
|
3697
|
+
const now = /* @__PURE__ */ new Date();
|
|
3698
|
+
if (resetTime.getTime() <= now.getTime()) {
|
|
3699
|
+
resetTime.setTime(resetTime.getTime() + 24 * 60 * 60 * 1e3);
|
|
3700
|
+
}
|
|
3701
|
+
return { resetTime };
|
|
3702
|
+
}
|
|
3650
3703
|
function detectQuotaLimit(message) {
|
|
3651
3704
|
if (!message || typeof message !== "string") {
|
|
3652
3705
|
return { detected: false };
|
|
3653
3706
|
}
|
|
3654
|
-
const
|
|
3655
|
-
|
|
3707
|
+
const claudePattern = /you'?ve hit your limit/i;
|
|
3708
|
+
const codexPattern = /you'?ve hit your usage limit/i;
|
|
3709
|
+
const isClaudeQuota = claudePattern.test(message) && !codexPattern.test(message);
|
|
3710
|
+
const isCodexQuota = codexPattern.test(message);
|
|
3711
|
+
if (!isClaudeQuota && !isCodexQuota) {
|
|
3656
3712
|
return { detected: false };
|
|
3657
3713
|
}
|
|
3658
|
-
const
|
|
3659
|
-
|
|
3714
|
+
const source = isCodexQuota ? "codex" : "claude";
|
|
3715
|
+
const parsedClaude = parseResetTime(message);
|
|
3716
|
+
if (parsedClaude) {
|
|
3660
3717
|
const now = /* @__PURE__ */ new Date();
|
|
3661
|
-
const sleepDurationMs = Math.max(0,
|
|
3718
|
+
const sleepDurationMs = Math.max(0, parsedClaude.resetTime.getTime() - now.getTime());
|
|
3662
3719
|
return {
|
|
3663
3720
|
detected: true,
|
|
3664
|
-
resetTime:
|
|
3721
|
+
resetTime: parsedClaude.resetTime,
|
|
3665
3722
|
sleepDurationMs,
|
|
3666
|
-
timezone:
|
|
3667
|
-
originalMessage: message
|
|
3723
|
+
timezone: parsedClaude.timezone,
|
|
3724
|
+
originalMessage: message,
|
|
3725
|
+
source
|
|
3726
|
+
};
|
|
3727
|
+
}
|
|
3728
|
+
const parsedCodex = parseCodexResetTime(message);
|
|
3729
|
+
if (parsedCodex) {
|
|
3730
|
+
const now = /* @__PURE__ */ new Date();
|
|
3731
|
+
const sleepDurationMs = Math.max(0, parsedCodex.resetTime.getTime() - now.getTime());
|
|
3732
|
+
return {
|
|
3733
|
+
detected: true,
|
|
3734
|
+
resetTime: parsedCodex.resetTime,
|
|
3735
|
+
sleepDurationMs,
|
|
3736
|
+
timezone: "local",
|
|
3737
|
+
originalMessage: message,
|
|
3738
|
+
source
|
|
3668
3739
|
};
|
|
3669
3740
|
}
|
|
3670
3741
|
return {
|
|
3671
3742
|
detected: true,
|
|
3672
3743
|
sleepDurationMs: 5 * 60 * 1e3,
|
|
3673
3744
|
// 5 minutes default
|
|
3674
|
-
originalMessage: message
|
|
3745
|
+
originalMessage: message,
|
|
3746
|
+
source
|
|
3675
3747
|
};
|
|
3676
3748
|
}
|
|
3677
3749
|
function formatDuration(ms) {
|
|
@@ -4196,8 +4268,68 @@ var init_shell_backend = __esm({
|
|
|
4196
4268
|
metadata
|
|
4197
4269
|
};
|
|
4198
4270
|
}
|
|
4271
|
+
if (subagentType === "codex") {
|
|
4272
|
+
const codexQuotaMessage = this.extractCodexQuotaMessage(result.output, result.error);
|
|
4273
|
+
if (codexQuotaMessage) {
|
|
4274
|
+
const quotaLimitInfo = detectQuotaLimit(codexQuotaMessage);
|
|
4275
|
+
if (quotaLimitInfo.detected) {
|
|
4276
|
+
const metadata = {
|
|
4277
|
+
structuredOutput: true,
|
|
4278
|
+
contentType: "application/json",
|
|
4279
|
+
rawOutput: result.output,
|
|
4280
|
+
quotaLimitInfo
|
|
4281
|
+
};
|
|
4282
|
+
const structuredPayload = {
|
|
4283
|
+
type: "result",
|
|
4284
|
+
subtype: "error",
|
|
4285
|
+
is_error: true,
|
|
4286
|
+
result: codexQuotaMessage,
|
|
4287
|
+
error: codexQuotaMessage,
|
|
4288
|
+
exit_code: result.exitCode,
|
|
4289
|
+
duration_ms: result.duration,
|
|
4290
|
+
quota_limit: quotaLimitInfo
|
|
4291
|
+
};
|
|
4292
|
+
return {
|
|
4293
|
+
content: JSON.stringify(structuredPayload),
|
|
4294
|
+
metadata
|
|
4295
|
+
};
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4199
4299
|
return { content: result.output, metadata: result.metadata };
|
|
4200
4300
|
}
|
|
4301
|
+
/**
|
|
4302
|
+
* Extract quota limit message from Codex stream output
|
|
4303
|
+
* Codex outputs JSON events like:
|
|
4304
|
+
* {"type": "error", "message": "You've hit your usage limit..."}
|
|
4305
|
+
* {"type": "turn.failed", "error": {"message": "You've hit your usage limit..."}}
|
|
4306
|
+
*/
|
|
4307
|
+
extractCodexQuotaMessage(output, stderr) {
|
|
4308
|
+
const sources = [output, stderr].filter(Boolean);
|
|
4309
|
+
for (const source of sources) {
|
|
4310
|
+
const lines = source.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4311
|
+
for (const line of lines) {
|
|
4312
|
+
try {
|
|
4313
|
+
const parsed = JSON.parse(line);
|
|
4314
|
+
if (parsed?.type === "error" && parsed?.message) {
|
|
4315
|
+
if (/you'?ve hit your usage limit/i.test(parsed.message)) {
|
|
4316
|
+
return parsed.message;
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
if (parsed?.type === "turn.failed" && parsed?.error?.message) {
|
|
4320
|
+
if (/you'?ve hit your usage limit/i.test(parsed.error.message)) {
|
|
4321
|
+
return parsed.error.message;
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
} catch {
|
|
4325
|
+
if (/you'?ve hit your usage limit/i.test(line)) {
|
|
4326
|
+
return line;
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
return null;
|
|
4332
|
+
}
|
|
4201
4333
|
/**
|
|
4202
4334
|
* Extract the last valid JSON object from a script's stdout to use as a structured payload fallback.
|
|
4203
4335
|
*/
|
|
@@ -5177,8 +5309,9 @@ var init_engine = __esm({
|
|
|
5177
5309
|
hour12: true,
|
|
5178
5310
|
timeZoneName: "short"
|
|
5179
5311
|
}) : "unknown";
|
|
5312
|
+
const sourceLabel = quotaInfo.source === "codex" ? "Codex" : "Claude";
|
|
5180
5313
|
engineLogger.info(`\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`);
|
|
5181
|
-
engineLogger.info(`\u2551
|
|
5314
|
+
engineLogger.info(`\u2551 ${sourceLabel} Quota Limit Reached${" ".repeat(44 - sourceLabel.length - " Quota Limit Reached".length)}\u2551`);
|
|
5182
5315
|
engineLogger.info(`\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`);
|
|
5183
5316
|
engineLogger.info(`\u2551 Quota resets at: ${resetTimeStr2.padEnd(44)}\u2551`);
|
|
5184
5317
|
engineLogger.info(`\u2551 Behavior: raise (exit immediately) \u2551`);
|
|
@@ -5188,7 +5321,7 @@ var init_engine = __esm({
|
|
|
5188
5321
|
engineLogger.info(`\u2551 Or in config.json: { "onHourlyLimit": "wait" } \u2551`);
|
|
5189
5322
|
engineLogger.info(`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`);
|
|
5190
5323
|
this.emit("quota-limit:raise", { context, quotaInfo });
|
|
5191
|
-
throw new Error(
|
|
5324
|
+
throw new Error(`${sourceLabel} quota limit reached. Quota resets at ${resetTimeStr2}. Use --on-hourly-limit wait to auto-retry.`);
|
|
5192
5325
|
}
|
|
5193
5326
|
const waitTimeMs = quotaInfo.sleepDurationMs;
|
|
5194
5327
|
const maxWaitTimeMs = 12 * 60 * 60 * 1e3;
|
|
@@ -5204,8 +5337,9 @@ var init_engine = __esm({
|
|
|
5204
5337
|
timeZoneName: "short"
|
|
5205
5338
|
}) : "unknown";
|
|
5206
5339
|
const durationStr = formatDuration(waitTimeMs);
|
|
5340
|
+
const waitSourceLabel = quotaInfo.source === "codex" ? "Codex" : "Claude";
|
|
5207
5341
|
engineLogger.info(`\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`);
|
|
5208
|
-
engineLogger.info(`\u2551
|
|
5342
|
+
engineLogger.info(`\u2551 ${waitSourceLabel} Quota Limit Reached${" ".repeat(44 - waitSourceLabel.length - " Quota Limit Reached".length)}\u2551`);
|
|
5209
5343
|
engineLogger.info(`\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`);
|
|
5210
5344
|
engineLogger.info(`\u2551 Quota resets at: ${resetTimeStr.padEnd(44)}\u2551`);
|
|
5211
5345
|
engineLogger.info(`\u2551 Sleeping for: ${durationStr.padEnd(44)}\u2551`);
|
|
@@ -12664,12 +12798,12 @@ function safeConsoleOutput(message, options = {}) {
|
|
|
12664
12798
|
const capabilities = getTUICapabilities();
|
|
12665
12799
|
let output = message;
|
|
12666
12800
|
if (capabilities.hasColors && (color || bold)) {
|
|
12667
|
-
const
|
|
12668
|
-
if (color &&
|
|
12669
|
-
output =
|
|
12801
|
+
const chalk25 = __require("chalk");
|
|
12802
|
+
if (color && chalk25[color]) {
|
|
12803
|
+
output = chalk25[color](output);
|
|
12670
12804
|
}
|
|
12671
12805
|
if (bold) {
|
|
12672
|
-
output =
|
|
12806
|
+
output = chalk25.bold(output);
|
|
12673
12807
|
}
|
|
12674
12808
|
}
|
|
12675
12809
|
console[type](output);
|
|
@@ -13005,8 +13139,45 @@ var init_tui = __esm({
|
|
|
13005
13139
|
var main_exports = {};
|
|
13006
13140
|
__export(main_exports, {
|
|
13007
13141
|
createMainCommand: () => createMainCommand,
|
|
13142
|
+
getDefaultModelForSubagent: () => getDefaultModelForSubagent,
|
|
13143
|
+
isModelCompatibleWithSubagent: () => isModelCompatibleWithSubagent,
|
|
13008
13144
|
mainCommandHandler: () => mainCommandHandler
|
|
13009
13145
|
});
|
|
13146
|
+
function getDefaultModelForSubagent(subagent) {
|
|
13147
|
+
const modelDefaults = {
|
|
13148
|
+
claude: ":sonnet",
|
|
13149
|
+
codex: ":codex",
|
|
13150
|
+
// Expands to gpt-5.3-codex in codex.py
|
|
13151
|
+
gemini: ":pro",
|
|
13152
|
+
// Expands to gemini-2.5-pro in gemini.py
|
|
13153
|
+
cursor: "auto"
|
|
13154
|
+
};
|
|
13155
|
+
return modelDefaults[subagent] || modelDefaults.claude;
|
|
13156
|
+
}
|
|
13157
|
+
function isModelCompatibleWithSubagent(model, subagent) {
|
|
13158
|
+
if (!model.startsWith(":")) {
|
|
13159
|
+
return true;
|
|
13160
|
+
}
|
|
13161
|
+
const claudeShorthands = [":sonnet", ":haiku", ":opus"];
|
|
13162
|
+
const codexShorthands = [":codex", ":codex-mini", ":gpt-5", ":mini"];
|
|
13163
|
+
const geminiShorthands = [":pro", ":flash"];
|
|
13164
|
+
const isClaudeModel = claudeShorthands.includes(model) || model.startsWith(":claude");
|
|
13165
|
+
const isCodexModel = codexShorthands.includes(model) || model.startsWith(":gpt");
|
|
13166
|
+
const isGeminiModel = geminiShorthands.includes(model) || model.startsWith(":gemini");
|
|
13167
|
+
switch (subagent) {
|
|
13168
|
+
case "claude":
|
|
13169
|
+
return isClaudeModel || !isCodexModel && !isGeminiModel;
|
|
13170
|
+
case "codex":
|
|
13171
|
+
return isCodexModel || !isClaudeModel && !isGeminiModel;
|
|
13172
|
+
case "gemini":
|
|
13173
|
+
return isGeminiModel || !isClaudeModel && !isCodexModel;
|
|
13174
|
+
case "cursor":
|
|
13175
|
+
return true;
|
|
13176
|
+
// Cursor accepts any model
|
|
13177
|
+
default:
|
|
13178
|
+
return true;
|
|
13179
|
+
}
|
|
13180
|
+
}
|
|
13010
13181
|
async function mainCommandHandler(args, options, command) {
|
|
13011
13182
|
try {
|
|
13012
13183
|
const validSubagents = ["claude", "cursor", "codex", "gemini"];
|
|
@@ -13058,13 +13229,15 @@ async function mainCommandHandler(args, options, command) {
|
|
|
13058
13229
|
["Use -1 for unlimited iterations", "Use positive integers like 1, 5, or 10", "Example: -i 5"]
|
|
13059
13230
|
);
|
|
13060
13231
|
}
|
|
13232
|
+
const configModelIsValid = config.defaultModel && config.defaultSubagent === options.subagent && isModelCompatibleWithSubagent(config.defaultModel, options.subagent);
|
|
13233
|
+
const resolvedModel = options.model || (configModelIsValid ? config.defaultModel : void 0) || getDefaultModelForSubagent(options.subagent);
|
|
13061
13234
|
const executionRequest = createExecutionRequest({
|
|
13062
13235
|
instruction,
|
|
13063
13236
|
subagent: options.subagent,
|
|
13064
13237
|
backend: selectedBackend,
|
|
13065
13238
|
workingDirectory: config.workingDirectory,
|
|
13066
13239
|
maxIterations: options.maxIterations ?? config.defaultMaxIterations,
|
|
13067
|
-
model:
|
|
13240
|
+
model: resolvedModel,
|
|
13068
13241
|
agents: options.agents,
|
|
13069
13242
|
tools: options.tools,
|
|
13070
13243
|
allowedTools: options.allowedTools,
|
|
@@ -13586,6 +13759,289 @@ var init_main = __esm({
|
|
|
13586
13759
|
}
|
|
13587
13760
|
});
|
|
13588
13761
|
|
|
13762
|
+
// src/utils/skill-installer.ts
|
|
13763
|
+
var skill_installer_exports = {};
|
|
13764
|
+
__export(skill_installer_exports, {
|
|
13765
|
+
SkillInstaller: () => SkillInstaller
|
|
13766
|
+
});
|
|
13767
|
+
var SkillInstaller;
|
|
13768
|
+
var init_skill_installer = __esm({
|
|
13769
|
+
"src/utils/skill-installer.ts"() {
|
|
13770
|
+
init_version();
|
|
13771
|
+
SkillInstaller = class {
|
|
13772
|
+
/**
|
|
13773
|
+
* Skill groups define which template folders map to which project directories.
|
|
13774
|
+
* New agents can be added here without changing any other logic.
|
|
13775
|
+
*/
|
|
13776
|
+
static SKILL_GROUPS = [
|
|
13777
|
+
{ name: "codex", destDir: ".agents/skills" },
|
|
13778
|
+
{ name: "claude", destDir: ".claude/skills" }
|
|
13779
|
+
];
|
|
13780
|
+
/**
|
|
13781
|
+
* Get the templates skills directory from the package
|
|
13782
|
+
*/
|
|
13783
|
+
static getPackageSkillsDir() {
|
|
13784
|
+
const __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
|
|
13785
|
+
const candidates = [
|
|
13786
|
+
path3.join(__dirname2, "..", "..", "templates", "skills"),
|
|
13787
|
+
// dist (production)
|
|
13788
|
+
path3.join(__dirname2, "..", "templates", "skills")
|
|
13789
|
+
// src (development)
|
|
13790
|
+
];
|
|
13791
|
+
for (const skillsPath of candidates) {
|
|
13792
|
+
if (fs3.existsSync(skillsPath)) {
|
|
13793
|
+
return skillsPath;
|
|
13794
|
+
}
|
|
13795
|
+
}
|
|
13796
|
+
if (process.env.JUNO_CODE_DEBUG === "1") {
|
|
13797
|
+
console.error("[DEBUG] SkillInstaller: Could not find templates/skills directory");
|
|
13798
|
+
console.error("[DEBUG] Tried:", candidates);
|
|
13799
|
+
}
|
|
13800
|
+
return null;
|
|
13801
|
+
}
|
|
13802
|
+
/**
|
|
13803
|
+
* Get list of skill files in a specific skill group template directory.
|
|
13804
|
+
* Returns paths relative to the group directory.
|
|
13805
|
+
*/
|
|
13806
|
+
static async getSkillFiles(groupDir) {
|
|
13807
|
+
if (!await fs3.pathExists(groupDir)) {
|
|
13808
|
+
return [];
|
|
13809
|
+
}
|
|
13810
|
+
const files = [];
|
|
13811
|
+
const walk = async (dir, prefix) => {
|
|
13812
|
+
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
13813
|
+
for (const entry of entries) {
|
|
13814
|
+
if (entry.name.startsWith(".") || entry.name === "__pycache__") {
|
|
13815
|
+
continue;
|
|
13816
|
+
}
|
|
13817
|
+
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
13818
|
+
if (entry.isDirectory()) {
|
|
13819
|
+
await walk(path3.join(dir, entry.name), relPath);
|
|
13820
|
+
} else {
|
|
13821
|
+
files.push(relPath);
|
|
13822
|
+
}
|
|
13823
|
+
}
|
|
13824
|
+
};
|
|
13825
|
+
await walk(groupDir, "");
|
|
13826
|
+
return files;
|
|
13827
|
+
}
|
|
13828
|
+
/**
|
|
13829
|
+
* Install skills for a single skill group.
|
|
13830
|
+
* Only copies skill files, does NOT delete or modify any other files in the destination.
|
|
13831
|
+
*
|
|
13832
|
+
* @param projectDir - The project root directory
|
|
13833
|
+
* @param group - The skill group to install
|
|
13834
|
+
* @param silent - If true, suppresses console output
|
|
13835
|
+
* @param force - If true, overwrite even if content is identical
|
|
13836
|
+
* @returns number of files installed or updated
|
|
13837
|
+
*/
|
|
13838
|
+
static async installGroup(projectDir, group, silent = true, force = false) {
|
|
13839
|
+
const debug = process.env.JUNO_CODE_DEBUG === "1";
|
|
13840
|
+
const packageSkillsDir = this.getPackageSkillsDir();
|
|
13841
|
+
if (!packageSkillsDir) {
|
|
13842
|
+
if (debug) {
|
|
13843
|
+
console.error("[DEBUG] SkillInstaller: Package skills directory not found");
|
|
13844
|
+
}
|
|
13845
|
+
return 0;
|
|
13846
|
+
}
|
|
13847
|
+
const sourceGroupDir = path3.join(packageSkillsDir, group.name);
|
|
13848
|
+
const destGroupDir = path3.join(projectDir, group.destDir);
|
|
13849
|
+
const skillFiles = await this.getSkillFiles(sourceGroupDir);
|
|
13850
|
+
if (skillFiles.length === 0) {
|
|
13851
|
+
if (debug) {
|
|
13852
|
+
console.error(`[DEBUG] SkillInstaller: No skill files found for group '${group.name}'`);
|
|
13853
|
+
}
|
|
13854
|
+
return 0;
|
|
13855
|
+
}
|
|
13856
|
+
await fs3.ensureDir(destGroupDir);
|
|
13857
|
+
let installed = 0;
|
|
13858
|
+
for (const relFile of skillFiles) {
|
|
13859
|
+
const srcPath = path3.join(sourceGroupDir, relFile);
|
|
13860
|
+
const destPath = path3.join(destGroupDir, relFile);
|
|
13861
|
+
const destParent = path3.dirname(destPath);
|
|
13862
|
+
await fs3.ensureDir(destParent);
|
|
13863
|
+
let shouldCopy = force;
|
|
13864
|
+
if (!shouldCopy) {
|
|
13865
|
+
if (!await fs3.pathExists(destPath)) {
|
|
13866
|
+
shouldCopy = true;
|
|
13867
|
+
} else {
|
|
13868
|
+
const [srcContent, destContent] = await Promise.all([
|
|
13869
|
+
fs3.readFile(srcPath, "utf-8"),
|
|
13870
|
+
fs3.readFile(destPath, "utf-8")
|
|
13871
|
+
]);
|
|
13872
|
+
if (srcContent !== destContent) {
|
|
13873
|
+
shouldCopy = true;
|
|
13874
|
+
}
|
|
13875
|
+
}
|
|
13876
|
+
}
|
|
13877
|
+
if (shouldCopy) {
|
|
13878
|
+
await fs3.copy(srcPath, destPath, { overwrite: true });
|
|
13879
|
+
if (relFile.endsWith(".sh") || relFile.endsWith(".py")) {
|
|
13880
|
+
await fs3.chmod(destPath, 493);
|
|
13881
|
+
}
|
|
13882
|
+
installed++;
|
|
13883
|
+
if (debug) {
|
|
13884
|
+
console.error(`[DEBUG] SkillInstaller: Installed ${group.name}/${relFile} -> ${destPath}`);
|
|
13885
|
+
}
|
|
13886
|
+
}
|
|
13887
|
+
}
|
|
13888
|
+
if (installed > 0 && !silent) {
|
|
13889
|
+
console.log(`\u2713 Installed ${installed} skill file(s) for ${group.name} -> ${group.destDir}`);
|
|
13890
|
+
}
|
|
13891
|
+
return installed;
|
|
13892
|
+
}
|
|
13893
|
+
/**
|
|
13894
|
+
* Install skills for all skill groups.
|
|
13895
|
+
* This copies skill files to the appropriate project directories while
|
|
13896
|
+
* preserving any existing files the user may have added.
|
|
13897
|
+
*
|
|
13898
|
+
* @param projectDir - The project root directory
|
|
13899
|
+
* @param silent - If true, suppresses console output
|
|
13900
|
+
* @param force - If true, overwrite even if content matches
|
|
13901
|
+
* @returns true if any skill files were installed or updated
|
|
13902
|
+
*/
|
|
13903
|
+
static async install(projectDir, silent = false, force = false) {
|
|
13904
|
+
const debug = process.env.JUNO_CODE_DEBUG === "1";
|
|
13905
|
+
let totalInstalled = 0;
|
|
13906
|
+
for (const group of this.SKILL_GROUPS) {
|
|
13907
|
+
try {
|
|
13908
|
+
const count = await this.installGroup(projectDir, group, silent, force);
|
|
13909
|
+
totalInstalled += count;
|
|
13910
|
+
} catch (error) {
|
|
13911
|
+
if (debug) {
|
|
13912
|
+
console.error(`[DEBUG] SkillInstaller: Error installing group '${group.name}':`, error);
|
|
13913
|
+
}
|
|
13914
|
+
if (!silent) {
|
|
13915
|
+
console.error(`\u26A0\uFE0F Failed to install skills for ${group.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
13916
|
+
}
|
|
13917
|
+
}
|
|
13918
|
+
}
|
|
13919
|
+
if (totalInstalled > 0 && !silent) {
|
|
13920
|
+
console.log(`\u2713 Total: ${totalInstalled} skill file(s) installed/updated`);
|
|
13921
|
+
}
|
|
13922
|
+
return totalInstalled > 0;
|
|
13923
|
+
}
|
|
13924
|
+
/**
|
|
13925
|
+
* Auto-update skills on CLI startup.
|
|
13926
|
+
* Only installs/updates if the project is initialized (.juno_task exists).
|
|
13927
|
+
* Silently does nothing if no skill files are bundled or project is not initialized.
|
|
13928
|
+
*
|
|
13929
|
+
* @param projectDir - The project root directory
|
|
13930
|
+
* @param force - If true, force reinstall all skills
|
|
13931
|
+
* @returns true if any updates occurred
|
|
13932
|
+
*/
|
|
13933
|
+
static async autoUpdate(projectDir, force = false) {
|
|
13934
|
+
try {
|
|
13935
|
+
const debug = process.env.JUNO_CODE_DEBUG === "1";
|
|
13936
|
+
const junoTaskDir = path3.join(projectDir, ".juno_task");
|
|
13937
|
+
if (!await fs3.pathExists(junoTaskDir)) {
|
|
13938
|
+
return false;
|
|
13939
|
+
}
|
|
13940
|
+
if (debug) {
|
|
13941
|
+
console.error(`[DEBUG] SkillInstaller: Auto-updating skills (force=${force})`);
|
|
13942
|
+
}
|
|
13943
|
+
const updated = await this.install(projectDir, true, force);
|
|
13944
|
+
if (updated && debug) {
|
|
13945
|
+
console.error("[DEBUG] SkillInstaller: Skills auto-updated successfully");
|
|
13946
|
+
}
|
|
13947
|
+
return updated;
|
|
13948
|
+
} catch (error) {
|
|
13949
|
+
if (process.env.JUNO_CODE_DEBUG === "1") {
|
|
13950
|
+
console.error("[DEBUG] SkillInstaller: autoUpdate error:", error instanceof Error ? error.message : String(error));
|
|
13951
|
+
}
|
|
13952
|
+
return false;
|
|
13953
|
+
}
|
|
13954
|
+
}
|
|
13955
|
+
/**
|
|
13956
|
+
* Check if any skills need to be installed or updated.
|
|
13957
|
+
*
|
|
13958
|
+
* @param projectDir - The project root directory
|
|
13959
|
+
* @returns true if any skills are missing or outdated
|
|
13960
|
+
*/
|
|
13961
|
+
static async needsUpdate(projectDir) {
|
|
13962
|
+
try {
|
|
13963
|
+
const junoTaskDir = path3.join(projectDir, ".juno_task");
|
|
13964
|
+
if (!await fs3.pathExists(junoTaskDir)) {
|
|
13965
|
+
return false;
|
|
13966
|
+
}
|
|
13967
|
+
const packageSkillsDir = this.getPackageSkillsDir();
|
|
13968
|
+
if (!packageSkillsDir) {
|
|
13969
|
+
return false;
|
|
13970
|
+
}
|
|
13971
|
+
for (const group of this.SKILL_GROUPS) {
|
|
13972
|
+
const sourceGroupDir = path3.join(packageSkillsDir, group.name);
|
|
13973
|
+
const destGroupDir = path3.join(projectDir, group.destDir);
|
|
13974
|
+
const skillFiles = await this.getSkillFiles(sourceGroupDir);
|
|
13975
|
+
for (const relFile of skillFiles) {
|
|
13976
|
+
const srcPath = path3.join(sourceGroupDir, relFile);
|
|
13977
|
+
const destPath = path3.join(destGroupDir, relFile);
|
|
13978
|
+
if (!await fs3.pathExists(destPath)) {
|
|
13979
|
+
return true;
|
|
13980
|
+
}
|
|
13981
|
+
const [srcContent, destContent] = await Promise.all([
|
|
13982
|
+
fs3.readFile(srcPath, "utf-8"),
|
|
13983
|
+
fs3.readFile(destPath, "utf-8")
|
|
13984
|
+
]);
|
|
13985
|
+
if (srcContent !== destContent) {
|
|
13986
|
+
return true;
|
|
13987
|
+
}
|
|
13988
|
+
}
|
|
13989
|
+
}
|
|
13990
|
+
return false;
|
|
13991
|
+
} catch {
|
|
13992
|
+
return false;
|
|
13993
|
+
}
|
|
13994
|
+
}
|
|
13995
|
+
/**
|
|
13996
|
+
* List all skill groups and their installation status.
|
|
13997
|
+
*
|
|
13998
|
+
* @param projectDir - The project root directory
|
|
13999
|
+
* @returns Array of skill group status objects
|
|
14000
|
+
*/
|
|
14001
|
+
static async listSkillGroups(projectDir) {
|
|
14002
|
+
const packageSkillsDir = this.getPackageSkillsDir();
|
|
14003
|
+
const results = [];
|
|
14004
|
+
for (const group of this.SKILL_GROUPS) {
|
|
14005
|
+
const sourceGroupDir = packageSkillsDir ? path3.join(packageSkillsDir, group.name) : "";
|
|
14006
|
+
const destGroupDir = path3.join(projectDir, group.destDir);
|
|
14007
|
+
const skillFiles = packageSkillsDir ? await this.getSkillFiles(sourceGroupDir) : [];
|
|
14008
|
+
const files = [];
|
|
14009
|
+
for (const relFile of skillFiles) {
|
|
14010
|
+
const srcPath = path3.join(sourceGroupDir, relFile);
|
|
14011
|
+
const destPath = path3.join(destGroupDir, relFile);
|
|
14012
|
+
const installed = await fs3.pathExists(destPath);
|
|
14013
|
+
let upToDate = false;
|
|
14014
|
+
if (installed) {
|
|
14015
|
+
try {
|
|
14016
|
+
const [srcContent, destContent] = await Promise.all([
|
|
14017
|
+
fs3.readFile(srcPath, "utf-8"),
|
|
14018
|
+
fs3.readFile(destPath, "utf-8")
|
|
14019
|
+
]);
|
|
14020
|
+
upToDate = srcContent === destContent;
|
|
14021
|
+
} catch {
|
|
14022
|
+
upToDate = false;
|
|
14023
|
+
}
|
|
14024
|
+
}
|
|
14025
|
+
files.push({ name: relFile, installed, upToDate });
|
|
14026
|
+
}
|
|
14027
|
+
results.push({
|
|
14028
|
+
name: group.name,
|
|
14029
|
+
destDir: group.destDir,
|
|
14030
|
+
files
|
|
14031
|
+
});
|
|
14032
|
+
}
|
|
14033
|
+
return results;
|
|
14034
|
+
}
|
|
14035
|
+
/**
|
|
14036
|
+
* Get the list of skill group configurations.
|
|
14037
|
+
*/
|
|
14038
|
+
static getSkillGroups() {
|
|
14039
|
+
return [...this.SKILL_GROUPS];
|
|
14040
|
+
}
|
|
14041
|
+
};
|
|
14042
|
+
}
|
|
14043
|
+
});
|
|
14044
|
+
|
|
13589
14045
|
// src/utils/script-installer.ts
|
|
13590
14046
|
var script_installer_exports = {};
|
|
13591
14047
|
__export(script_installer_exports, {
|
|
@@ -13604,12 +14060,16 @@ var init_script_installer = __esm({
|
|
|
13604
14060
|
* Required scripts include both standalone scripts and their dependencies.
|
|
13605
14061
|
* kanban.sh depends on install_requirements.sh for Python venv setup.
|
|
13606
14062
|
* Slack integration scripts allow fetching tasks from Slack and responding.
|
|
14063
|
+
* Hook scripts are stored in the hooks/ subdirectory.
|
|
13607
14064
|
*/
|
|
13608
14065
|
static REQUIRED_SCRIPTS = [
|
|
13609
14066
|
"run_until_completion.sh",
|
|
13610
14067
|
"kanban.sh",
|
|
13611
14068
|
"install_requirements.sh",
|
|
13612
14069
|
// Required by kanban.sh for Python venv creation
|
|
14070
|
+
// Shared utilities
|
|
14071
|
+
"attachment_downloader.py",
|
|
14072
|
+
// File attachment downloading utility (used by Slack/GitHub)
|
|
13613
14073
|
// Slack integration scripts
|
|
13614
14074
|
"slack_state.py",
|
|
13615
14075
|
// State management for Slack integration
|
|
@@ -13622,8 +14082,14 @@ var init_script_installer = __esm({
|
|
|
13622
14082
|
"slack_respond.sh",
|
|
13623
14083
|
// Wrapper script for Slack respond
|
|
13624
14084
|
// GitHub integration script (single-file architecture)
|
|
13625
|
-
"github.py"
|
|
14085
|
+
"github.py",
|
|
13626
14086
|
// Unified GitHub integration (fetch, respond, sync)
|
|
14087
|
+
// Claude Code hooks (stored in hooks/ subdirectory)
|
|
14088
|
+
"hooks/session_counter.sh",
|
|
14089
|
+
// Session message counter hook for warning about long sessions
|
|
14090
|
+
// Log scanning utility
|
|
14091
|
+
"log_scanner.sh"
|
|
14092
|
+
// Scans log files for errors/exceptions and creates kanban bug reports
|
|
13627
14093
|
];
|
|
13628
14094
|
/**
|
|
13629
14095
|
* Get the templates scripts directory from the package
|
|
@@ -13680,6 +14146,10 @@ var init_script_installer = __esm({
|
|
|
13680
14146
|
const destDir = path3.join(projectDir, ".juno_task", "scripts");
|
|
13681
14147
|
await fs3.ensureDir(destDir);
|
|
13682
14148
|
const destPath = path3.join(destDir, scriptName);
|
|
14149
|
+
const destParentDir = path3.dirname(destPath);
|
|
14150
|
+
if (destParentDir !== destDir) {
|
|
14151
|
+
await fs3.ensureDir(destParentDir);
|
|
14152
|
+
}
|
|
13683
14153
|
await fs3.copy(sourcePath, destPath, { overwrite: true });
|
|
13684
14154
|
if (scriptName.endsWith(".sh") || scriptName.endsWith(".py")) {
|
|
13685
14155
|
await fs3.chmod(destPath, 493);
|
|
@@ -14855,8 +15325,7 @@ After completing the proccess an implementer agent would start the job and go th
|
|
|
14855
15325
|
category: "core" /* CORE */,
|
|
14856
15326
|
content: `0a. study @.juno_task/implement.md.
|
|
14857
15327
|
|
|
14858
|
-
0b. When you discover a syntax, logic, UI, User Flow Error or bug. Immediately update
|
|
14859
|
-
|
|
15328
|
+
0b. When you discover a syntax, logic, UI, User Flow Error or bug. Immediately update Kanban with your findings using a {{SUBAGENT}} subagent. When the issue is resolved, update Kanban.
|
|
14860
15329
|
|
|
14861
15330
|
999. Important: When authoring documentation capture the why tests and the backing implementation is important.
|
|
14862
15331
|
|
|
@@ -14946,7 +15415,7 @@ Tasks , USER_FEEDBACK and @{{AGENT_DOC_FILE}} should repesent truth. User Open I
|
|
|
14946
15415
|
description: "Implementation steps and current task breakdown",
|
|
14947
15416
|
category: "workflow" /* WORKFLOW */,
|
|
14948
15417
|
content: `---
|
|
14949
|
-
description: Execute the implementation plan by processing and executing all tasks defined in
|
|
15418
|
+
description: Execute the implementation plan by processing and executing all tasks defined in Kanban
|
|
14950
15419
|
---
|
|
14951
15420
|
|
|
14952
15421
|
## User Input
|
|
@@ -15005,7 +15474,7 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|
|
15005
15474
|
1. Run \`./.juno_task/scripts/kanban.sh list\` from repo root and check current project status.
|
|
15006
15475
|
|
|
15007
15476
|
2. Load and analyze the implementation context:
|
|
15008
|
-
- **REQUIRED**: Read
|
|
15477
|
+
- **REQUIRED**: Read Kanban for the complete task list and execution plan
|
|
15009
15478
|
- **REQUIRED**: Read plan.md for tech stack, architecture, and file structure
|
|
15010
15479
|
- **IF EXISTS**: Read data-model.md for entities and relationships
|
|
15011
15480
|
- **IF EXISTS**: Read contracts/ for API specifications and test requirements
|
|
@@ -15045,7 +15514,7 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|
|
15045
15514
|
- **Prettier**: \`node_modules/\`, \`dist/\`, \`build/\`, \`coverage/\`, \`package-lock.json\`, \`yarn.lock\`, \`pnpm-lock.yaml\`
|
|
15046
15515
|
- **Terraform**: \`.terraform/\`, \`*.tfstate*\`, \`*.tfvars\`, \`.terraform.lock.hcl\`
|
|
15047
15516
|
|
|
15048
|
-
5. Parse
|
|
15517
|
+
5. Parse Kanban structure and extract:
|
|
15049
15518
|
- **Task phases**: Setup, Tests, Core, Integration, Polish
|
|
15050
15519
|
- **Task dependencies**: Sequential vs parallel execution rules
|
|
15051
15520
|
- **Task details**: ID, description, file paths, parallel markers [P]
|
|
@@ -15094,7 +15563,7 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|
|
15094
15563
|
|
|
15095
15564
|
|
|
15096
15565
|
|
|
15097
|
-
Note: This command assumes a complete task breakdown exists in
|
|
15566
|
+
Note: This command assumes a complete task breakdown exists in Kanban. If tasks are incomplete or missing, suggest running \`/tasks\` first to regenerate the task list.
|
|
15098
15567
|
|
|
15099
15568
|
|
|
15100
15569
|
---
|
|
@@ -16753,8 +17222,10 @@ ${variables.EDITOR ? `using ${variables.EDITOR} as primary AI subagent` : ""}
|
|
|
16753
17222
|
getDefaultModelForSubagent(subagent) {
|
|
16754
17223
|
const modelDefaults = {
|
|
16755
17224
|
claude: ":sonnet",
|
|
16756
|
-
codex: "
|
|
16757
|
-
|
|
17225
|
+
codex: ":codex",
|
|
17226
|
+
// Expands to gpt-5.3-codex in codex.py
|
|
17227
|
+
gemini: ":pro",
|
|
17228
|
+
// Expands to gemini-2.5-pro in gemini.py
|
|
16758
17229
|
cursor: "auto"
|
|
16759
17230
|
};
|
|
16760
17231
|
return modelDefaults[subagent] || modelDefaults.claude;
|
|
@@ -19104,8 +19575,8 @@ async function compactConfigFile(filePath, options = {}) {
|
|
|
19104
19575
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
19105
19576
|
const ext = path3.extname(filePath);
|
|
19106
19577
|
const basename11 = path3.basename(filePath, ext);
|
|
19107
|
-
const
|
|
19108
|
-
backupPath = path3.join(
|
|
19578
|
+
const dirname14 = path3.dirname(filePath);
|
|
19579
|
+
backupPath = path3.join(dirname14, `${basename11}.backup.${timestamp}${ext}`);
|
|
19109
19580
|
await fs3.writeFile(backupPath, originalContent, "utf-8");
|
|
19110
19581
|
}
|
|
19111
19582
|
const compactionAnalysis = analyzeMarkdownStructure(originalContent);
|
|
@@ -21571,7 +22042,7 @@ var LogViewer = ({
|
|
|
21571
22042
|
init_types();
|
|
21572
22043
|
async function exportLogs(logger2, filepath, options) {
|
|
21573
22044
|
try {
|
|
21574
|
-
const
|
|
22045
|
+
const fs24 = await import('fs-extra');
|
|
21575
22046
|
let entries = logger2.getRecentEntries(options.tail || 1e3);
|
|
21576
22047
|
if (options.level) {
|
|
21577
22048
|
const level = LogLevel[options.level.toUpperCase()];
|
|
@@ -21601,7 +22072,7 @@ async function exportLogs(logger2, filepath, options) {
|
|
|
21601
22072
|
},
|
|
21602
22073
|
entries
|
|
21603
22074
|
};
|
|
21604
|
-
await
|
|
22075
|
+
await fs24.writeFile(filepath, JSON.stringify(exportData, null, 2));
|
|
21605
22076
|
console.log(chalk15.green(`\u2705 Exported ${entries.length} log entries to: ${filepath}`));
|
|
21606
22077
|
} catch (error) {
|
|
21607
22078
|
console.error(chalk15.red(`\u274C Failed to export logs: ${error}`));
|
|
@@ -23491,6 +23962,125 @@ Location: ${ServiceInstaller.getServicesDir()}`));
|
|
|
23491
23962
|
return servicesCmd;
|
|
23492
23963
|
}
|
|
23493
23964
|
|
|
23965
|
+
// src/cli/commands/skills.ts
|
|
23966
|
+
init_version();
|
|
23967
|
+
init_skill_installer();
|
|
23968
|
+
function createSkillsCommand() {
|
|
23969
|
+
const skillsCmd = new Command("skills").description("Manage agent skill files").addHelpText("after", `
|
|
23970
|
+
Examples:
|
|
23971
|
+
$ juno-code skills install Install skill files to project directories
|
|
23972
|
+
$ juno-code skills install --force Force reinstall all skill files
|
|
23973
|
+
$ juno-code skills list List skill groups and their files
|
|
23974
|
+
$ juno-code skills status Check installation status
|
|
23975
|
+
|
|
23976
|
+
Skill files are copied from the juno-code package into the project:
|
|
23977
|
+
- Codex skills -> .agents/skills/
|
|
23978
|
+
- Claude skills -> .claude/skills/
|
|
23979
|
+
|
|
23980
|
+
Skills are installed for ALL agents regardless of which subagent is selected.
|
|
23981
|
+
Existing files in the destination directories are preserved.
|
|
23982
|
+
`);
|
|
23983
|
+
skillsCmd.command("install").description("Install skill files to project directories").option("-f, --force", "Force reinstall even if files are up-to-date").action(async (options) => {
|
|
23984
|
+
try {
|
|
23985
|
+
const projectDir = process.cwd();
|
|
23986
|
+
if (!options.force) {
|
|
23987
|
+
const needsUpdate = await SkillInstaller.needsUpdate(projectDir);
|
|
23988
|
+
if (!needsUpdate) {
|
|
23989
|
+
console.log(chalk15.yellow("\u26A0 All skill files are up-to-date"));
|
|
23990
|
+
console.log(chalk15.dim(" Use --force to reinstall"));
|
|
23991
|
+
return;
|
|
23992
|
+
}
|
|
23993
|
+
}
|
|
23994
|
+
console.log(chalk15.blue("Installing skill files..."));
|
|
23995
|
+
const installed = await SkillInstaller.install(projectDir, false, options.force);
|
|
23996
|
+
if (installed) {
|
|
23997
|
+
console.log(chalk15.green("\n\u2713 Skill files installed successfully"));
|
|
23998
|
+
} else {
|
|
23999
|
+
console.log(chalk15.yellow("\u26A0 No skill files to install (templates may be empty)"));
|
|
24000
|
+
}
|
|
24001
|
+
} catch (error) {
|
|
24002
|
+
console.error(chalk15.red("\u2717 Installation failed:"));
|
|
24003
|
+
console.error(chalk15.red(error instanceof Error ? error.message : String(error)));
|
|
24004
|
+
process.exit(1);
|
|
24005
|
+
}
|
|
24006
|
+
});
|
|
24007
|
+
skillsCmd.command("list").alias("ls").description("List skill groups and their files").action(async () => {
|
|
24008
|
+
try {
|
|
24009
|
+
const projectDir = process.cwd();
|
|
24010
|
+
const groups = await SkillInstaller.listSkillGroups(projectDir);
|
|
24011
|
+
if (groups.length === 0) {
|
|
24012
|
+
console.log(chalk15.yellow("\u26A0 No skill groups configured"));
|
|
24013
|
+
return;
|
|
24014
|
+
}
|
|
24015
|
+
let hasAnyFiles = false;
|
|
24016
|
+
for (const group of groups) {
|
|
24017
|
+
console.log(chalk15.blue.bold(`
|
|
24018
|
+
${group.name} skills -> ${group.destDir}/`));
|
|
24019
|
+
if (group.files.length === 0) {
|
|
24020
|
+
console.log(chalk15.dim(" (no skill files bundled yet)"));
|
|
24021
|
+
continue;
|
|
24022
|
+
}
|
|
24023
|
+
hasAnyFiles = true;
|
|
24024
|
+
for (const file of group.files) {
|
|
24025
|
+
const statusIcon = !file.installed ? chalk15.red("\u2717") : file.upToDate ? chalk15.green("\u2713") : chalk15.yellow("\u21BB");
|
|
24026
|
+
const statusLabel = !file.installed ? chalk15.dim("not installed") : file.upToDate ? chalk15.dim("up-to-date") : chalk15.yellow("outdated");
|
|
24027
|
+
console.log(` ${statusIcon} ${file.name} ${statusLabel}`);
|
|
24028
|
+
}
|
|
24029
|
+
}
|
|
24030
|
+
if (!hasAnyFiles) {
|
|
24031
|
+
console.log(chalk15.dim("\nNo skill files bundled yet. Add files to src/templates/skills/<agent>/ to bundle skills."));
|
|
24032
|
+
}
|
|
24033
|
+
} catch (error) {
|
|
24034
|
+
console.error(chalk15.red("\u2717 Failed to list skills:"));
|
|
24035
|
+
console.error(chalk15.red(error instanceof Error ? error.message : String(error)));
|
|
24036
|
+
process.exit(1);
|
|
24037
|
+
}
|
|
24038
|
+
});
|
|
24039
|
+
skillsCmd.command("status").description("Check skill installation status").action(async () => {
|
|
24040
|
+
try {
|
|
24041
|
+
const projectDir = process.cwd();
|
|
24042
|
+
const groups = await SkillInstaller.listSkillGroups(projectDir);
|
|
24043
|
+
const skillGroups = SkillInstaller.getSkillGroups();
|
|
24044
|
+
console.log(chalk15.blue("Skills Status:\n"));
|
|
24045
|
+
console.log(chalk15.dim(" Skill groups:"));
|
|
24046
|
+
for (const sg of skillGroups) {
|
|
24047
|
+
console.log(chalk15.dim(` ${sg.name} -> ${sg.destDir}/`));
|
|
24048
|
+
}
|
|
24049
|
+
const needsUpdate = await SkillInstaller.needsUpdate(projectDir);
|
|
24050
|
+
console.log(`
|
|
24051
|
+
${needsUpdate ? chalk15.yellow("\u26A0 Updates available") : chalk15.green("\u2713 All skills up-to-date")}`);
|
|
24052
|
+
let totalFiles = 0;
|
|
24053
|
+
let installedFiles = 0;
|
|
24054
|
+
let outdatedFiles = 0;
|
|
24055
|
+
for (const group of groups) {
|
|
24056
|
+
for (const file of group.files) {
|
|
24057
|
+
totalFiles++;
|
|
24058
|
+
if (file.installed) {
|
|
24059
|
+
installedFiles++;
|
|
24060
|
+
if (!file.upToDate) {
|
|
24061
|
+
outdatedFiles++;
|
|
24062
|
+
}
|
|
24063
|
+
}
|
|
24064
|
+
}
|
|
24065
|
+
}
|
|
24066
|
+
if (totalFiles > 0) {
|
|
24067
|
+
console.log(chalk15.dim(`
|
|
24068
|
+
Files: ${installedFiles}/${totalFiles} installed, ${outdatedFiles} outdated`));
|
|
24069
|
+
} else {
|
|
24070
|
+
console.log(chalk15.dim("\n No skill files bundled yet"));
|
|
24071
|
+
}
|
|
24072
|
+
if (needsUpdate) {
|
|
24073
|
+
console.log(chalk15.dim("\n Run: juno-code skills install"));
|
|
24074
|
+
}
|
|
24075
|
+
} catch (error) {
|
|
24076
|
+
console.error(chalk15.red("\u2717 Failed to check status:"));
|
|
24077
|
+
console.error(chalk15.red(error instanceof Error ? error.message : String(error)));
|
|
24078
|
+
process.exit(1);
|
|
24079
|
+
}
|
|
24080
|
+
});
|
|
24081
|
+
return skillsCmd;
|
|
24082
|
+
}
|
|
24083
|
+
|
|
23494
24084
|
// src/cli/commands/completion.ts
|
|
23495
24085
|
init_version();
|
|
23496
24086
|
|
|
@@ -24729,7 +25319,7 @@ function handleCLIError(error, verbose = false) {
|
|
|
24729
25319
|
process.exit(EXIT_CODES.UNEXPECTED_ERROR);
|
|
24730
25320
|
}
|
|
24731
25321
|
function setupGlobalOptions(program) {
|
|
24732
|
-
program.option("-v, --verbose", "Enable verbose output with detailed progress").option("-q, --quiet", "Disable rich formatting, use plain text").option("-c, --config <path>", "Configuration file path (.json, .toml, pyproject.toml)").option("-l, --log-file <path>", "Log file path (auto-generated if not specified)").option("--no-color", "Disable colored output").option("--log-level <level>", "Log level for output (error, warn, info, debug, trace)", "info").option("-s, --subagent <name>", "Subagent to use (claude, cursor, codex, gemini)").option("-b, --backend <type>", "Backend to use (mcp, shell) - default: shell").option("-m, --model <name>", "Model to use (subagent-specific)").option("--agents <config>", "Agents configuration (forwarded to shell backend, ignored for MCP)").option("--tools <tools...>", 'Specify the list of available tools from the built-in set (only works with --print mode). Use "" to disable all tools, "default" to use all tools, or specify tool names (e.g. "Bash,Edit,Read"). Passed to shell backend, ignored for MCP.').option("--allowed-tools <tools...>", 'Permission-based filtering of specific tool instances (e.g. "Bash(git:*) Edit"). Default when not specified: Task, Bash, Glob, Grep, ExitPlanMode, Read, Edit, Write, NotebookEdit, WebFetch, TodoWrite, WebSearch, BashOutput, KillShell, Skill, SlashCommand, EnterPlanMode. Passed to shell backend, ignored for MCP.').option("--disallowed-tools <tools...>", "Disallowed tools for Claude (passed to shell backend, ignored for MCP). By default, no tools are disallowed").option("--append-allowed-tools <tools...>", "Append tools to the default allowed-tools list (mutually exclusive with --allowed-tools). Passed to shell backend, ignored for MCP.").option("--mcp-timeout <number>", "MCP server timeout in milliseconds", parseInt).option("--enable-feedback", "Enable interactive feedback mode (F+Enter to enter, Q+Enter to submit)").option("-r, --resume <sessionId>", "Resume a conversation by session ID (shell backend only)").option("--continue", "Continue the most recent conversation (shell backend only)").option("--til-completion", "Run juno-code in a loop until all kanban tasks are complete (aliases: --until-completion, --run-until-completion, --till-complete)").option("--until-completion", "Alias for --til-completion").addOption(new Option("--run-until-completion", "Alias for --til-completion").hideHelp()).addOption(new Option("--till-complete", "Alias for --til-completion").hideHelp()).option("--pre-run-hook <hooks...>", "Execute named hooks from .juno_task/config.json before each iteration (only with --til-completion)").option("--force-update", "Force update scripts, services, and Python dependencies (bypasses 24-hour cache)").option("--on-hourly-limit <behavior>", 'Behavior when Claude hourly quota limit is reached: "wait" to sleep until reset, "raise" to exit immediately (default: raise)');
|
|
25322
|
+
program.option("-v, --verbose", "Enable verbose output with detailed progress").option("-q, --quiet", "Disable rich formatting, use plain text").option("-c, --config <path>", "Configuration file path (.json, .toml, pyproject.toml)").option("-l, --log-file <path>", "Log file path (auto-generated if not specified)").option("--no-color", "Disable colored output").option("--log-level <level>", "Log level for output (error, warn, info, debug, trace)", "info").option("-s, --subagent <name>", "Subagent to use (claude, cursor, codex, gemini)").option("-b, --backend <type>", "Backend to use (mcp, shell) - default: shell").option("-m, --model <name>", "Model to use (subagent-specific)").option("--agents <config>", "Agents configuration (forwarded to shell backend, ignored for MCP)").option("--tools <tools...>", 'Specify the list of available tools from the built-in set (only works with --print mode). Use "" to disable all tools, "default" to use all tools, or specify tool names (e.g. "Bash,Edit,Read"). Passed to shell backend, ignored for MCP.').option("--allowed-tools <tools...>", 'Permission-based filtering of specific tool instances (e.g. "Bash(git:*) Edit"). Default when not specified: Task, Bash, Glob, Grep, ExitPlanMode, Read, Edit, Write, NotebookEdit, WebFetch, TodoWrite, WebSearch, BashOutput, KillShell, Skill, SlashCommand, EnterPlanMode. Passed to shell backend, ignored for MCP.').option("--disallowed-tools <tools...>", "Disallowed tools for Claude (passed to shell backend, ignored for MCP). By default, no tools are disallowed").option("--append-allowed-tools <tools...>", "Append tools to the default allowed-tools list (mutually exclusive with --allowed-tools). Passed to shell backend, ignored for MCP.").option("--mcp-timeout <number>", "MCP server timeout in milliseconds", parseInt).option("--enable-feedback", "Enable interactive feedback mode (F+Enter to enter, Q+Enter to submit)").option("-r, --resume <sessionId>", "Resume a conversation by session ID (shell backend only)").option("--continue", "Continue the most recent conversation (shell backend only)").option("--til-completion", "Run juno-code in a loop until all kanban tasks are complete (aliases: --until-completion, --run-until-completion, --till-complete)").option("--until-completion", "Alias for --til-completion").addOption(new Option("--run-until-completion", "Alias for --til-completion").hideHelp()).addOption(new Option("--till-complete", "Alias for --til-completion").hideHelp()).option("--pre-run-hook <hooks...>", "Execute named hooks from .juno_task/config.json before each iteration (only with --til-completion)").option("--stale-threshold <n>", "Number of stale iterations before exiting (default: 3). Set to 0 to disable. (only with --til-completion)", parseInt).option("--no-stale-check", "Disable stale iteration detection (alias for --stale-threshold 0). (only with --til-completion)").option("--force-update", "Force update scripts, services, and Python dependencies (bypasses 24-hour cache)").option("--on-hourly-limit <behavior>", 'Behavior when Claude hourly quota limit is reached: "wait" to sleep until reset, "raise" to exit immediately (default: raise)');
|
|
24733
25323
|
program.exitOverride((err) => {
|
|
24734
25324
|
if (err.code === "commander.helpDisplayed") {
|
|
24735
25325
|
process.exit(0);
|
|
@@ -24756,10 +25346,10 @@ function setupMainCommand(program) {
|
|
|
24756
25346
|
const allOptions2 = { ...definedGlobalOptions, ...options };
|
|
24757
25347
|
if (allOptions2.tilCompletion || allOptions2.untilCompletion || allOptions2.runUntilCompletion || allOptions2.tillComplete) {
|
|
24758
25348
|
const { spawn: spawn4 } = await import('child_process');
|
|
24759
|
-
const
|
|
24760
|
-
const
|
|
24761
|
-
const scriptPath =
|
|
24762
|
-
if (!await
|
|
25349
|
+
const path25 = await import('path');
|
|
25350
|
+
const fs24 = await import('fs-extra');
|
|
25351
|
+
const scriptPath = path25.join(process.cwd(), ".juno_task", "scripts", "run_until_completion.sh");
|
|
25352
|
+
if (!await fs24.pathExists(scriptPath)) {
|
|
24763
25353
|
console.error(chalk15.red.bold("\n\u274C Error: run_until_completion.sh not found"));
|
|
24764
25354
|
console.error(chalk15.red(` Expected location: ${scriptPath}`));
|
|
24765
25355
|
console.error(chalk15.yellow('\n\u{1F4A1} Suggestion: Run "juno-code init" to initialize the project'));
|
|
@@ -24810,11 +25400,11 @@ function setupMainCommand(program) {
|
|
|
24810
25400
|
return;
|
|
24811
25401
|
}
|
|
24812
25402
|
if (!globalOptions.subagent && !options.prompt && !options.interactive && !options.interactivePrompt) {
|
|
24813
|
-
const
|
|
24814
|
-
const
|
|
25403
|
+
const fs24 = await import('fs-extra');
|
|
25404
|
+
const path25 = await import('path');
|
|
24815
25405
|
const cwd2 = process.cwd();
|
|
24816
|
-
const junoTaskDir =
|
|
24817
|
-
if (await
|
|
25406
|
+
const junoTaskDir = path25.join(cwd2, ".juno_task");
|
|
25407
|
+
if (await fs24.pathExists(junoTaskDir)) {
|
|
24818
25408
|
console.log(chalk15.blue.bold("\u{1F3AF} Juno Code - Auto-detected Initialized Project\n"));
|
|
24819
25409
|
try {
|
|
24820
25410
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
@@ -24831,12 +25421,12 @@ function setupMainCommand(program) {
|
|
|
24831
25421
|
allOptions2.subagent = config.defaultSubagent;
|
|
24832
25422
|
console.log(chalk15.gray(`\u{1F916} Using configured subagent: ${chalk15.cyan(config.defaultSubagent)}`));
|
|
24833
25423
|
}
|
|
24834
|
-
const promptFile =
|
|
24835
|
-
if (!allOptions2.prompt && await
|
|
25424
|
+
const promptFile = path25.join(junoTaskDir, "prompt.md");
|
|
25425
|
+
if (!allOptions2.prompt && await fs24.pathExists(promptFile)) {
|
|
24836
25426
|
allOptions2.prompt = promptFile;
|
|
24837
25427
|
console.log(chalk15.gray(`\u{1F4C4} Using default prompt: ${chalk15.cyan(".juno_task/prompt.md")}`));
|
|
24838
25428
|
}
|
|
24839
|
-
if (allOptions2.subagent && (allOptions2.prompt || await
|
|
25429
|
+
if (allOptions2.subagent && (allOptions2.prompt || await fs24.pathExists(promptFile))) {
|
|
24840
25430
|
console.log(chalk15.green("\u2713 Auto-detected project configuration\n"));
|
|
24841
25431
|
const { mainCommandHandler: mainCommandHandler3 } = await Promise.resolve().then(() => (init_main(), main_exports));
|
|
24842
25432
|
await mainCommandHandler3([], allOptions2, command);
|
|
@@ -25018,6 +25608,23 @@ async function main() {
|
|
|
25018
25608
|
console.error("[DEBUG] Script auto-update failed:", error instanceof Error ? error.message : String(error));
|
|
25019
25609
|
}
|
|
25020
25610
|
}
|
|
25611
|
+
try {
|
|
25612
|
+
const { SkillInstaller: SkillInstaller2 } = await Promise.resolve().then(() => (init_skill_installer(), skill_installer_exports));
|
|
25613
|
+
if (isForceUpdate) {
|
|
25614
|
+
console.log(chalk15.blue("\u{1F504} Force updating agent skill files..."));
|
|
25615
|
+
await SkillInstaller2.autoUpdate(process.cwd(), true);
|
|
25616
|
+
console.log(chalk15.green("\u2713 Agent skill files updated"));
|
|
25617
|
+
} else {
|
|
25618
|
+
const updated = await SkillInstaller2.autoUpdate(process.cwd());
|
|
25619
|
+
if (updated && (process.argv.includes("--verbose") || process.argv.includes("-v") || process.env.JUNO_CODE_DEBUG === "1")) {
|
|
25620
|
+
console.error("[DEBUG] Agent skill files auto-updated");
|
|
25621
|
+
}
|
|
25622
|
+
}
|
|
25623
|
+
} catch (error) {
|
|
25624
|
+
if (process.env.JUNO_CODE_DEBUG === "1") {
|
|
25625
|
+
console.error("[DEBUG] Skill auto-update failed:", error instanceof Error ? error.message : String(error));
|
|
25626
|
+
}
|
|
25627
|
+
}
|
|
25021
25628
|
program.name("juno-code").description("TypeScript implementation of juno-code CLI tool for AI subagent orchestration").version(VERSION, "-V, --version", "Display version information").helpOption("-h, --help", "Display help information");
|
|
25022
25629
|
setupGlobalOptions(program);
|
|
25023
25630
|
const isVerbose = process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
@@ -25025,10 +25632,10 @@ async function main() {
|
|
|
25025
25632
|
const isHelpOrVersion = process.argv.includes("--help") || process.argv.includes("-h") || process.argv.includes("--version") || process.argv.includes("-V");
|
|
25026
25633
|
const hasNoArguments = process.argv.length <= 2;
|
|
25027
25634
|
const isInitCommand = process.argv.includes("init");
|
|
25028
|
-
const
|
|
25029
|
-
const
|
|
25030
|
-
const junoTaskDir =
|
|
25031
|
-
const isInitialized = await
|
|
25635
|
+
const fs24 = await import('fs-extra');
|
|
25636
|
+
const path25 = await import('path');
|
|
25637
|
+
const junoTaskDir = path25.join(process.cwd(), ".juno_task");
|
|
25638
|
+
const isInitialized = await fs24.pathExists(junoTaskDir);
|
|
25032
25639
|
if (!isHelpOrVersion && !hasNoArguments && !isInitCommand && isInitialized) {
|
|
25033
25640
|
try {
|
|
25034
25641
|
const { validateStartupConfigs: validateStartupConfigs2 } = await Promise.resolve().then(() => (init_startup_validation(), startup_validation_exports));
|
|
@@ -25055,6 +25662,7 @@ async function main() {
|
|
|
25055
25662
|
configureHelpCommand(program);
|
|
25056
25663
|
setupConfigCommand(program);
|
|
25057
25664
|
program.addCommand(createServicesCommand());
|
|
25665
|
+
program.addCommand(createSkillsCommand());
|
|
25058
25666
|
setupCompletion(program);
|
|
25059
25667
|
setupAliases(program);
|
|
25060
25668
|
setupMainCommand(program);
|