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.js
CHANGED
|
@@ -178,8 +178,8 @@ var init_types = __esm({
|
|
|
178
178
|
};
|
|
179
179
|
FileSystemError = class extends CLIError {
|
|
180
180
|
code = "FILESYSTEM_ERROR";
|
|
181
|
-
constructor(message,
|
|
182
|
-
super(
|
|
181
|
+
constructor(message, path25) {
|
|
182
|
+
super(path25 ? `${message}: ${path25}` : message);
|
|
183
183
|
this.suggestions = [
|
|
184
184
|
"Check file/directory permissions",
|
|
185
185
|
"Verify path exists and is accessible",
|
|
@@ -1854,8 +1854,8 @@ ${helpText}
|
|
|
1854
1854
|
}
|
|
1855
1855
|
}
|
|
1856
1856
|
if (options.cwd) {
|
|
1857
|
-
const
|
|
1858
|
-
if (!await
|
|
1857
|
+
const fs24 = await import('fs-extra');
|
|
1858
|
+
if (!await fs24.pathExists(options.cwd)) {
|
|
1859
1859
|
throw new ValidationError(
|
|
1860
1860
|
`Working directory does not exist: ${options.cwd}`,
|
|
1861
1861
|
["Verify the path exists", "Use absolute paths to avoid ambiguity"]
|
|
@@ -3682,31 +3682,103 @@ function parseResetTime(message) {
|
|
|
3682
3682
|
}
|
|
3683
3683
|
return { resetTime, timezone };
|
|
3684
3684
|
}
|
|
3685
|
+
function parseCodexResetTime(message) {
|
|
3686
|
+
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;
|
|
3687
|
+
const match = message.match(resetPattern);
|
|
3688
|
+
if (!match) {
|
|
3689
|
+
return null;
|
|
3690
|
+
}
|
|
3691
|
+
const monthStr = match[1];
|
|
3692
|
+
const day = parseInt(match[2], 10);
|
|
3693
|
+
const year = parseInt(match[3], 10);
|
|
3694
|
+
let hours = parseInt(match[4], 10);
|
|
3695
|
+
const minutes = parseInt(match[5], 10);
|
|
3696
|
+
const ampm = match[6].toUpperCase();
|
|
3697
|
+
const MONTH_MAP = {
|
|
3698
|
+
"jan": 0,
|
|
3699
|
+
"january": 0,
|
|
3700
|
+
"feb": 1,
|
|
3701
|
+
"february": 1,
|
|
3702
|
+
"mar": 2,
|
|
3703
|
+
"march": 2,
|
|
3704
|
+
"apr": 3,
|
|
3705
|
+
"april": 3,
|
|
3706
|
+
"may": 4,
|
|
3707
|
+
"jun": 5,
|
|
3708
|
+
"june": 5,
|
|
3709
|
+
"jul": 6,
|
|
3710
|
+
"july": 6,
|
|
3711
|
+
"aug": 7,
|
|
3712
|
+
"august": 7,
|
|
3713
|
+
"sep": 8,
|
|
3714
|
+
"september": 8,
|
|
3715
|
+
"oct": 9,
|
|
3716
|
+
"october": 9,
|
|
3717
|
+
"nov": 10,
|
|
3718
|
+
"november": 10,
|
|
3719
|
+
"dec": 11,
|
|
3720
|
+
"december": 11
|
|
3721
|
+
};
|
|
3722
|
+
const month = MONTH_MAP[monthStr.toLowerCase()];
|
|
3723
|
+
if (month === void 0) {
|
|
3724
|
+
return null;
|
|
3725
|
+
}
|
|
3726
|
+
if (ampm === "PM" && hours !== 12) {
|
|
3727
|
+
hours += 12;
|
|
3728
|
+
} else if (ampm === "AM" && hours === 12) {
|
|
3729
|
+
hours = 0;
|
|
3730
|
+
}
|
|
3731
|
+
const resetTime = new Date(year, month, day, hours, minutes, 0, 0);
|
|
3732
|
+
const now = /* @__PURE__ */ new Date();
|
|
3733
|
+
if (resetTime.getTime() <= now.getTime()) {
|
|
3734
|
+
resetTime.setTime(resetTime.getTime() + 24 * 60 * 60 * 1e3);
|
|
3735
|
+
}
|
|
3736
|
+
return { resetTime };
|
|
3737
|
+
}
|
|
3685
3738
|
function detectQuotaLimit(message) {
|
|
3686
3739
|
if (!message || typeof message !== "string") {
|
|
3687
3740
|
return { detected: false };
|
|
3688
3741
|
}
|
|
3689
|
-
const
|
|
3690
|
-
|
|
3742
|
+
const claudePattern = /you'?ve hit your limit/i;
|
|
3743
|
+
const codexPattern = /you'?ve hit your usage limit/i;
|
|
3744
|
+
const isClaudeQuota = claudePattern.test(message) && !codexPattern.test(message);
|
|
3745
|
+
const isCodexQuota = codexPattern.test(message);
|
|
3746
|
+
if (!isClaudeQuota && !isCodexQuota) {
|
|
3691
3747
|
return { detected: false };
|
|
3692
3748
|
}
|
|
3693
|
-
const
|
|
3694
|
-
|
|
3749
|
+
const source = isCodexQuota ? "codex" : "claude";
|
|
3750
|
+
const parsedClaude = parseResetTime(message);
|
|
3751
|
+
if (parsedClaude) {
|
|
3695
3752
|
const now = /* @__PURE__ */ new Date();
|
|
3696
|
-
const sleepDurationMs = Math.max(0,
|
|
3753
|
+
const sleepDurationMs = Math.max(0, parsedClaude.resetTime.getTime() - now.getTime());
|
|
3697
3754
|
return {
|
|
3698
3755
|
detected: true,
|
|
3699
|
-
resetTime:
|
|
3756
|
+
resetTime: parsedClaude.resetTime,
|
|
3700
3757
|
sleepDurationMs,
|
|
3701
|
-
timezone:
|
|
3702
|
-
originalMessage: message
|
|
3758
|
+
timezone: parsedClaude.timezone,
|
|
3759
|
+
originalMessage: message,
|
|
3760
|
+
source
|
|
3761
|
+
};
|
|
3762
|
+
}
|
|
3763
|
+
const parsedCodex = parseCodexResetTime(message);
|
|
3764
|
+
if (parsedCodex) {
|
|
3765
|
+
const now = /* @__PURE__ */ new Date();
|
|
3766
|
+
const sleepDurationMs = Math.max(0, parsedCodex.resetTime.getTime() - now.getTime());
|
|
3767
|
+
return {
|
|
3768
|
+
detected: true,
|
|
3769
|
+
resetTime: parsedCodex.resetTime,
|
|
3770
|
+
sleepDurationMs,
|
|
3771
|
+
timezone: "local",
|
|
3772
|
+
originalMessage: message,
|
|
3773
|
+
source
|
|
3703
3774
|
};
|
|
3704
3775
|
}
|
|
3705
3776
|
return {
|
|
3706
3777
|
detected: true,
|
|
3707
3778
|
sleepDurationMs: 5 * 60 * 1e3,
|
|
3708
3779
|
// 5 minutes default
|
|
3709
|
-
originalMessage: message
|
|
3780
|
+
originalMessage: message,
|
|
3781
|
+
source
|
|
3710
3782
|
};
|
|
3711
3783
|
}
|
|
3712
3784
|
function formatDuration(ms) {
|
|
@@ -4231,8 +4303,68 @@ var init_shell_backend = __esm({
|
|
|
4231
4303
|
metadata
|
|
4232
4304
|
};
|
|
4233
4305
|
}
|
|
4306
|
+
if (subagentType === "codex") {
|
|
4307
|
+
const codexQuotaMessage = this.extractCodexQuotaMessage(result.output, result.error);
|
|
4308
|
+
if (codexQuotaMessage) {
|
|
4309
|
+
const quotaLimitInfo = detectQuotaLimit(codexQuotaMessage);
|
|
4310
|
+
if (quotaLimitInfo.detected) {
|
|
4311
|
+
const metadata = {
|
|
4312
|
+
structuredOutput: true,
|
|
4313
|
+
contentType: "application/json",
|
|
4314
|
+
rawOutput: result.output,
|
|
4315
|
+
quotaLimitInfo
|
|
4316
|
+
};
|
|
4317
|
+
const structuredPayload = {
|
|
4318
|
+
type: "result",
|
|
4319
|
+
subtype: "error",
|
|
4320
|
+
is_error: true,
|
|
4321
|
+
result: codexQuotaMessage,
|
|
4322
|
+
error: codexQuotaMessage,
|
|
4323
|
+
exit_code: result.exitCode,
|
|
4324
|
+
duration_ms: result.duration,
|
|
4325
|
+
quota_limit: quotaLimitInfo
|
|
4326
|
+
};
|
|
4327
|
+
return {
|
|
4328
|
+
content: JSON.stringify(structuredPayload),
|
|
4329
|
+
metadata
|
|
4330
|
+
};
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4234
4334
|
return { content: result.output, metadata: result.metadata };
|
|
4235
4335
|
}
|
|
4336
|
+
/**
|
|
4337
|
+
* Extract quota limit message from Codex stream output
|
|
4338
|
+
* Codex outputs JSON events like:
|
|
4339
|
+
* {"type": "error", "message": "You've hit your usage limit..."}
|
|
4340
|
+
* {"type": "turn.failed", "error": {"message": "You've hit your usage limit..."}}
|
|
4341
|
+
*/
|
|
4342
|
+
extractCodexQuotaMessage(output, stderr) {
|
|
4343
|
+
const sources = [output, stderr].filter(Boolean);
|
|
4344
|
+
for (const source of sources) {
|
|
4345
|
+
const lines = source.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4346
|
+
for (const line of lines) {
|
|
4347
|
+
try {
|
|
4348
|
+
const parsed = JSON.parse(line);
|
|
4349
|
+
if (parsed?.type === "error" && parsed?.message) {
|
|
4350
|
+
if (/you'?ve hit your usage limit/i.test(parsed.message)) {
|
|
4351
|
+
return parsed.message;
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
if (parsed?.type === "turn.failed" && parsed?.error?.message) {
|
|
4355
|
+
if (/you'?ve hit your usage limit/i.test(parsed.error.message)) {
|
|
4356
|
+
return parsed.error.message;
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
} catch {
|
|
4360
|
+
if (/you'?ve hit your usage limit/i.test(line)) {
|
|
4361
|
+
return line;
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
return null;
|
|
4367
|
+
}
|
|
4236
4368
|
/**
|
|
4237
4369
|
* Extract the last valid JSON object from a script's stdout to use as a structured payload fallback.
|
|
4238
4370
|
*/
|
|
@@ -5212,8 +5344,9 @@ var init_engine = __esm({
|
|
|
5212
5344
|
hour12: true,
|
|
5213
5345
|
timeZoneName: "short"
|
|
5214
5346
|
}) : "unknown";
|
|
5347
|
+
const sourceLabel = quotaInfo.source === "codex" ? "Codex" : "Claude";
|
|
5215
5348
|
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`);
|
|
5216
|
-
engineLogger.info(`\u2551
|
|
5349
|
+
engineLogger.info(`\u2551 ${sourceLabel} Quota Limit Reached${" ".repeat(44 - sourceLabel.length - " Quota Limit Reached".length)}\u2551`);
|
|
5217
5350
|
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`);
|
|
5218
5351
|
engineLogger.info(`\u2551 Quota resets at: ${resetTimeStr2.padEnd(44)}\u2551`);
|
|
5219
5352
|
engineLogger.info(`\u2551 Behavior: raise (exit immediately) \u2551`);
|
|
@@ -5223,7 +5356,7 @@ var init_engine = __esm({
|
|
|
5223
5356
|
engineLogger.info(`\u2551 Or in config.json: { "onHourlyLimit": "wait" } \u2551`);
|
|
5224
5357
|
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`);
|
|
5225
5358
|
this.emit("quota-limit:raise", { context, quotaInfo });
|
|
5226
|
-
throw new Error(
|
|
5359
|
+
throw new Error(`${sourceLabel} quota limit reached. Quota resets at ${resetTimeStr2}. Use --on-hourly-limit wait to auto-retry.`);
|
|
5227
5360
|
}
|
|
5228
5361
|
const waitTimeMs = quotaInfo.sleepDurationMs;
|
|
5229
5362
|
const maxWaitTimeMs = 12 * 60 * 60 * 1e3;
|
|
@@ -5239,8 +5372,9 @@ var init_engine = __esm({
|
|
|
5239
5372
|
timeZoneName: "short"
|
|
5240
5373
|
}) : "unknown";
|
|
5241
5374
|
const durationStr = formatDuration(waitTimeMs);
|
|
5375
|
+
const waitSourceLabel = quotaInfo.source === "codex" ? "Codex" : "Claude";
|
|
5242
5376
|
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`);
|
|
5243
|
-
engineLogger.info(`\u2551
|
|
5377
|
+
engineLogger.info(`\u2551 ${waitSourceLabel} Quota Limit Reached${" ".repeat(44 - waitSourceLabel.length - " Quota Limit Reached".length)}\u2551`);
|
|
5244
5378
|
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`);
|
|
5245
5379
|
engineLogger.info(`\u2551 Quota resets at: ${resetTimeStr.padEnd(44)}\u2551`);
|
|
5246
5380
|
engineLogger.info(`\u2551 Sleeping for: ${durationStr.padEnd(44)}\u2551`);
|
|
@@ -12699,12 +12833,12 @@ function safeConsoleOutput(message, options = {}) {
|
|
|
12699
12833
|
const capabilities = getTUICapabilities();
|
|
12700
12834
|
let output = message;
|
|
12701
12835
|
if (capabilities.hasColors && (color || bold)) {
|
|
12702
|
-
const
|
|
12703
|
-
if (color &&
|
|
12704
|
-
output =
|
|
12836
|
+
const chalk25 = __require("chalk");
|
|
12837
|
+
if (color && chalk25[color]) {
|
|
12838
|
+
output = chalk25[color](output);
|
|
12705
12839
|
}
|
|
12706
12840
|
if (bold) {
|
|
12707
|
-
output =
|
|
12841
|
+
output = chalk25.bold(output);
|
|
12708
12842
|
}
|
|
12709
12843
|
}
|
|
12710
12844
|
console[type](output);
|
|
@@ -13040,8 +13174,45 @@ var init_tui = __esm({
|
|
|
13040
13174
|
var main_exports = {};
|
|
13041
13175
|
__export(main_exports, {
|
|
13042
13176
|
createMainCommand: () => createMainCommand,
|
|
13177
|
+
getDefaultModelForSubagent: () => getDefaultModelForSubagent,
|
|
13178
|
+
isModelCompatibleWithSubagent: () => isModelCompatibleWithSubagent,
|
|
13043
13179
|
mainCommandHandler: () => mainCommandHandler
|
|
13044
13180
|
});
|
|
13181
|
+
function getDefaultModelForSubagent(subagent) {
|
|
13182
|
+
const modelDefaults = {
|
|
13183
|
+
claude: ":sonnet",
|
|
13184
|
+
codex: ":codex",
|
|
13185
|
+
// Expands to gpt-5.3-codex in codex.py
|
|
13186
|
+
gemini: ":pro",
|
|
13187
|
+
// Expands to gemini-2.5-pro in gemini.py
|
|
13188
|
+
cursor: "auto"
|
|
13189
|
+
};
|
|
13190
|
+
return modelDefaults[subagent] || modelDefaults.claude;
|
|
13191
|
+
}
|
|
13192
|
+
function isModelCompatibleWithSubagent(model, subagent) {
|
|
13193
|
+
if (!model.startsWith(":")) {
|
|
13194
|
+
return true;
|
|
13195
|
+
}
|
|
13196
|
+
const claudeShorthands = [":sonnet", ":haiku", ":opus"];
|
|
13197
|
+
const codexShorthands = [":codex", ":codex-mini", ":gpt-5", ":mini"];
|
|
13198
|
+
const geminiShorthands = [":pro", ":flash"];
|
|
13199
|
+
const isClaudeModel = claudeShorthands.includes(model) || model.startsWith(":claude");
|
|
13200
|
+
const isCodexModel = codexShorthands.includes(model) || model.startsWith(":gpt");
|
|
13201
|
+
const isGeminiModel = geminiShorthands.includes(model) || model.startsWith(":gemini");
|
|
13202
|
+
switch (subagent) {
|
|
13203
|
+
case "claude":
|
|
13204
|
+
return isClaudeModel || !isCodexModel && !isGeminiModel;
|
|
13205
|
+
case "codex":
|
|
13206
|
+
return isCodexModel || !isClaudeModel && !isGeminiModel;
|
|
13207
|
+
case "gemini":
|
|
13208
|
+
return isGeminiModel || !isClaudeModel && !isCodexModel;
|
|
13209
|
+
case "cursor":
|
|
13210
|
+
return true;
|
|
13211
|
+
// Cursor accepts any model
|
|
13212
|
+
default:
|
|
13213
|
+
return true;
|
|
13214
|
+
}
|
|
13215
|
+
}
|
|
13045
13216
|
async function mainCommandHandler(args, options, command) {
|
|
13046
13217
|
try {
|
|
13047
13218
|
const validSubagents = ["claude", "cursor", "codex", "gemini"];
|
|
@@ -13093,13 +13264,15 @@ async function mainCommandHandler(args, options, command) {
|
|
|
13093
13264
|
["Use -1 for unlimited iterations", "Use positive integers like 1, 5, or 10", "Example: -i 5"]
|
|
13094
13265
|
);
|
|
13095
13266
|
}
|
|
13267
|
+
const configModelIsValid = config.defaultModel && config.defaultSubagent === options.subagent && isModelCompatibleWithSubagent(config.defaultModel, options.subagent);
|
|
13268
|
+
const resolvedModel = options.model || (configModelIsValid ? config.defaultModel : void 0) || getDefaultModelForSubagent(options.subagent);
|
|
13096
13269
|
const executionRequest = createExecutionRequest({
|
|
13097
13270
|
instruction,
|
|
13098
13271
|
subagent: options.subagent,
|
|
13099
13272
|
backend: selectedBackend,
|
|
13100
13273
|
workingDirectory: config.workingDirectory,
|
|
13101
13274
|
maxIterations: options.maxIterations ?? config.defaultMaxIterations,
|
|
13102
|
-
model:
|
|
13275
|
+
model: resolvedModel,
|
|
13103
13276
|
agents: options.agents,
|
|
13104
13277
|
tools: options.tools,
|
|
13105
13278
|
allowedTools: options.allowedTools,
|
|
@@ -13621,6 +13794,289 @@ var init_main = __esm({
|
|
|
13621
13794
|
}
|
|
13622
13795
|
});
|
|
13623
13796
|
|
|
13797
|
+
// src/utils/skill-installer.ts
|
|
13798
|
+
var skill_installer_exports = {};
|
|
13799
|
+
__export(skill_installer_exports, {
|
|
13800
|
+
SkillInstaller: () => SkillInstaller
|
|
13801
|
+
});
|
|
13802
|
+
var SkillInstaller;
|
|
13803
|
+
var init_skill_installer = __esm({
|
|
13804
|
+
"src/utils/skill-installer.ts"() {
|
|
13805
|
+
init_version();
|
|
13806
|
+
SkillInstaller = class {
|
|
13807
|
+
/**
|
|
13808
|
+
* Skill groups define which template folders map to which project directories.
|
|
13809
|
+
* New agents can be added here without changing any other logic.
|
|
13810
|
+
*/
|
|
13811
|
+
static SKILL_GROUPS = [
|
|
13812
|
+
{ name: "codex", destDir: ".agents/skills" },
|
|
13813
|
+
{ name: "claude", destDir: ".claude/skills" }
|
|
13814
|
+
];
|
|
13815
|
+
/**
|
|
13816
|
+
* Get the templates skills directory from the package
|
|
13817
|
+
*/
|
|
13818
|
+
static getPackageSkillsDir() {
|
|
13819
|
+
const __dirname2 = path3__namespace.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href))));
|
|
13820
|
+
const candidates = [
|
|
13821
|
+
path3__namespace.join(__dirname2, "..", "..", "templates", "skills"),
|
|
13822
|
+
// dist (production)
|
|
13823
|
+
path3__namespace.join(__dirname2, "..", "templates", "skills")
|
|
13824
|
+
// src (development)
|
|
13825
|
+
];
|
|
13826
|
+
for (const skillsPath of candidates) {
|
|
13827
|
+
if (fs3__default.default.existsSync(skillsPath)) {
|
|
13828
|
+
return skillsPath;
|
|
13829
|
+
}
|
|
13830
|
+
}
|
|
13831
|
+
if (process.env.JUNO_CODE_DEBUG === "1") {
|
|
13832
|
+
console.error("[DEBUG] SkillInstaller: Could not find templates/skills directory");
|
|
13833
|
+
console.error("[DEBUG] Tried:", candidates);
|
|
13834
|
+
}
|
|
13835
|
+
return null;
|
|
13836
|
+
}
|
|
13837
|
+
/**
|
|
13838
|
+
* Get list of skill files in a specific skill group template directory.
|
|
13839
|
+
* Returns paths relative to the group directory.
|
|
13840
|
+
*/
|
|
13841
|
+
static async getSkillFiles(groupDir) {
|
|
13842
|
+
if (!await fs3__default.default.pathExists(groupDir)) {
|
|
13843
|
+
return [];
|
|
13844
|
+
}
|
|
13845
|
+
const files = [];
|
|
13846
|
+
const walk = async (dir, prefix) => {
|
|
13847
|
+
const entries = await fs3__default.default.readdir(dir, { withFileTypes: true });
|
|
13848
|
+
for (const entry of entries) {
|
|
13849
|
+
if (entry.name.startsWith(".") || entry.name === "__pycache__") {
|
|
13850
|
+
continue;
|
|
13851
|
+
}
|
|
13852
|
+
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
13853
|
+
if (entry.isDirectory()) {
|
|
13854
|
+
await walk(path3__namespace.join(dir, entry.name), relPath);
|
|
13855
|
+
} else {
|
|
13856
|
+
files.push(relPath);
|
|
13857
|
+
}
|
|
13858
|
+
}
|
|
13859
|
+
};
|
|
13860
|
+
await walk(groupDir, "");
|
|
13861
|
+
return files;
|
|
13862
|
+
}
|
|
13863
|
+
/**
|
|
13864
|
+
* Install skills for a single skill group.
|
|
13865
|
+
* Only copies skill files, does NOT delete or modify any other files in the destination.
|
|
13866
|
+
*
|
|
13867
|
+
* @param projectDir - The project root directory
|
|
13868
|
+
* @param group - The skill group to install
|
|
13869
|
+
* @param silent - If true, suppresses console output
|
|
13870
|
+
* @param force - If true, overwrite even if content is identical
|
|
13871
|
+
* @returns number of files installed or updated
|
|
13872
|
+
*/
|
|
13873
|
+
static async installGroup(projectDir, group, silent = true, force = false) {
|
|
13874
|
+
const debug = process.env.JUNO_CODE_DEBUG === "1";
|
|
13875
|
+
const packageSkillsDir = this.getPackageSkillsDir();
|
|
13876
|
+
if (!packageSkillsDir) {
|
|
13877
|
+
if (debug) {
|
|
13878
|
+
console.error("[DEBUG] SkillInstaller: Package skills directory not found");
|
|
13879
|
+
}
|
|
13880
|
+
return 0;
|
|
13881
|
+
}
|
|
13882
|
+
const sourceGroupDir = path3__namespace.join(packageSkillsDir, group.name);
|
|
13883
|
+
const destGroupDir = path3__namespace.join(projectDir, group.destDir);
|
|
13884
|
+
const skillFiles = await this.getSkillFiles(sourceGroupDir);
|
|
13885
|
+
if (skillFiles.length === 0) {
|
|
13886
|
+
if (debug) {
|
|
13887
|
+
console.error(`[DEBUG] SkillInstaller: No skill files found for group '${group.name}'`);
|
|
13888
|
+
}
|
|
13889
|
+
return 0;
|
|
13890
|
+
}
|
|
13891
|
+
await fs3__default.default.ensureDir(destGroupDir);
|
|
13892
|
+
let installed = 0;
|
|
13893
|
+
for (const relFile of skillFiles) {
|
|
13894
|
+
const srcPath = path3__namespace.join(sourceGroupDir, relFile);
|
|
13895
|
+
const destPath = path3__namespace.join(destGroupDir, relFile);
|
|
13896
|
+
const destParent = path3__namespace.dirname(destPath);
|
|
13897
|
+
await fs3__default.default.ensureDir(destParent);
|
|
13898
|
+
let shouldCopy = force;
|
|
13899
|
+
if (!shouldCopy) {
|
|
13900
|
+
if (!await fs3__default.default.pathExists(destPath)) {
|
|
13901
|
+
shouldCopy = true;
|
|
13902
|
+
} else {
|
|
13903
|
+
const [srcContent, destContent] = await Promise.all([
|
|
13904
|
+
fs3__default.default.readFile(srcPath, "utf-8"),
|
|
13905
|
+
fs3__default.default.readFile(destPath, "utf-8")
|
|
13906
|
+
]);
|
|
13907
|
+
if (srcContent !== destContent) {
|
|
13908
|
+
shouldCopy = true;
|
|
13909
|
+
}
|
|
13910
|
+
}
|
|
13911
|
+
}
|
|
13912
|
+
if (shouldCopy) {
|
|
13913
|
+
await fs3__default.default.copy(srcPath, destPath, { overwrite: true });
|
|
13914
|
+
if (relFile.endsWith(".sh") || relFile.endsWith(".py")) {
|
|
13915
|
+
await fs3__default.default.chmod(destPath, 493);
|
|
13916
|
+
}
|
|
13917
|
+
installed++;
|
|
13918
|
+
if (debug) {
|
|
13919
|
+
console.error(`[DEBUG] SkillInstaller: Installed ${group.name}/${relFile} -> ${destPath}`);
|
|
13920
|
+
}
|
|
13921
|
+
}
|
|
13922
|
+
}
|
|
13923
|
+
if (installed > 0 && !silent) {
|
|
13924
|
+
console.log(`\u2713 Installed ${installed} skill file(s) for ${group.name} -> ${group.destDir}`);
|
|
13925
|
+
}
|
|
13926
|
+
return installed;
|
|
13927
|
+
}
|
|
13928
|
+
/**
|
|
13929
|
+
* Install skills for all skill groups.
|
|
13930
|
+
* This copies skill files to the appropriate project directories while
|
|
13931
|
+
* preserving any existing files the user may have added.
|
|
13932
|
+
*
|
|
13933
|
+
* @param projectDir - The project root directory
|
|
13934
|
+
* @param silent - If true, suppresses console output
|
|
13935
|
+
* @param force - If true, overwrite even if content matches
|
|
13936
|
+
* @returns true if any skill files were installed or updated
|
|
13937
|
+
*/
|
|
13938
|
+
static async install(projectDir, silent = false, force = false) {
|
|
13939
|
+
const debug = process.env.JUNO_CODE_DEBUG === "1";
|
|
13940
|
+
let totalInstalled = 0;
|
|
13941
|
+
for (const group of this.SKILL_GROUPS) {
|
|
13942
|
+
try {
|
|
13943
|
+
const count = await this.installGroup(projectDir, group, silent, force);
|
|
13944
|
+
totalInstalled += count;
|
|
13945
|
+
} catch (error) {
|
|
13946
|
+
if (debug) {
|
|
13947
|
+
console.error(`[DEBUG] SkillInstaller: Error installing group '${group.name}':`, error);
|
|
13948
|
+
}
|
|
13949
|
+
if (!silent) {
|
|
13950
|
+
console.error(`\u26A0\uFE0F Failed to install skills for ${group.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
13951
|
+
}
|
|
13952
|
+
}
|
|
13953
|
+
}
|
|
13954
|
+
if (totalInstalled > 0 && !silent) {
|
|
13955
|
+
console.log(`\u2713 Total: ${totalInstalled} skill file(s) installed/updated`);
|
|
13956
|
+
}
|
|
13957
|
+
return totalInstalled > 0;
|
|
13958
|
+
}
|
|
13959
|
+
/**
|
|
13960
|
+
* Auto-update skills on CLI startup.
|
|
13961
|
+
* Only installs/updates if the project is initialized (.juno_task exists).
|
|
13962
|
+
* Silently does nothing if no skill files are bundled or project is not initialized.
|
|
13963
|
+
*
|
|
13964
|
+
* @param projectDir - The project root directory
|
|
13965
|
+
* @param force - If true, force reinstall all skills
|
|
13966
|
+
* @returns true if any updates occurred
|
|
13967
|
+
*/
|
|
13968
|
+
static async autoUpdate(projectDir, force = false) {
|
|
13969
|
+
try {
|
|
13970
|
+
const debug = process.env.JUNO_CODE_DEBUG === "1";
|
|
13971
|
+
const junoTaskDir = path3__namespace.join(projectDir, ".juno_task");
|
|
13972
|
+
if (!await fs3__default.default.pathExists(junoTaskDir)) {
|
|
13973
|
+
return false;
|
|
13974
|
+
}
|
|
13975
|
+
if (debug) {
|
|
13976
|
+
console.error(`[DEBUG] SkillInstaller: Auto-updating skills (force=${force})`);
|
|
13977
|
+
}
|
|
13978
|
+
const updated = await this.install(projectDir, true, force);
|
|
13979
|
+
if (updated && debug) {
|
|
13980
|
+
console.error("[DEBUG] SkillInstaller: Skills auto-updated successfully");
|
|
13981
|
+
}
|
|
13982
|
+
return updated;
|
|
13983
|
+
} catch (error) {
|
|
13984
|
+
if (process.env.JUNO_CODE_DEBUG === "1") {
|
|
13985
|
+
console.error("[DEBUG] SkillInstaller: autoUpdate error:", error instanceof Error ? error.message : String(error));
|
|
13986
|
+
}
|
|
13987
|
+
return false;
|
|
13988
|
+
}
|
|
13989
|
+
}
|
|
13990
|
+
/**
|
|
13991
|
+
* Check if any skills need to be installed or updated.
|
|
13992
|
+
*
|
|
13993
|
+
* @param projectDir - The project root directory
|
|
13994
|
+
* @returns true if any skills are missing or outdated
|
|
13995
|
+
*/
|
|
13996
|
+
static async needsUpdate(projectDir) {
|
|
13997
|
+
try {
|
|
13998
|
+
const junoTaskDir = path3__namespace.join(projectDir, ".juno_task");
|
|
13999
|
+
if (!await fs3__default.default.pathExists(junoTaskDir)) {
|
|
14000
|
+
return false;
|
|
14001
|
+
}
|
|
14002
|
+
const packageSkillsDir = this.getPackageSkillsDir();
|
|
14003
|
+
if (!packageSkillsDir) {
|
|
14004
|
+
return false;
|
|
14005
|
+
}
|
|
14006
|
+
for (const group of this.SKILL_GROUPS) {
|
|
14007
|
+
const sourceGroupDir = path3__namespace.join(packageSkillsDir, group.name);
|
|
14008
|
+
const destGroupDir = path3__namespace.join(projectDir, group.destDir);
|
|
14009
|
+
const skillFiles = await this.getSkillFiles(sourceGroupDir);
|
|
14010
|
+
for (const relFile of skillFiles) {
|
|
14011
|
+
const srcPath = path3__namespace.join(sourceGroupDir, relFile);
|
|
14012
|
+
const destPath = path3__namespace.join(destGroupDir, relFile);
|
|
14013
|
+
if (!await fs3__default.default.pathExists(destPath)) {
|
|
14014
|
+
return true;
|
|
14015
|
+
}
|
|
14016
|
+
const [srcContent, destContent] = await Promise.all([
|
|
14017
|
+
fs3__default.default.readFile(srcPath, "utf-8"),
|
|
14018
|
+
fs3__default.default.readFile(destPath, "utf-8")
|
|
14019
|
+
]);
|
|
14020
|
+
if (srcContent !== destContent) {
|
|
14021
|
+
return true;
|
|
14022
|
+
}
|
|
14023
|
+
}
|
|
14024
|
+
}
|
|
14025
|
+
return false;
|
|
14026
|
+
} catch {
|
|
14027
|
+
return false;
|
|
14028
|
+
}
|
|
14029
|
+
}
|
|
14030
|
+
/**
|
|
14031
|
+
* List all skill groups and their installation status.
|
|
14032
|
+
*
|
|
14033
|
+
* @param projectDir - The project root directory
|
|
14034
|
+
* @returns Array of skill group status objects
|
|
14035
|
+
*/
|
|
14036
|
+
static async listSkillGroups(projectDir) {
|
|
14037
|
+
const packageSkillsDir = this.getPackageSkillsDir();
|
|
14038
|
+
const results = [];
|
|
14039
|
+
for (const group of this.SKILL_GROUPS) {
|
|
14040
|
+
const sourceGroupDir = packageSkillsDir ? path3__namespace.join(packageSkillsDir, group.name) : "";
|
|
14041
|
+
const destGroupDir = path3__namespace.join(projectDir, group.destDir);
|
|
14042
|
+
const skillFiles = packageSkillsDir ? await this.getSkillFiles(sourceGroupDir) : [];
|
|
14043
|
+
const files = [];
|
|
14044
|
+
for (const relFile of skillFiles) {
|
|
14045
|
+
const srcPath = path3__namespace.join(sourceGroupDir, relFile);
|
|
14046
|
+
const destPath = path3__namespace.join(destGroupDir, relFile);
|
|
14047
|
+
const installed = await fs3__default.default.pathExists(destPath);
|
|
14048
|
+
let upToDate = false;
|
|
14049
|
+
if (installed) {
|
|
14050
|
+
try {
|
|
14051
|
+
const [srcContent, destContent] = await Promise.all([
|
|
14052
|
+
fs3__default.default.readFile(srcPath, "utf-8"),
|
|
14053
|
+
fs3__default.default.readFile(destPath, "utf-8")
|
|
14054
|
+
]);
|
|
14055
|
+
upToDate = srcContent === destContent;
|
|
14056
|
+
} catch {
|
|
14057
|
+
upToDate = false;
|
|
14058
|
+
}
|
|
14059
|
+
}
|
|
14060
|
+
files.push({ name: relFile, installed, upToDate });
|
|
14061
|
+
}
|
|
14062
|
+
results.push({
|
|
14063
|
+
name: group.name,
|
|
14064
|
+
destDir: group.destDir,
|
|
14065
|
+
files
|
|
14066
|
+
});
|
|
14067
|
+
}
|
|
14068
|
+
return results;
|
|
14069
|
+
}
|
|
14070
|
+
/**
|
|
14071
|
+
* Get the list of skill group configurations.
|
|
14072
|
+
*/
|
|
14073
|
+
static getSkillGroups() {
|
|
14074
|
+
return [...this.SKILL_GROUPS];
|
|
14075
|
+
}
|
|
14076
|
+
};
|
|
14077
|
+
}
|
|
14078
|
+
});
|
|
14079
|
+
|
|
13624
14080
|
// src/utils/script-installer.ts
|
|
13625
14081
|
var script_installer_exports = {};
|
|
13626
14082
|
__export(script_installer_exports, {
|
|
@@ -13639,12 +14095,16 @@ var init_script_installer = __esm({
|
|
|
13639
14095
|
* Required scripts include both standalone scripts and their dependencies.
|
|
13640
14096
|
* kanban.sh depends on install_requirements.sh for Python venv setup.
|
|
13641
14097
|
* Slack integration scripts allow fetching tasks from Slack and responding.
|
|
14098
|
+
* Hook scripts are stored in the hooks/ subdirectory.
|
|
13642
14099
|
*/
|
|
13643
14100
|
static REQUIRED_SCRIPTS = [
|
|
13644
14101
|
"run_until_completion.sh",
|
|
13645
14102
|
"kanban.sh",
|
|
13646
14103
|
"install_requirements.sh",
|
|
13647
14104
|
// Required by kanban.sh for Python venv creation
|
|
14105
|
+
// Shared utilities
|
|
14106
|
+
"attachment_downloader.py",
|
|
14107
|
+
// File attachment downloading utility (used by Slack/GitHub)
|
|
13648
14108
|
// Slack integration scripts
|
|
13649
14109
|
"slack_state.py",
|
|
13650
14110
|
// State management for Slack integration
|
|
@@ -13657,8 +14117,14 @@ var init_script_installer = __esm({
|
|
|
13657
14117
|
"slack_respond.sh",
|
|
13658
14118
|
// Wrapper script for Slack respond
|
|
13659
14119
|
// GitHub integration script (single-file architecture)
|
|
13660
|
-
"github.py"
|
|
14120
|
+
"github.py",
|
|
13661
14121
|
// Unified GitHub integration (fetch, respond, sync)
|
|
14122
|
+
// Claude Code hooks (stored in hooks/ subdirectory)
|
|
14123
|
+
"hooks/session_counter.sh",
|
|
14124
|
+
// Session message counter hook for warning about long sessions
|
|
14125
|
+
// Log scanning utility
|
|
14126
|
+
"log_scanner.sh"
|
|
14127
|
+
// Scans log files for errors/exceptions and creates kanban bug reports
|
|
13662
14128
|
];
|
|
13663
14129
|
/**
|
|
13664
14130
|
* Get the templates scripts directory from the package
|
|
@@ -13715,6 +14181,10 @@ var init_script_installer = __esm({
|
|
|
13715
14181
|
const destDir = path3__namespace.join(projectDir, ".juno_task", "scripts");
|
|
13716
14182
|
await fs3__default.default.ensureDir(destDir);
|
|
13717
14183
|
const destPath = path3__namespace.join(destDir, scriptName);
|
|
14184
|
+
const destParentDir = path3__namespace.dirname(destPath);
|
|
14185
|
+
if (destParentDir !== destDir) {
|
|
14186
|
+
await fs3__default.default.ensureDir(destParentDir);
|
|
14187
|
+
}
|
|
13718
14188
|
await fs3__default.default.copy(sourcePath, destPath, { overwrite: true });
|
|
13719
14189
|
if (scriptName.endsWith(".sh") || scriptName.endsWith(".py")) {
|
|
13720
14190
|
await fs3__default.default.chmod(destPath, 493);
|
|
@@ -14890,8 +15360,7 @@ After completing the proccess an implementer agent would start the job and go th
|
|
|
14890
15360
|
category: "core" /* CORE */,
|
|
14891
15361
|
content: `0a. study @.juno_task/implement.md.
|
|
14892
15362
|
|
|
14893
|
-
0b. When you discover a syntax, logic, UI, User Flow Error or bug. Immediately update
|
|
14894
|
-
|
|
15363
|
+
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.
|
|
14895
15364
|
|
|
14896
15365
|
999. Important: When authoring documentation capture the why tests and the backing implementation is important.
|
|
14897
15366
|
|
|
@@ -14981,7 +15450,7 @@ Tasks , USER_FEEDBACK and @{{AGENT_DOC_FILE}} should repesent truth. User Open I
|
|
|
14981
15450
|
description: "Implementation steps and current task breakdown",
|
|
14982
15451
|
category: "workflow" /* WORKFLOW */,
|
|
14983
15452
|
content: `---
|
|
14984
|
-
description: Execute the implementation plan by processing and executing all tasks defined in
|
|
15453
|
+
description: Execute the implementation plan by processing and executing all tasks defined in Kanban
|
|
14985
15454
|
---
|
|
14986
15455
|
|
|
14987
15456
|
## User Input
|
|
@@ -15040,7 +15509,7 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|
|
15040
15509
|
1. Run \`./.juno_task/scripts/kanban.sh list\` from repo root and check current project status.
|
|
15041
15510
|
|
|
15042
15511
|
2. Load and analyze the implementation context:
|
|
15043
|
-
- **REQUIRED**: Read
|
|
15512
|
+
- **REQUIRED**: Read Kanban for the complete task list and execution plan
|
|
15044
15513
|
- **REQUIRED**: Read plan.md for tech stack, architecture, and file structure
|
|
15045
15514
|
- **IF EXISTS**: Read data-model.md for entities and relationships
|
|
15046
15515
|
- **IF EXISTS**: Read contracts/ for API specifications and test requirements
|
|
@@ -15080,7 +15549,7 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|
|
15080
15549
|
- **Prettier**: \`node_modules/\`, \`dist/\`, \`build/\`, \`coverage/\`, \`package-lock.json\`, \`yarn.lock\`, \`pnpm-lock.yaml\`
|
|
15081
15550
|
- **Terraform**: \`.terraform/\`, \`*.tfstate*\`, \`*.tfvars\`, \`.terraform.lock.hcl\`
|
|
15082
15551
|
|
|
15083
|
-
5. Parse
|
|
15552
|
+
5. Parse Kanban structure and extract:
|
|
15084
15553
|
- **Task phases**: Setup, Tests, Core, Integration, Polish
|
|
15085
15554
|
- **Task dependencies**: Sequential vs parallel execution rules
|
|
15086
15555
|
- **Task details**: ID, description, file paths, parallel markers [P]
|
|
@@ -15129,7 +15598,7 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|
|
15129
15598
|
|
|
15130
15599
|
|
|
15131
15600
|
|
|
15132
|
-
Note: This command assumes a complete task breakdown exists in
|
|
15601
|
+
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.
|
|
15133
15602
|
|
|
15134
15603
|
|
|
15135
15604
|
---
|
|
@@ -16788,8 +17257,10 @@ ${variables.EDITOR ? `using ${variables.EDITOR} as primary AI subagent` : ""}
|
|
|
16788
17257
|
getDefaultModelForSubagent(subagent) {
|
|
16789
17258
|
const modelDefaults = {
|
|
16790
17259
|
claude: ":sonnet",
|
|
16791
|
-
codex: "
|
|
16792
|
-
|
|
17260
|
+
codex: ":codex",
|
|
17261
|
+
// Expands to gpt-5.3-codex in codex.py
|
|
17262
|
+
gemini: ":pro",
|
|
17263
|
+
// Expands to gemini-2.5-pro in gemini.py
|
|
16793
17264
|
cursor: "auto"
|
|
16794
17265
|
};
|
|
16795
17266
|
return modelDefaults[subagent] || modelDefaults.claude;
|
|
@@ -19139,8 +19610,8 @@ async function compactConfigFile(filePath, options = {}) {
|
|
|
19139
19610
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
19140
19611
|
const ext = path3__namespace.extname(filePath);
|
|
19141
19612
|
const basename11 = path3__namespace.basename(filePath, ext);
|
|
19142
|
-
const
|
|
19143
|
-
backupPath = path3__namespace.join(
|
|
19613
|
+
const dirname14 = path3__namespace.dirname(filePath);
|
|
19614
|
+
backupPath = path3__namespace.join(dirname14, `${basename11}.backup.${timestamp}${ext}`);
|
|
19144
19615
|
await fs3__default.default.writeFile(backupPath, originalContent, "utf-8");
|
|
19145
19616
|
}
|
|
19146
19617
|
const compactionAnalysis = analyzeMarkdownStructure(originalContent);
|
|
@@ -21606,7 +22077,7 @@ var LogViewer = ({
|
|
|
21606
22077
|
init_types();
|
|
21607
22078
|
async function exportLogs(logger2, filepath, options) {
|
|
21608
22079
|
try {
|
|
21609
|
-
const
|
|
22080
|
+
const fs24 = await import('fs-extra');
|
|
21610
22081
|
let entries = logger2.getRecentEntries(options.tail || 1e3);
|
|
21611
22082
|
if (options.level) {
|
|
21612
22083
|
const level = LogLevel[options.level.toUpperCase()];
|
|
@@ -21636,7 +22107,7 @@ async function exportLogs(logger2, filepath, options) {
|
|
|
21636
22107
|
},
|
|
21637
22108
|
entries
|
|
21638
22109
|
};
|
|
21639
|
-
await
|
|
22110
|
+
await fs24.writeFile(filepath, JSON.stringify(exportData, null, 2));
|
|
21640
22111
|
console.log(chalk15__default.default.green(`\u2705 Exported ${entries.length} log entries to: ${filepath}`));
|
|
21641
22112
|
} catch (error) {
|
|
21642
22113
|
console.error(chalk15__default.default.red(`\u274C Failed to export logs: ${error}`));
|
|
@@ -23526,6 +23997,125 @@ Location: ${ServiceInstaller.getServicesDir()}`));
|
|
|
23526
23997
|
return servicesCmd;
|
|
23527
23998
|
}
|
|
23528
23999
|
|
|
24000
|
+
// src/cli/commands/skills.ts
|
|
24001
|
+
init_version();
|
|
24002
|
+
init_skill_installer();
|
|
24003
|
+
function createSkillsCommand() {
|
|
24004
|
+
const skillsCmd = new commander.Command("skills").description("Manage agent skill files").addHelpText("after", `
|
|
24005
|
+
Examples:
|
|
24006
|
+
$ juno-code skills install Install skill files to project directories
|
|
24007
|
+
$ juno-code skills install --force Force reinstall all skill files
|
|
24008
|
+
$ juno-code skills list List skill groups and their files
|
|
24009
|
+
$ juno-code skills status Check installation status
|
|
24010
|
+
|
|
24011
|
+
Skill files are copied from the juno-code package into the project:
|
|
24012
|
+
- Codex skills -> .agents/skills/
|
|
24013
|
+
- Claude skills -> .claude/skills/
|
|
24014
|
+
|
|
24015
|
+
Skills are installed for ALL agents regardless of which subagent is selected.
|
|
24016
|
+
Existing files in the destination directories are preserved.
|
|
24017
|
+
`);
|
|
24018
|
+
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) => {
|
|
24019
|
+
try {
|
|
24020
|
+
const projectDir = process.cwd();
|
|
24021
|
+
if (!options.force) {
|
|
24022
|
+
const needsUpdate = await SkillInstaller.needsUpdate(projectDir);
|
|
24023
|
+
if (!needsUpdate) {
|
|
24024
|
+
console.log(chalk15__default.default.yellow("\u26A0 All skill files are up-to-date"));
|
|
24025
|
+
console.log(chalk15__default.default.dim(" Use --force to reinstall"));
|
|
24026
|
+
return;
|
|
24027
|
+
}
|
|
24028
|
+
}
|
|
24029
|
+
console.log(chalk15__default.default.blue("Installing skill files..."));
|
|
24030
|
+
const installed = await SkillInstaller.install(projectDir, false, options.force);
|
|
24031
|
+
if (installed) {
|
|
24032
|
+
console.log(chalk15__default.default.green("\n\u2713 Skill files installed successfully"));
|
|
24033
|
+
} else {
|
|
24034
|
+
console.log(chalk15__default.default.yellow("\u26A0 No skill files to install (templates may be empty)"));
|
|
24035
|
+
}
|
|
24036
|
+
} catch (error) {
|
|
24037
|
+
console.error(chalk15__default.default.red("\u2717 Installation failed:"));
|
|
24038
|
+
console.error(chalk15__default.default.red(error instanceof Error ? error.message : String(error)));
|
|
24039
|
+
process.exit(1);
|
|
24040
|
+
}
|
|
24041
|
+
});
|
|
24042
|
+
skillsCmd.command("list").alias("ls").description("List skill groups and their files").action(async () => {
|
|
24043
|
+
try {
|
|
24044
|
+
const projectDir = process.cwd();
|
|
24045
|
+
const groups = await SkillInstaller.listSkillGroups(projectDir);
|
|
24046
|
+
if (groups.length === 0) {
|
|
24047
|
+
console.log(chalk15__default.default.yellow("\u26A0 No skill groups configured"));
|
|
24048
|
+
return;
|
|
24049
|
+
}
|
|
24050
|
+
let hasAnyFiles = false;
|
|
24051
|
+
for (const group of groups) {
|
|
24052
|
+
console.log(chalk15__default.default.blue.bold(`
|
|
24053
|
+
${group.name} skills -> ${group.destDir}/`));
|
|
24054
|
+
if (group.files.length === 0) {
|
|
24055
|
+
console.log(chalk15__default.default.dim(" (no skill files bundled yet)"));
|
|
24056
|
+
continue;
|
|
24057
|
+
}
|
|
24058
|
+
hasAnyFiles = true;
|
|
24059
|
+
for (const file of group.files) {
|
|
24060
|
+
const statusIcon = !file.installed ? chalk15__default.default.red("\u2717") : file.upToDate ? chalk15__default.default.green("\u2713") : chalk15__default.default.yellow("\u21BB");
|
|
24061
|
+
const statusLabel = !file.installed ? chalk15__default.default.dim("not installed") : file.upToDate ? chalk15__default.default.dim("up-to-date") : chalk15__default.default.yellow("outdated");
|
|
24062
|
+
console.log(` ${statusIcon} ${file.name} ${statusLabel}`);
|
|
24063
|
+
}
|
|
24064
|
+
}
|
|
24065
|
+
if (!hasAnyFiles) {
|
|
24066
|
+
console.log(chalk15__default.default.dim("\nNo skill files bundled yet. Add files to src/templates/skills/<agent>/ to bundle skills."));
|
|
24067
|
+
}
|
|
24068
|
+
} catch (error) {
|
|
24069
|
+
console.error(chalk15__default.default.red("\u2717 Failed to list skills:"));
|
|
24070
|
+
console.error(chalk15__default.default.red(error instanceof Error ? error.message : String(error)));
|
|
24071
|
+
process.exit(1);
|
|
24072
|
+
}
|
|
24073
|
+
});
|
|
24074
|
+
skillsCmd.command("status").description("Check skill installation status").action(async () => {
|
|
24075
|
+
try {
|
|
24076
|
+
const projectDir = process.cwd();
|
|
24077
|
+
const groups = await SkillInstaller.listSkillGroups(projectDir);
|
|
24078
|
+
const skillGroups = SkillInstaller.getSkillGroups();
|
|
24079
|
+
console.log(chalk15__default.default.blue("Skills Status:\n"));
|
|
24080
|
+
console.log(chalk15__default.default.dim(" Skill groups:"));
|
|
24081
|
+
for (const sg of skillGroups) {
|
|
24082
|
+
console.log(chalk15__default.default.dim(` ${sg.name} -> ${sg.destDir}/`));
|
|
24083
|
+
}
|
|
24084
|
+
const needsUpdate = await SkillInstaller.needsUpdate(projectDir);
|
|
24085
|
+
console.log(`
|
|
24086
|
+
${needsUpdate ? chalk15__default.default.yellow("\u26A0 Updates available") : chalk15__default.default.green("\u2713 All skills up-to-date")}`);
|
|
24087
|
+
let totalFiles = 0;
|
|
24088
|
+
let installedFiles = 0;
|
|
24089
|
+
let outdatedFiles = 0;
|
|
24090
|
+
for (const group of groups) {
|
|
24091
|
+
for (const file of group.files) {
|
|
24092
|
+
totalFiles++;
|
|
24093
|
+
if (file.installed) {
|
|
24094
|
+
installedFiles++;
|
|
24095
|
+
if (!file.upToDate) {
|
|
24096
|
+
outdatedFiles++;
|
|
24097
|
+
}
|
|
24098
|
+
}
|
|
24099
|
+
}
|
|
24100
|
+
}
|
|
24101
|
+
if (totalFiles > 0) {
|
|
24102
|
+
console.log(chalk15__default.default.dim(`
|
|
24103
|
+
Files: ${installedFiles}/${totalFiles} installed, ${outdatedFiles} outdated`));
|
|
24104
|
+
} else {
|
|
24105
|
+
console.log(chalk15__default.default.dim("\n No skill files bundled yet"));
|
|
24106
|
+
}
|
|
24107
|
+
if (needsUpdate) {
|
|
24108
|
+
console.log(chalk15__default.default.dim("\n Run: juno-code skills install"));
|
|
24109
|
+
}
|
|
24110
|
+
} catch (error) {
|
|
24111
|
+
console.error(chalk15__default.default.red("\u2717 Failed to check status:"));
|
|
24112
|
+
console.error(chalk15__default.default.red(error instanceof Error ? error.message : String(error)));
|
|
24113
|
+
process.exit(1);
|
|
24114
|
+
}
|
|
24115
|
+
});
|
|
24116
|
+
return skillsCmd;
|
|
24117
|
+
}
|
|
24118
|
+
|
|
23529
24119
|
// src/cli/commands/completion.ts
|
|
23530
24120
|
init_version();
|
|
23531
24121
|
|
|
@@ -24764,7 +25354,7 @@ function handleCLIError(error, verbose = false) {
|
|
|
24764
25354
|
process.exit(EXIT_CODES.UNEXPECTED_ERROR);
|
|
24765
25355
|
}
|
|
24766
25356
|
function setupGlobalOptions(program) {
|
|
24767
|
-
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 commander.Option("--run-until-completion", "Alias for --til-completion").hideHelp()).addOption(new commander.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)');
|
|
25357
|
+
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 commander.Option("--run-until-completion", "Alias for --til-completion").hideHelp()).addOption(new commander.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)');
|
|
24768
25358
|
program.exitOverride((err) => {
|
|
24769
25359
|
if (err.code === "commander.helpDisplayed") {
|
|
24770
25360
|
process.exit(0);
|
|
@@ -24791,10 +25381,10 @@ function setupMainCommand(program) {
|
|
|
24791
25381
|
const allOptions2 = { ...definedGlobalOptions, ...options };
|
|
24792
25382
|
if (allOptions2.tilCompletion || allOptions2.untilCompletion || allOptions2.runUntilCompletion || allOptions2.tillComplete) {
|
|
24793
25383
|
const { spawn: spawn4 } = await import('child_process');
|
|
24794
|
-
const
|
|
24795
|
-
const
|
|
24796
|
-
const scriptPath =
|
|
24797
|
-
if (!await
|
|
25384
|
+
const path25 = await import('path');
|
|
25385
|
+
const fs24 = await import('fs-extra');
|
|
25386
|
+
const scriptPath = path25.join(process.cwd(), ".juno_task", "scripts", "run_until_completion.sh");
|
|
25387
|
+
if (!await fs24.pathExists(scriptPath)) {
|
|
24798
25388
|
console.error(chalk15__default.default.red.bold("\n\u274C Error: run_until_completion.sh not found"));
|
|
24799
25389
|
console.error(chalk15__default.default.red(` Expected location: ${scriptPath}`));
|
|
24800
25390
|
console.error(chalk15__default.default.yellow('\n\u{1F4A1} Suggestion: Run "juno-code init" to initialize the project'));
|
|
@@ -24845,11 +25435,11 @@ function setupMainCommand(program) {
|
|
|
24845
25435
|
return;
|
|
24846
25436
|
}
|
|
24847
25437
|
if (!globalOptions.subagent && !options.prompt && !options.interactive && !options.interactivePrompt) {
|
|
24848
|
-
const
|
|
24849
|
-
const
|
|
25438
|
+
const fs24 = await import('fs-extra');
|
|
25439
|
+
const path25 = await import('path');
|
|
24850
25440
|
const cwd2 = process.cwd();
|
|
24851
|
-
const junoTaskDir =
|
|
24852
|
-
if (await
|
|
25441
|
+
const junoTaskDir = path25.join(cwd2, ".juno_task");
|
|
25442
|
+
if (await fs24.pathExists(junoTaskDir)) {
|
|
24853
25443
|
console.log(chalk15__default.default.blue.bold("\u{1F3AF} Juno Code - Auto-detected Initialized Project\n"));
|
|
24854
25444
|
try {
|
|
24855
25445
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
@@ -24866,12 +25456,12 @@ function setupMainCommand(program) {
|
|
|
24866
25456
|
allOptions2.subagent = config.defaultSubagent;
|
|
24867
25457
|
console.log(chalk15__default.default.gray(`\u{1F916} Using configured subagent: ${chalk15__default.default.cyan(config.defaultSubagent)}`));
|
|
24868
25458
|
}
|
|
24869
|
-
const promptFile =
|
|
24870
|
-
if (!allOptions2.prompt && await
|
|
25459
|
+
const promptFile = path25.join(junoTaskDir, "prompt.md");
|
|
25460
|
+
if (!allOptions2.prompt && await fs24.pathExists(promptFile)) {
|
|
24871
25461
|
allOptions2.prompt = promptFile;
|
|
24872
25462
|
console.log(chalk15__default.default.gray(`\u{1F4C4} Using default prompt: ${chalk15__default.default.cyan(".juno_task/prompt.md")}`));
|
|
24873
25463
|
}
|
|
24874
|
-
if (allOptions2.subagent && (allOptions2.prompt || await
|
|
25464
|
+
if (allOptions2.subagent && (allOptions2.prompt || await fs24.pathExists(promptFile))) {
|
|
24875
25465
|
console.log(chalk15__default.default.green("\u2713 Auto-detected project configuration\n"));
|
|
24876
25466
|
const { mainCommandHandler: mainCommandHandler3 } = await Promise.resolve().then(() => (init_main(), main_exports));
|
|
24877
25467
|
await mainCommandHandler3([], allOptions2, command);
|
|
@@ -25053,6 +25643,23 @@ async function main() {
|
|
|
25053
25643
|
console.error("[DEBUG] Script auto-update failed:", error instanceof Error ? error.message : String(error));
|
|
25054
25644
|
}
|
|
25055
25645
|
}
|
|
25646
|
+
try {
|
|
25647
|
+
const { SkillInstaller: SkillInstaller2 } = await Promise.resolve().then(() => (init_skill_installer(), skill_installer_exports));
|
|
25648
|
+
if (isForceUpdate) {
|
|
25649
|
+
console.log(chalk15__default.default.blue("\u{1F504} Force updating agent skill files..."));
|
|
25650
|
+
await SkillInstaller2.autoUpdate(process.cwd(), true);
|
|
25651
|
+
console.log(chalk15__default.default.green("\u2713 Agent skill files updated"));
|
|
25652
|
+
} else {
|
|
25653
|
+
const updated = await SkillInstaller2.autoUpdate(process.cwd());
|
|
25654
|
+
if (updated && (process.argv.includes("--verbose") || process.argv.includes("-v") || process.env.JUNO_CODE_DEBUG === "1")) {
|
|
25655
|
+
console.error("[DEBUG] Agent skill files auto-updated");
|
|
25656
|
+
}
|
|
25657
|
+
}
|
|
25658
|
+
} catch (error) {
|
|
25659
|
+
if (process.env.JUNO_CODE_DEBUG === "1") {
|
|
25660
|
+
console.error("[DEBUG] Skill auto-update failed:", error instanceof Error ? error.message : String(error));
|
|
25661
|
+
}
|
|
25662
|
+
}
|
|
25056
25663
|
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");
|
|
25057
25664
|
setupGlobalOptions(program);
|
|
25058
25665
|
const isVerbose = process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
@@ -25060,10 +25667,10 @@ async function main() {
|
|
|
25060
25667
|
const isHelpOrVersion = process.argv.includes("--help") || process.argv.includes("-h") || process.argv.includes("--version") || process.argv.includes("-V");
|
|
25061
25668
|
const hasNoArguments = process.argv.length <= 2;
|
|
25062
25669
|
const isInitCommand = process.argv.includes("init");
|
|
25063
|
-
const
|
|
25064
|
-
const
|
|
25065
|
-
const junoTaskDir =
|
|
25066
|
-
const isInitialized = await
|
|
25670
|
+
const fs24 = await import('fs-extra');
|
|
25671
|
+
const path25 = await import('path');
|
|
25672
|
+
const junoTaskDir = path25.join(process.cwd(), ".juno_task");
|
|
25673
|
+
const isInitialized = await fs24.pathExists(junoTaskDir);
|
|
25067
25674
|
if (!isHelpOrVersion && !hasNoArguments && !isInitCommand && isInitialized) {
|
|
25068
25675
|
try {
|
|
25069
25676
|
const { validateStartupConfigs: validateStartupConfigs2 } = await Promise.resolve().then(() => (init_startup_validation(), startup_validation_exports));
|
|
@@ -25090,6 +25697,7 @@ async function main() {
|
|
|
25090
25697
|
configureHelpCommand(program);
|
|
25091
25698
|
setupConfigCommand(program);
|
|
25092
25699
|
program.addCommand(createServicesCommand());
|
|
25700
|
+
program.addCommand(createSkillsCommand());
|
|
25093
25701
|
setupCompletion(program);
|
|
25094
25702
|
setupAliases(program);
|
|
25095
25703
|
setupMainCommand(program);
|