juno-code 1.0.35 → 1.0.37

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/dist/bin/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import fs2 from 'fs-extra';
2
+ import fs3 from 'fs-extra';
3
3
  import * as path3 from 'path';
4
4
  import path3__default, { dirname, join } from 'path';
5
5
  import * as os4 from 'os';
@@ -142,8 +142,8 @@ var init_types = __esm({
142
142
  };
143
143
  FileSystemError = class extends CLIError {
144
144
  code = "FILESYSTEM_ERROR";
145
- constructor(message, path23) {
146
- super(path23 ? `${message}: ${path23}` : message);
145
+ constructor(message, path24) {
146
+ super(path24 ? `${message}: ${path24}` : message);
147
147
  this.suggestions = [
148
148
  "Check file/directory permissions",
149
149
  "Verify path exists and is accessible",
@@ -282,6 +282,13 @@ var init_default_hooks = __esm({
282
282
  "src/templates/default-hooks.ts"() {
283
283
  init_version();
284
284
  DEFAULT_HOOKS = {
285
+ // Executes once at the beginning of a run (before all iterations)
286
+ // Use for: setup, environment checks, notifications, pre-run cleanup
287
+ START_RUN: {
288
+ commands: []
289
+ },
290
+ // Executes at the start of each iteration
291
+ // Use for: file monitoring, state checks, per-iteration setup
285
292
  START_ITERATION: {
286
293
  commands: [
287
294
  // Monitor CLAUDE.md file size
@@ -290,6 +297,16 @@ var init_default_hooks = __esm({
290
297
  'file="AGENTS.md"; lines=$(wc -l < "$file" 2>/dev/null || echo 0); chars=$(wc -m < "$file" 2>/dev/null || echo 0); if [ "$lines" -gt 450 ] || [ "$chars" -gt 60000 ]; then juno-kanban "[Critical] file $file is too large, keep it lean and useful for every run of the agent."; fi',
291
298
  "./.juno_task/scripts/cleanup_feedback.sh"
292
299
  ]
300
+ },
301
+ // Executes at the end of each iteration
302
+ // Use for: validation, logging, per-iteration cleanup, progress tracking
303
+ END_ITERATION: {
304
+ commands: []
305
+ },
306
+ // Executes once at the end of a run (after all iterations complete)
307
+ // Use for: final cleanup, notifications, reports, post-run actions
308
+ END_RUN: {
309
+ commands: []
293
310
  }
294
311
  };
295
312
  }
@@ -305,8 +322,21 @@ var init_service_installer = __esm({
305
322
  "src/utils/service-installer.ts"() {
306
323
  init_version();
307
324
  ServiceInstaller = class {
325
+ static REQUIRED_SCRIPTS = ["codex.py", "claude.py", "gemini.py"];
308
326
  static SERVICES_DIR = path3.join(homedir(), ".juno_code", "services");
309
327
  static VERSION_FILE = path3.join(homedir(), ".juno_code", "services", ".version");
328
+ static missingScripts(baseDir) {
329
+ return this.REQUIRED_SCRIPTS.filter((file) => !fs3.existsSync(path3.join(baseDir, file)));
330
+ }
331
+ static async missingScriptsAsync(baseDir) {
332
+ const results = await Promise.all(
333
+ this.REQUIRED_SCRIPTS.map(async (file) => ({
334
+ file,
335
+ exists: await fs3.pathExists(path3.join(baseDir, file))
336
+ }))
337
+ );
338
+ return results.filter((result) => !result.exists).map((result) => result.file);
339
+ }
310
340
  /**
311
341
  * Get the current package version
312
342
  */
@@ -315,12 +345,12 @@ var init_service_installer = __esm({
315
345
  const __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
316
346
  const require3 = createRequire(import.meta.url);
317
347
  let packageJsonPath = path3.join(__dirname2, "..", "..", "package.json");
318
- if (fs2.existsSync(packageJsonPath)) {
348
+ if (fs3.existsSync(packageJsonPath)) {
319
349
  const packageJson2 = require3(packageJsonPath);
320
350
  return packageJson2.version;
321
351
  }
322
352
  packageJsonPath = path3.join(__dirname2, "..", "..", "..", "package.json");
323
- if (fs2.existsSync(packageJsonPath)) {
353
+ if (fs3.existsSync(packageJsonPath)) {
324
354
  const packageJson2 = require3(packageJsonPath);
325
355
  return packageJson2.version;
326
356
  }
@@ -334,11 +364,11 @@ var init_service_installer = __esm({
334
364
  */
335
365
  static async getInstalledVersion() {
336
366
  try {
337
- const exists = await fs2.pathExists(this.VERSION_FILE);
367
+ const exists = await fs3.pathExists(this.VERSION_FILE);
338
368
  if (!exists) {
339
369
  return null;
340
370
  }
341
- const version3 = await fs2.readFile(this.VERSION_FILE, "utf-8");
371
+ const version3 = await fs3.readFile(this.VERSION_FILE, "utf-8");
342
372
  return version3.trim();
343
373
  } catch {
344
374
  return null;
@@ -349,7 +379,7 @@ var init_service_installer = __esm({
349
379
  */
350
380
  static async saveVersion() {
351
381
  const version3 = this.getPackageVersion();
352
- await fs2.writeFile(this.VERSION_FILE, version3, "utf-8");
382
+ await fs3.writeFile(this.VERSION_FILE, version3, "utf-8");
353
383
  }
354
384
  /**
355
385
  * Check if services need to be updated based on version
@@ -361,7 +391,7 @@ var init_service_installer = __esm({
361
391
  if (!installedVersion) {
362
392
  return true;
363
393
  }
364
- const exists = await fs2.pathExists(this.SERVICES_DIR);
394
+ const exists = await fs3.pathExists(this.SERVICES_DIR);
365
395
  if (!exists) {
366
396
  return true;
367
397
  }
@@ -369,32 +399,22 @@ var init_service_installer = __esm({
369
399
  return true;
370
400
  }
371
401
  if (semver.eq(packageVersion, installedVersion)) {
372
- const installedCodex = path3.join(this.SERVICES_DIR, "codex.py");
373
- const installedClaude = path3.join(this.SERVICES_DIR, "claude.py");
374
- const codexExists = await fs2.pathExists(installedCodex);
375
- const claudeExists = await fs2.pathExists(installedClaude);
376
- if (!codexExists || !claudeExists) {
402
+ const missingInstalled = await this.missingScriptsAsync(this.SERVICES_DIR);
403
+ if (missingInstalled.length > 0) {
377
404
  return true;
378
405
  }
379
406
  try {
380
407
  const packageServicesDir = this.getPackageServicesDir();
381
- const packageCodex = path3.join(packageServicesDir, "codex.py");
382
- const packageClaude = path3.join(packageServicesDir, "claude.py");
383
- const packageCodexExists = await fs2.pathExists(packageCodex);
384
- const packageClaudeExists = await fs2.pathExists(packageClaude);
385
- if (packageCodexExists) {
386
- const [pkg, inst] = await Promise.all([
387
- fs2.readFile(packageCodex, "utf-8"),
388
- fs2.readFile(installedCodex, "utf-8")
389
- ]);
390
- if (pkg !== inst) {
391
- return true;
392
- }
408
+ const missingPackageScripts = this.missingScripts(packageServicesDir);
409
+ if (missingPackageScripts.length > 0) {
410
+ throw new Error(`Missing required service scripts: ${missingPackageScripts.join(", ")}`);
393
411
  }
394
- if (packageClaudeExists) {
412
+ for (const script of this.REQUIRED_SCRIPTS) {
413
+ const packageScript = path3.join(packageServicesDir, script);
414
+ const installedScript = path3.join(this.SERVICES_DIR, script);
395
415
  const [pkg, inst] = await Promise.all([
396
- fs2.readFile(packageClaude, "utf-8"),
397
- fs2.readFile(installedClaude, "utf-8")
416
+ fs3.readFile(packageScript, "utf-8"),
417
+ fs3.readFile(installedScript, "utf-8")
398
418
  ]);
399
419
  if (pkg !== inst) {
400
420
  return true;
@@ -418,15 +438,27 @@ var init_service_installer = __esm({
418
438
  */
419
439
  static getPackageServicesDir() {
420
440
  const __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
421
- let servicesPath = path3.join(__dirname2, "..", "..", "templates", "services");
422
- if (fs2.existsSync(servicesPath)) {
423
- return servicesPath;
424
- }
425
- servicesPath = path3.join(__dirname2, "..", "templates", "services");
426
- if (fs2.existsSync(servicesPath)) {
427
- return servicesPath;
441
+ const candidates = [
442
+ path3.join(__dirname2, "..", "..", "templates", "services"),
443
+ // dist (production)
444
+ path3.join(__dirname2, "..", "templates", "services")
445
+ // src (development)
446
+ ];
447
+ for (const servicesPath of candidates) {
448
+ if (!fs3.existsSync(servicesPath)) {
449
+ continue;
450
+ }
451
+ const missing = this.missingScripts(servicesPath);
452
+ if (missing.length === 0) {
453
+ return servicesPath;
454
+ }
455
+ if (process.env.JUNO_CODE_DEBUG === "1") {
456
+ console.error(`[DEBUG] Services path missing required scripts (${servicesPath}): ${missing.join(", ")}`);
457
+ }
428
458
  }
429
- throw new Error("Could not find services directory in package");
459
+ throw new Error(
460
+ "Could not find services directory in package containing codex.py, claude.py, and gemini.py. Try reinstalling juno-code or re-running npm run build to refresh service scripts."
461
+ );
430
462
  }
431
463
  /**
432
464
  * Install all service scripts to ~/.juno_code/services/
@@ -434,18 +466,22 @@ var init_service_installer = __esm({
434
466
  */
435
467
  static async install(silent = false) {
436
468
  try {
437
- await fs2.ensureDir(this.SERVICES_DIR);
469
+ await fs3.ensureDir(this.SERVICES_DIR);
438
470
  const packageServicesDir = this.getPackageServicesDir();
439
- await fs2.copy(packageServicesDir, this.SERVICES_DIR, {
471
+ await fs3.copy(packageServicesDir, this.SERVICES_DIR, {
440
472
  overwrite: true,
441
473
  preserveTimestamps: true
442
474
  });
443
- const files = await fs2.readdir(this.SERVICES_DIR);
475
+ const missingAfterCopy = await this.missingScriptsAsync(this.SERVICES_DIR);
476
+ if (missingAfterCopy.length > 0) {
477
+ throw new Error(`Installed services missing required service scripts: ${missingAfterCopy.join(", ")}`);
478
+ }
479
+ const files = await fs3.readdir(this.SERVICES_DIR);
444
480
  for (const file of files) {
445
481
  const filePath = path3.join(this.SERVICES_DIR, file);
446
- const stat = await fs2.stat(filePath);
482
+ const stat = await fs3.stat(filePath);
447
483
  if (stat.isFile() && (file.endsWith(".py") || file.endsWith(".sh"))) {
448
- await fs2.chmod(filePath, 493);
484
+ await fs3.chmod(filePath, 493);
449
485
  }
450
486
  }
451
487
  await this.saveVersion();
@@ -492,11 +528,11 @@ var init_service_installer = __esm({
492
528
  */
493
529
  static async isInstalled() {
494
530
  try {
495
- const exists = await fs2.pathExists(this.SERVICES_DIR);
531
+ const exists = await fs3.pathExists(this.SERVICES_DIR);
496
532
  if (!exists) {
497
533
  return false;
498
534
  }
499
- const files = await fs2.readdir(this.SERVICES_DIR);
535
+ const files = await fs3.readdir(this.SERVICES_DIR);
500
536
  return files.length > 0;
501
537
  } catch {
502
538
  return false;
@@ -519,11 +555,11 @@ var init_service_installer = __esm({
519
555
  */
520
556
  static async listServices() {
521
557
  try {
522
- const exists = await fs2.pathExists(this.SERVICES_DIR);
558
+ const exists = await fs3.pathExists(this.SERVICES_DIR);
523
559
  if (!exists) {
524
560
  return [];
525
561
  }
526
- const files = await fs2.readdir(this.SERVICES_DIR);
562
+ const files = await fs3.readdir(this.SERVICES_DIR);
527
563
  return files.filter((file) => file.endsWith(".py") || file.endsWith(".sh"));
528
564
  } catch {
529
565
  return [];
@@ -534,9 +570,9 @@ var init_service_installer = __esm({
534
570
  */
535
571
  static async uninstall() {
536
572
  try {
537
- const exists = await fs2.pathExists(this.SERVICES_DIR);
573
+ const exists = await fs3.pathExists(this.SERVICES_DIR);
538
574
  if (exists) {
539
- await fs2.remove(this.SERVICES_DIR);
575
+ await fs3.remove(this.SERVICES_DIR);
540
576
  console.log("\u2713 Services uninstalled");
541
577
  }
542
578
  } catch (error) {
@@ -1112,17 +1148,17 @@ async function ensureHooksConfig(baseDir) {
1112
1148
  try {
1113
1149
  const configDir = path3.join(baseDir, ".juno_task");
1114
1150
  const configPath = path3.join(configDir, "config.json");
1115
- await fs2.ensureDir(configDir);
1116
- const configExists = await fs2.pathExists(configPath);
1151
+ await fs3.ensureDir(configDir);
1152
+ const configExists = await fs3.pathExists(configPath);
1117
1153
  const allHookTypes = getDefaultHooks();
1118
1154
  if (!configExists) {
1119
1155
  const defaultConfig = {
1120
1156
  ...DEFAULT_CONFIG,
1121
1157
  hooks: allHookTypes
1122
1158
  };
1123
- await fs2.writeJson(configPath, defaultConfig, { spaces: 2 });
1159
+ await fs3.writeJson(configPath, defaultConfig, { spaces: 2 });
1124
1160
  } else {
1125
- const existingConfig = await fs2.readJson(configPath);
1161
+ const existingConfig = await fs3.readJson(configPath);
1126
1162
  let needsUpdate = false;
1127
1163
  if (!existingConfig.hooks) {
1128
1164
  existingConfig.hooks = allHookTypes;
@@ -1140,7 +1176,7 @@ async function ensureHooksConfig(baseDir) {
1140
1176
  needsUpdate = true;
1141
1177
  }
1142
1178
  if (needsUpdate) {
1143
- await fs2.writeJson(configPath, existingConfig, { spaces: 2 });
1179
+ await fs3.writeJson(configPath, existingConfig, { spaces: 2 });
1144
1180
  }
1145
1181
  }
1146
1182
  } catch (error) {
@@ -1750,6 +1786,12 @@ ${helpText}
1750
1786
  }
1751
1787
  }
1752
1788
  if (options.maxIterations !== void 0) {
1789
+ if (Number.isNaN(options.maxIterations)) {
1790
+ throw new ValidationError(
1791
+ "Max iterations must be a valid number",
1792
+ ["Use -1 for unlimited iterations", "Use positive integers like 1, 5, or 10", "Example: -i 5"]
1793
+ );
1794
+ }
1753
1795
  if (options.maxIterations !== -1 && options.maxIterations < 1) {
1754
1796
  throw new ValidationError(
1755
1797
  "Max iterations must be -1 (unlimited) or a positive number",
@@ -1758,8 +1800,8 @@ ${helpText}
1758
1800
  }
1759
1801
  }
1760
1802
  if (options.cwd) {
1761
- const fs21 = await import('fs-extra');
1762
- if (!await fs21.pathExists(options.cwd)) {
1803
+ const fs22 = await import('fs-extra');
1804
+ if (!await fs22.pathExists(options.cwd)) {
1763
1805
  throw new ValidationError(
1764
1806
  `Working directory does not exist: ${options.cwd}`,
1765
1807
  ["Verify the path exists", "Use absolute paths to avoid ambiguity"]
@@ -3843,8 +3885,8 @@ var init_engine = __esm({
3843
3885
  if (!request.workingDirectory?.trim()) {
3844
3886
  throw new Error("Working directory is required");
3845
3887
  }
3846
- if (request.maxIterations < -1 || request.maxIterations === 0) {
3847
- throw new Error("Max iterations must be positive or -1 for unlimited");
3888
+ if (Number.isNaN(request.maxIterations) || request.maxIterations < -1 || request.maxIterations === 0) {
3889
+ throw new Error("Max iterations must be a positive number or -1 for unlimited");
3848
3890
  }
3849
3891
  }
3850
3892
  /**
@@ -4854,7 +4896,7 @@ var init_config2 = __esm({
4854
4896
  return cached;
4855
4897
  }
4856
4898
  try {
4857
- const configContent = await fs2.readFile(configPath, "utf-8");
4899
+ const configContent = await fs3.readFile(configPath, "utf-8");
4858
4900
  const config = JSON.parse(configContent);
4859
4901
  this.validateConfig(config);
4860
4902
  const resolvedConfig = this.resolveConfigPaths(config, path3.dirname(configPath));
@@ -4876,7 +4918,7 @@ var init_config2 = __esm({
4876
4918
  const rootDir = path3.parse(currentDir).root;
4877
4919
  while (currentDir !== rootDir) {
4878
4920
  const configPath = path3.join(currentDir, ".juno_task", "mcp.json");
4879
- if (await fs2.pathExists(configPath)) {
4921
+ if (await fs3.pathExists(configPath)) {
4880
4922
  return configPath;
4881
4923
  }
4882
4924
  currentDir = path3.dirname(currentDir);
@@ -5062,7 +5104,7 @@ var init_logger = __esm({
5062
5104
  * Matches Python's generate_log_file function
5063
5105
  */
5064
5106
  async initialize(subagent = "mcp") {
5065
- await fs2.ensureDir(this.logDirectory);
5107
+ await fs3.ensureDir(this.logDirectory);
5066
5108
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").split("T");
5067
5109
  const dateStr = timestamp[0];
5068
5110
  const timeStr = timestamp[1].split("-")[0].substring(0, 6);
@@ -5071,7 +5113,7 @@ var init_logger = __esm({
5071
5113
  this.logFilePath = path3__default.join(this.logDirectory, logFileName);
5072
5114
  const header = `# Juno-Task TypeScript Log - Started ${(/* @__PURE__ */ new Date()).toISOString()}
5073
5115
  `;
5074
- await fs2.writeFile(this.logFilePath, header);
5116
+ await fs3.writeFile(this.logFilePath, header);
5075
5117
  }
5076
5118
  /**
5077
5119
  * Format log message matching Python version format
@@ -5089,7 +5131,7 @@ var init_logger = __esm({
5089
5131
  if (!this.logFilePath) {
5090
5132
  throw new Error("Logger not initialized. Call initialize() first.");
5091
5133
  }
5092
- await fs2.appendFile(this.logFilePath, message);
5134
+ await fs3.appendFile(this.logFilePath, message);
5093
5135
  }
5094
5136
  /**
5095
5137
  * Log message at specified level
@@ -7100,7 +7142,7 @@ var init_shell_backend = __esm({
7100
7142
  const timestamp = now.getFullYear().toString() + (now.getMonth() + 1).toString().padStart(2, "0") + now.getDate().toString().padStart(2, "0") + "_" + now.getHours().toString().padStart(2, "0") + now.getMinutes().toString().padStart(2, "0") + now.getSeconds().toString().padStart(2, "0");
7101
7143
  const logDir = path3.join(this.config.workingDirectory, ".juno_task", "logs");
7102
7144
  try {
7103
- await fs2.ensureDir(logDir);
7145
+ await fs3.ensureDir(logDir);
7104
7146
  } catch (error) {
7105
7147
  if (this.config?.debug) {
7106
7148
  engineLogger.warn(`Failed to create log directory: ${error instanceof Error ? error.message : String(error)}`);
@@ -7125,7 +7167,7 @@ var init_shell_backend = __esm({
7125
7167
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
7126
7168
  const logEntry = `[${timestamp}] ${message}
7127
7169
  `;
7128
- await fs2.appendFile(this.logFilePath, logEntry, "utf-8");
7170
+ await fs3.appendFile(this.logFilePath, logEntry, "utf-8");
7129
7171
  } catch (error) {
7130
7172
  if (this.config?.debug) {
7131
7173
  engineLogger.warn(`Failed to write to log file: ${error instanceof Error ? error.message : String(error)}`);
@@ -7199,6 +7241,7 @@ var init_shell_backend = __esm({
7199
7241
  return new Promise(async (resolve9, reject) => {
7200
7242
  const startTime = Date.now();
7201
7243
  const isPython = scriptPath.endsWith(".py");
7244
+ const isGemini = subagentType === "gemini";
7202
7245
  const env2 = {
7203
7246
  ...process.env,
7204
7247
  ...this.config.environment,
@@ -7209,6 +7252,9 @@ var init_shell_backend = __esm({
7209
7252
  JUNO_ITERATION: String(request.arguments?.iteration || 1),
7210
7253
  JUNO_TOOL_ID: toolId
7211
7254
  };
7255
+ if (isGemini) {
7256
+ env2.GEMINI_OUTPUT_FORMAT = env2.GEMINI_OUTPUT_FORMAT || "stream-json";
7257
+ }
7212
7258
  let captureDir = null;
7213
7259
  let capturePath = null;
7214
7260
  if (subagentType === "claude") {
@@ -7230,6 +7276,9 @@ var init_shell_backend = __esm({
7230
7276
  if (isPython && request.arguments?.model) {
7231
7277
  args.push("-m", request.arguments.model);
7232
7278
  }
7279
+ if (isPython && isGemini) {
7280
+ args.push("--output-format", env2.GEMINI_OUTPUT_FORMAT || "stream-json");
7281
+ }
7233
7282
  if (isPython && request.arguments?.agents) {
7234
7283
  args.push("--agents", request.arguments.agents);
7235
7284
  }
@@ -12774,12 +12823,18 @@ async function mainCommandHandler(args, options, command) {
12774
12823
  console.error(chalk15.red("\n\u274C Error: --allowed-tools and --append-allowed-tools are mutually exclusive. Use one or the other."));
12775
12824
  process.exit(1);
12776
12825
  }
12826
+ if (options.maxIterations !== void 0 && Number.isNaN(options.maxIterations)) {
12827
+ throw new ValidationError(
12828
+ "Max iterations must be a valid number",
12829
+ ["Use -1 for unlimited iterations", "Use positive integers like 1, 5, or 10", "Example: -i 5"]
12830
+ );
12831
+ }
12777
12832
  const executionRequest = createExecutionRequest({
12778
12833
  instruction,
12779
12834
  subagent: options.subagent,
12780
12835
  backend: selectedBackend,
12781
12836
  workingDirectory: config.workingDirectory,
12782
- maxIterations: options.maxIterations || config.defaultMaxIterations,
12837
+ maxIterations: options.maxIterations ?? config.defaultMaxIterations,
12783
12838
  model: options.model || config.defaultModel,
12784
12839
  agents: options.agents,
12785
12840
  tools: options.tools,
@@ -12944,7 +12999,7 @@ var init_main = __esm({
12944
12999
  return await this.collectInteractivePrompt();
12945
13000
  } else {
12946
13001
  const defaultPromptPath = path3.join(process.cwd(), ".juno_task", "prompt.md");
12947
- if (await fs2.pathExists(defaultPromptPath)) {
13002
+ if (await fs3.pathExists(defaultPromptPath)) {
12948
13003
  console.error(chalk15.blue(`\u{1F4C4} Using default prompt: ${chalk15.cyan(".juno_task/prompt.md")}`));
12949
13004
  return await this.loadPromptFromFile(defaultPromptPath);
12950
13005
  } else {
@@ -12972,7 +13027,7 @@ var init_main = __esm({
12972
13027
  }
12973
13028
  try {
12974
13029
  const resolvedPath = path3.resolve(prompt);
12975
- return await fs2.pathExists(resolvedPath);
13030
+ return await fs3.pathExists(resolvedPath);
12976
13031
  } catch {
12977
13032
  return false;
12978
13033
  }
@@ -12980,7 +13035,7 @@ var init_main = __esm({
12980
13035
  async loadPromptFromFile(filePath) {
12981
13036
  try {
12982
13037
  const resolvedPath = path3.resolve(filePath);
12983
- const content = await fs2.readFile(resolvedPath, "utf-8");
13038
+ const content = await fs3.readFile(resolvedPath, "utf-8");
12984
13039
  if (!content.trim()) {
12985
13040
  throw new FileSystemError(
12986
13041
  "Prompt file is empty",
@@ -13302,6 +13357,342 @@ var init_main = __esm({
13302
13357
  }
13303
13358
  });
13304
13359
 
13360
+ // src/utils/script-installer.ts
13361
+ var script_installer_exports = {};
13362
+ __export(script_installer_exports, {
13363
+ ScriptInstaller: () => ScriptInstaller
13364
+ });
13365
+ var ScriptInstaller;
13366
+ var init_script_installer = __esm({
13367
+ "src/utils/script-installer.ts"() {
13368
+ init_version();
13369
+ ScriptInstaller = class {
13370
+ /**
13371
+ * Scripts that should be auto-installed if missing
13372
+ * These are critical scripts that users expect to be available
13373
+ */
13374
+ /**
13375
+ * Required scripts include both standalone scripts and their dependencies.
13376
+ * kanban.sh depends on install_requirements.sh for Python venv setup.
13377
+ * Slack integration scripts allow fetching tasks from Slack and responding.
13378
+ */
13379
+ static REQUIRED_SCRIPTS = [
13380
+ "run_until_completion.sh",
13381
+ "kanban.sh",
13382
+ "install_requirements.sh",
13383
+ // Required by kanban.sh for Python venv creation
13384
+ // Slack integration scripts
13385
+ "slack_state.py",
13386
+ // State management for Slack integration
13387
+ "slack_fetch.py",
13388
+ // Core logic for fetching Slack messages
13389
+ "slack_fetch.sh",
13390
+ // Wrapper script for Slack fetch
13391
+ "slack_respond.py",
13392
+ // Core logic for sending responses to Slack
13393
+ "slack_respond.sh"
13394
+ // Wrapper script for Slack respond
13395
+ ];
13396
+ /**
13397
+ * Get the templates scripts directory from the package
13398
+ */
13399
+ static getPackageScriptsDir() {
13400
+ const __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
13401
+ const candidates = [
13402
+ path3.join(__dirname2, "..", "..", "templates", "scripts"),
13403
+ // dist (production)
13404
+ path3.join(__dirname2, "..", "templates", "scripts")
13405
+ // src (development)
13406
+ ];
13407
+ for (const scriptsPath of candidates) {
13408
+ if (fs3.existsSync(scriptsPath)) {
13409
+ return scriptsPath;
13410
+ }
13411
+ }
13412
+ if (process.env.JUNO_CODE_DEBUG === "1") {
13413
+ console.error("[DEBUG] ScriptInstaller: Could not find templates/scripts directory");
13414
+ console.error("[DEBUG] Tried:", candidates);
13415
+ }
13416
+ return null;
13417
+ }
13418
+ /**
13419
+ * Check if a specific script exists in the project's .juno_task/scripts/ directory
13420
+ */
13421
+ static async scriptExists(projectDir, scriptName) {
13422
+ const scriptPath = path3.join(projectDir, ".juno_task", "scripts", scriptName);
13423
+ return fs3.pathExists(scriptPath);
13424
+ }
13425
+ /**
13426
+ * Install a specific script to the project's .juno_task/scripts/ directory
13427
+ * @param projectDir - The project root directory
13428
+ * @param scriptName - Name of the script to install (e.g., 'run_until_completion.sh')
13429
+ * @param silent - If true, suppresses console output
13430
+ * @returns true if script was installed, false if installation was skipped or failed
13431
+ */
13432
+ static async installScript(projectDir, scriptName, silent = false) {
13433
+ try {
13434
+ const packageScriptsDir = this.getPackageScriptsDir();
13435
+ if (!packageScriptsDir) {
13436
+ if (!silent && process.env.JUNO_CODE_DEBUG === "1") {
13437
+ console.error("[DEBUG] ScriptInstaller: Package scripts directory not found");
13438
+ }
13439
+ return false;
13440
+ }
13441
+ const sourcePath = path3.join(packageScriptsDir, scriptName);
13442
+ if (!await fs3.pathExists(sourcePath)) {
13443
+ if (!silent && process.env.JUNO_CODE_DEBUG === "1") {
13444
+ console.error(`[DEBUG] ScriptInstaller: Source script not found: ${sourcePath}`);
13445
+ }
13446
+ return false;
13447
+ }
13448
+ const destDir = path3.join(projectDir, ".juno_task", "scripts");
13449
+ await fs3.ensureDir(destDir);
13450
+ const destPath = path3.join(destDir, scriptName);
13451
+ await fs3.copy(sourcePath, destPath, { overwrite: true });
13452
+ if (scriptName.endsWith(".sh") || scriptName.endsWith(".py")) {
13453
+ await fs3.chmod(destPath, 493);
13454
+ }
13455
+ if (!silent) {
13456
+ console.log(`\u2713 Installed script: ${scriptName} to .juno_task/scripts/`);
13457
+ }
13458
+ if (process.env.JUNO_CODE_DEBUG === "1") {
13459
+ console.error(`[DEBUG] ScriptInstaller: Installed ${scriptName} to ${destPath}`);
13460
+ }
13461
+ return true;
13462
+ } catch (error) {
13463
+ if (!silent && process.env.JUNO_CODE_DEBUG === "1") {
13464
+ console.error(`[DEBUG] ScriptInstaller: Failed to install ${scriptName}:`, error);
13465
+ }
13466
+ return false;
13467
+ }
13468
+ }
13469
+ /**
13470
+ * Check which required scripts are missing from the project
13471
+ * @param projectDir - The project root directory
13472
+ * @returns Array of missing script names
13473
+ */
13474
+ static async getMissingScripts(projectDir) {
13475
+ const missing = [];
13476
+ for (const script of this.REQUIRED_SCRIPTS) {
13477
+ if (!await this.scriptExists(projectDir, script)) {
13478
+ missing.push(script);
13479
+ }
13480
+ }
13481
+ return missing;
13482
+ }
13483
+ /**
13484
+ * Auto-install any missing required scripts
13485
+ * This should be called on CLI startup for initialized projects
13486
+ * @param projectDir - The project root directory
13487
+ * @param silent - If true, suppresses console output
13488
+ * @returns true if any scripts were installed
13489
+ */
13490
+ static async autoInstallMissing(projectDir, silent = true) {
13491
+ try {
13492
+ const junoTaskDir = path3.join(projectDir, ".juno_task");
13493
+ if (!await fs3.pathExists(junoTaskDir)) {
13494
+ return false;
13495
+ }
13496
+ const missing = await this.getMissingScripts(projectDir);
13497
+ if (missing.length === 0) {
13498
+ return false;
13499
+ }
13500
+ if (process.env.JUNO_CODE_DEBUG === "1") {
13501
+ console.error(`[DEBUG] ScriptInstaller: Missing scripts: ${missing.join(", ")}`);
13502
+ }
13503
+ let installedAny = false;
13504
+ for (const script of missing) {
13505
+ const installed = await this.installScript(projectDir, script, silent);
13506
+ if (installed) {
13507
+ installedAny = true;
13508
+ }
13509
+ }
13510
+ if (installedAny && !silent) {
13511
+ console.log(`\u2713 Auto-installed ${missing.length} missing script(s)`);
13512
+ }
13513
+ return installedAny;
13514
+ } catch (error) {
13515
+ if (process.env.JUNO_CODE_DEBUG === "1") {
13516
+ console.error("[DEBUG] ScriptInstaller: autoInstallMissing error:", error);
13517
+ }
13518
+ return false;
13519
+ }
13520
+ }
13521
+ /**
13522
+ * Update a script if the package version is newer (by content comparison)
13523
+ * @param projectDir - The project root directory
13524
+ * @param scriptName - Name of the script to update
13525
+ * @param silent - If true, suppresses console output
13526
+ * @returns true if script was updated
13527
+ */
13528
+ static async updateScriptIfNewer(projectDir, scriptName, silent = true) {
13529
+ try {
13530
+ const packageScriptsDir = this.getPackageScriptsDir();
13531
+ if (!packageScriptsDir) {
13532
+ return false;
13533
+ }
13534
+ const sourcePath = path3.join(packageScriptsDir, scriptName);
13535
+ const destPath = path3.join(projectDir, ".juno_task", "scripts", scriptName);
13536
+ if (!await fs3.pathExists(destPath)) {
13537
+ return this.installScript(projectDir, scriptName, silent);
13538
+ }
13539
+ const [sourceContent, destContent] = await Promise.all([
13540
+ fs3.readFile(sourcePath, "utf-8"),
13541
+ fs3.readFile(destPath, "utf-8")
13542
+ ]);
13543
+ if (sourceContent !== destContent) {
13544
+ await fs3.copy(sourcePath, destPath, { overwrite: true });
13545
+ if (scriptName.endsWith(".sh") || scriptName.endsWith(".py")) {
13546
+ await fs3.chmod(destPath, 493);
13547
+ }
13548
+ if (!silent) {
13549
+ console.log(`\u2713 Updated script: ${scriptName}`);
13550
+ }
13551
+ if (process.env.JUNO_CODE_DEBUG === "1") {
13552
+ console.error(`[DEBUG] ScriptInstaller: Updated ${scriptName} (content changed)`);
13553
+ }
13554
+ return true;
13555
+ }
13556
+ return false;
13557
+ } catch (error) {
13558
+ if (process.env.JUNO_CODE_DEBUG === "1") {
13559
+ console.error(`[DEBUG] ScriptInstaller: updateScriptIfNewer error for ${scriptName}:`, error);
13560
+ }
13561
+ return false;
13562
+ }
13563
+ }
13564
+ /**
13565
+ * Get the path to a script in the project's .juno_task/scripts/ directory
13566
+ */
13567
+ static getScriptPath(projectDir, scriptName) {
13568
+ return path3.join(projectDir, ".juno_task", "scripts", scriptName);
13569
+ }
13570
+ /**
13571
+ * List all required scripts and their installation status
13572
+ */
13573
+ static async listRequiredScripts(projectDir) {
13574
+ const results = [];
13575
+ for (const script of this.REQUIRED_SCRIPTS) {
13576
+ results.push({
13577
+ name: script,
13578
+ installed: await this.scriptExists(projectDir, script)
13579
+ });
13580
+ }
13581
+ return results;
13582
+ }
13583
+ /**
13584
+ * Get scripts that need updates based on content comparison
13585
+ * @param projectDir - The project root directory
13586
+ * @returns Array of script names that have different content from package version
13587
+ */
13588
+ static async getOutdatedScripts(projectDir) {
13589
+ const outdated = [];
13590
+ const packageScriptsDir = this.getPackageScriptsDir();
13591
+ if (!packageScriptsDir) {
13592
+ return outdated;
13593
+ }
13594
+ for (const script of this.REQUIRED_SCRIPTS) {
13595
+ const sourcePath = path3.join(packageScriptsDir, script);
13596
+ const destPath = path3.join(projectDir, ".juno_task", "scripts", script);
13597
+ if (!await fs3.pathExists(sourcePath)) {
13598
+ continue;
13599
+ }
13600
+ if (!await fs3.pathExists(destPath)) {
13601
+ continue;
13602
+ }
13603
+ try {
13604
+ const [sourceContent, destContent] = await Promise.all([
13605
+ fs3.readFile(sourcePath, "utf-8"),
13606
+ fs3.readFile(destPath, "utf-8")
13607
+ ]);
13608
+ if (sourceContent !== destContent) {
13609
+ outdated.push(script);
13610
+ }
13611
+ } catch {
13612
+ outdated.push(script);
13613
+ }
13614
+ }
13615
+ return outdated;
13616
+ }
13617
+ /**
13618
+ * Check if any scripts need installation or update
13619
+ * @param projectDir - The project root directory
13620
+ * @returns true if any scripts need to be installed or updated
13621
+ */
13622
+ static async needsUpdate(projectDir) {
13623
+ try {
13624
+ const junoTaskDir = path3.join(projectDir, ".juno_task");
13625
+ if (!await fs3.pathExists(junoTaskDir)) {
13626
+ return false;
13627
+ }
13628
+ const missing = await this.getMissingScripts(projectDir);
13629
+ if (missing.length > 0) {
13630
+ return true;
13631
+ }
13632
+ const outdated = await this.getOutdatedScripts(projectDir);
13633
+ return outdated.length > 0;
13634
+ } catch {
13635
+ return false;
13636
+ }
13637
+ }
13638
+ /**
13639
+ * Automatically update scripts - installs missing AND updates outdated scripts
13640
+ * Similar to ServiceInstaller.autoUpdate(), this ensures project scripts
13641
+ * are always in sync with the package version.
13642
+ *
13643
+ * This should be called on every CLI run to ensure scripts are up-to-date.
13644
+ * @param projectDir - The project root directory
13645
+ * @param silent - If true, suppresses console output
13646
+ * @returns true if any scripts were installed or updated
13647
+ */
13648
+ static async autoUpdate(projectDir, silent = true) {
13649
+ try {
13650
+ const debug = process.env.JUNO_CODE_DEBUG === "1";
13651
+ const junoTaskDir = path3.join(projectDir, ".juno_task");
13652
+ if (!await fs3.pathExists(junoTaskDir)) {
13653
+ return false;
13654
+ }
13655
+ const missing = await this.getMissingScripts(projectDir);
13656
+ const outdated = await this.getOutdatedScripts(projectDir);
13657
+ if (debug) {
13658
+ if (missing.length > 0) {
13659
+ console.error(`[DEBUG] ScriptInstaller: Missing scripts: ${missing.join(", ")}`);
13660
+ }
13661
+ if (outdated.length > 0) {
13662
+ console.error(`[DEBUG] ScriptInstaller: Outdated scripts: ${outdated.join(", ")}`);
13663
+ }
13664
+ }
13665
+ if (missing.length === 0 && outdated.length === 0) {
13666
+ return false;
13667
+ }
13668
+ const scriptsToUpdate = [.../* @__PURE__ */ new Set([...missing, ...outdated])];
13669
+ let updatedAny = false;
13670
+ for (const script of scriptsToUpdate) {
13671
+ const installed = await this.installScript(projectDir, script, silent);
13672
+ if (installed) {
13673
+ updatedAny = true;
13674
+ }
13675
+ }
13676
+ if (updatedAny) {
13677
+ if (debug) {
13678
+ console.error(`[DEBUG] ScriptInstaller: Updated ${scriptsToUpdate.length} script(s)`);
13679
+ }
13680
+ if (!silent) {
13681
+ console.log(`\u2713 Updated ${scriptsToUpdate.length} script(s) in .juno_task/scripts/`);
13682
+ }
13683
+ }
13684
+ return updatedAny;
13685
+ } catch (error) {
13686
+ if (process.env.JUNO_CODE_DEBUG === "1") {
13687
+ console.error("[DEBUG] ScriptInstaller: autoUpdate error:", error);
13688
+ }
13689
+ return false;
13690
+ }
13691
+ }
13692
+ };
13693
+ }
13694
+ });
13695
+
13305
13696
  // src/utils/startup-validation.ts
13306
13697
  var startup_validation_exports = {};
13307
13698
  __export(startup_validation_exports, {
@@ -13392,7 +13783,7 @@ async function validateJSONFile(configSchema, baseDir) {
13392
13783
  const warnings = [];
13393
13784
  const filePath = path3__default.join(baseDir, configSchema.file);
13394
13785
  try {
13395
- const exists = await fs2.pathExists(filePath);
13786
+ const exists = await fs3.pathExists(filePath);
13396
13787
  if (!exists) {
13397
13788
  if (configSchema.required) {
13398
13789
  errors.push({
@@ -13415,7 +13806,7 @@ async function validateJSONFile(configSchema, baseDir) {
13415
13806
  return { isValid: !configSchema.required, errors, warnings };
13416
13807
  }
13417
13808
  try {
13418
- await fs2.access(filePath, fs2.constants.R_OK);
13809
+ await fs3.access(filePath, fs3.constants.R_OK);
13419
13810
  } catch (accessError) {
13420
13811
  errors.push({
13421
13812
  file: configSchema.file,
@@ -13431,7 +13822,7 @@ async function validateJSONFile(configSchema, baseDir) {
13431
13822
  }
13432
13823
  let jsonData;
13433
13824
  try {
13434
- const fileContent = await fs2.readFile(filePath, "utf8");
13825
+ const fileContent = await fs3.readFile(filePath, "utf8");
13435
13826
  jsonData = JSON.parse(fileContent);
13436
13827
  } catch (parseError) {
13437
13828
  const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
@@ -13566,7 +13957,7 @@ function displayValidationResults(result) {
13566
13957
  }
13567
13958
  async function logValidationResults(result, baseDir = process.cwd()) {
13568
13959
  const logDir = path3__default.join(baseDir, ".juno_task", "logs");
13569
- await fs2.ensureDir(logDir);
13960
+ await fs3.ensureDir(logDir);
13570
13961
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
13571
13962
  const logFile = path3__default.join(logDir, `startup-validation-${timestamp}.log`);
13572
13963
  const logContent = [
@@ -13609,7 +14000,7 @@ async function logValidationResults(result, baseDir = process.cwd()) {
13609
14000
  logContent.push(``);
13610
14001
  }
13611
14002
  }
13612
- await fs2.writeFile(logFile, logContent.join("\n"));
14003
+ await fs3.writeFile(logFile, logContent.join("\n"));
13613
14004
  return logFile;
13614
14005
  }
13615
14006
  async function validateStartupConfigs(baseDir = process.cwd(), verbose = false) {
@@ -13943,7 +14334,7 @@ var TemplateEngine = class {
13943
14334
  let overallError;
13944
14335
  try {
13945
14336
  if (!options.dryRun) {
13946
- await fs2.ensureDir(targetDirectory);
14337
+ await fs3.ensureDir(targetDirectory);
13947
14338
  }
13948
14339
  for (const template of templates) {
13949
14340
  try {
@@ -15293,7 +15684,7 @@ This directory contains specification documents for your project.
15293
15684
  const fileName = this.getTemplateFileName(template);
15294
15685
  const targetPath = path3.join(targetDirectory, fileName);
15295
15686
  try {
15296
- const fileExists = await fs2.pathExists(targetPath);
15687
+ const fileExists = await fs3.pathExists(targetPath);
15297
15688
  if (fileExists && !options.force && options.onConflict !== "overwrite") {
15298
15689
  return {
15299
15690
  path: targetPath,
@@ -15315,10 +15706,10 @@ This directory contains specification documents for your project.
15315
15706
  }
15316
15707
  if (options.createBackup && fileExists) {
15317
15708
  const backupPath = `${targetPath}.backup.${Date.now()}`;
15318
- await fs2.copy(targetPath, backupPath);
15709
+ await fs3.copy(targetPath, backupPath);
15319
15710
  }
15320
- await fs2.ensureDir(path3.dirname(targetPath));
15321
- await fs2.writeFile(targetPath, renderedContent, "utf8");
15711
+ await fs3.ensureDir(path3.dirname(targetPath));
15712
+ await fs3.writeFile(targetPath, renderedContent, "utf8");
15322
15713
  return {
15323
15714
  path: targetPath,
15324
15715
  content: renderedContent,
@@ -15582,7 +15973,7 @@ var SimpleInitTUI = class {
15582
15973
  }
15583
15974
  async confirmSave(targetDirectory) {
15584
15975
  const junoTaskPath = path3.join(targetDirectory, ".juno_task");
15585
- if (await fs2.pathExists(junoTaskPath)) {
15976
+ if (await fs3.pathExists(junoTaskPath)) {
15586
15977
  console.log(chalk15.yellow(" \u26A0\uFE0F .juno_task directory already exists"));
15587
15978
  console.log(chalk15.gray(" Would you like to:"));
15588
15979
  console.log(chalk15.gray(" 1) Override existing files"));
@@ -15627,16 +16018,16 @@ var SimpleProjectGenerator = class {
15627
16018
  async generate() {
15628
16019
  const { targetDirectory, variables, force } = this.context;
15629
16020
  console.log(chalk15.blue("\u{1F4C1} Creating project directory..."));
15630
- await fs2.ensureDir(targetDirectory);
16021
+ await fs3.ensureDir(targetDirectory);
15631
16022
  const junoTaskDir = path3.join(targetDirectory, ".juno_task");
15632
- const junoTaskExists = await fs2.pathExists(junoTaskDir);
16023
+ const junoTaskExists = await fs3.pathExists(junoTaskDir);
15633
16024
  if (junoTaskExists && !force) {
15634
16025
  throw new ValidationError(
15635
16026
  "Project already initialized. Directory .juno_task already exists.",
15636
16027
  ["Use --force flag to overwrite existing files", "Choose a different directory"]
15637
16028
  );
15638
16029
  }
15639
- await fs2.ensureDir(junoTaskDir);
16030
+ await fs3.ensureDir(junoTaskDir);
15640
16031
  console.log(chalk15.blue("\u2699\uFE0F Creating project configuration..."));
15641
16032
  await this.createConfigFile(junoTaskDir, targetDirectory);
15642
16033
  console.log(chalk15.blue("\u{1F527} Setting up MCP configuration..."));
@@ -15670,21 +16061,21 @@ var SimpleProjectGenerator = class {
15670
16061
  const promptContent = await templateEngine.render(promptTemplate, templateContext);
15671
16062
  const initContent = await templateEngine.render(initTemplate, templateContext);
15672
16063
  const implementContent = await templateEngine.render(implementTemplate, templateContext);
15673
- await fs2.writeFile(path3.join(junoTaskDir, "prompt.md"), promptContent);
15674
- await fs2.writeFile(path3.join(junoTaskDir, "init.md"), initContent);
15675
- await fs2.writeFile(path3.join(junoTaskDir, "implement.md"), implementContent);
16064
+ await fs3.writeFile(path3.join(junoTaskDir, "prompt.md"), promptContent);
16065
+ await fs3.writeFile(path3.join(junoTaskDir, "init.md"), initContent);
16066
+ await fs3.writeFile(path3.join(junoTaskDir, "implement.md"), implementContent);
15676
16067
  const userFeedbackTemplate = templateEngine.getBuiltInTemplate("USER_FEEDBACK.md");
15677
16068
  if (userFeedbackTemplate) {
15678
16069
  const userFeedbackContent = await templateEngine.render(userFeedbackTemplate, templateContext);
15679
- await fs2.writeFile(path3.join(junoTaskDir, "USER_FEEDBACK.md"), userFeedbackContent);
16070
+ await fs3.writeFile(path3.join(junoTaskDir, "USER_FEEDBACK.md"), userFeedbackContent);
15680
16071
  }
15681
16072
  const planTemplate = templateEngine.getBuiltInTemplate("plan.md");
15682
16073
  if (planTemplate) {
15683
16074
  const planContent = await templateEngine.render(planTemplate, templateContext);
15684
- await fs2.writeFile(path3.join(junoTaskDir, "plan.md"), planContent);
16075
+ await fs3.writeFile(path3.join(junoTaskDir, "plan.md"), planContent);
15685
16076
  }
15686
16077
  const specsDir = path3.join(junoTaskDir, "specs");
15687
- await fs2.ensureDir(specsDir);
16078
+ await fs3.ensureDir(specsDir);
15688
16079
  const specsReadmeContent = `# Project Specifications
15689
16080
 
15690
16081
  This directory contains detailed specifications for the project components.
@@ -15701,7 +16092,7 @@ This directory contains detailed specifications for the project components.
15701
16092
  - Avoid conflicts with existing file names
15702
16093
  - Use \`.md\` extension for all specification files
15703
16094
  `;
15704
- await fs2.writeFile(path3.join(specsDir, "README.md"), specsReadmeContent);
16095
+ await fs3.writeFile(path3.join(specsDir, "README.md"), specsReadmeContent);
15705
16096
  const requirementsContent = `# Requirements Specification
15706
16097
 
15707
16098
  ## Functional Requirements
@@ -15748,7 +16139,7 @@ This directory contains detailed specifications for the project components.
15748
16139
  - Code quality: Clean, maintainable codebase
15749
16140
  - Documentation: Complete and accurate documentation
15750
16141
  `;
15751
- await fs2.writeFile(path3.join(specsDir, "requirements.md"), requirementsContent);
16142
+ await fs3.writeFile(path3.join(specsDir, "requirements.md"), requirementsContent);
15752
16143
  const architectureContent = `# Architecture Specification
15753
16144
 
15754
16145
  ## System Overview
@@ -15830,7 +16221,7 @@ This project uses AI-assisted development with juno-code to achieve: ${variables
15830
16221
  - Performance monitoring
15831
16222
  - Security best practices
15832
16223
  `;
15833
- await fs2.writeFile(path3.join(specsDir, "architecture.md"), architectureContent);
16224
+ await fs3.writeFile(path3.join(specsDir, "architecture.md"), architectureContent);
15834
16225
  const claudeContent = `# Claude Development Session Learnings
15835
16226
 
15836
16227
  ## Project Overview
@@ -15902,7 +16293,7 @@ This file will be updated as development progresses to track:
15902
16293
  - Solutions to complex problems
15903
16294
  - Performance improvements and optimizations
15904
16295
  `;
15905
- await fs2.writeFile(path3.join(targetDirectory, "CLAUDE.md"), claudeContent);
16296
+ await fs3.writeFile(path3.join(targetDirectory, "CLAUDE.md"), claudeContent);
15906
16297
  const agentsContent = `# AI Agent Selection and Performance
15907
16298
 
15908
16299
  ## Available Agents
@@ -15966,7 +16357,7 @@ Track agent performance for:
15966
16357
  4. **Feedback Loop**: Provide feedback to improve agent performance
15967
16358
  5. **Performance Monitoring**: Track and optimize agent usage
15968
16359
  `;
15969
- await fs2.writeFile(path3.join(targetDirectory, "AGENTS.md"), agentsContent);
16360
+ await fs3.writeFile(path3.join(targetDirectory, "AGENTS.md"), agentsContent);
15970
16361
  const readmeContent = `# ${variables.PROJECT_NAME}
15971
16362
 
15972
16363
  ${variables.DESCRIPTION}
@@ -16063,7 +16454,7 @@ ${variables.GIT_URL}` : ""}
16063
16454
  Created with juno-code on ${variables.CURRENT_DATE}
16064
16455
  ${variables.EDITOR ? `using ${variables.EDITOR} as primary AI subagent` : ""}
16065
16456
  `;
16066
- await fs2.writeFile(path3.join(targetDirectory, "README.md"), readmeContent);
16457
+ await fs3.writeFile(path3.join(targetDirectory, "README.md"), readmeContent);
16067
16458
  console.log(chalk15.blue("\u{1F4E6} Installing utility scripts..."));
16068
16459
  await this.copyScriptsFromTemplates(junoTaskDir);
16069
16460
  console.log(chalk15.blue("\u{1F40D} Installing Python requirements..."));
@@ -16115,7 +16506,7 @@ ${variables.EDITOR ? `using ${variables.EDITOR} as primary AI subagent` : ""}
16115
16506
  hooks: getDefaultHooks()
16116
16507
  };
16117
16508
  const configPath = path3.join(junoTaskDir, "config.json");
16118
- await fs2.writeFile(configPath, JSON.stringify(configContent, null, 2));
16509
+ await fs3.writeFile(configPath, JSON.stringify(configContent, null, 2));
16119
16510
  console.log(chalk15.green(` \u2713 Created .juno_task/config.json with ${this.context.subagent} as default subagent`));
16120
16511
  }
16121
16512
  async createMcpFile(junoTaskDir, targetDirectory) {
@@ -16169,7 +16560,7 @@ ${variables.EDITOR ? `using ${variables.EDITOR} as primary AI subagent` : ""}
16169
16560
  }
16170
16561
  };
16171
16562
  const mcpPath = path3.join(junoTaskDir, "mcp.json");
16172
- await fs2.writeFile(mcpPath, JSON.stringify(mcpContent, null, 2));
16563
+ await fs3.writeFile(mcpPath, JSON.stringify(mcpContent, null, 2));
16173
16564
  console.log(chalk15.green(` \u2713 Created .juno_task/mcp.json with roundtable-ai server configuration`));
16174
16565
  }
16175
16566
  getDefaultModelForSubagent(subagent) {
@@ -16188,7 +16579,7 @@ ${variables.EDITOR ? `using ${variables.EDITOR} as primary AI subagent` : ""}
16188
16579
  async copyScriptsFromTemplates(junoTaskDir) {
16189
16580
  try {
16190
16581
  const scriptsDir = path3.join(junoTaskDir, "scripts");
16191
- await fs2.ensureDir(scriptsDir);
16582
+ await fs3.ensureDir(scriptsDir);
16192
16583
  const __filename2 = fileURLToPath(import.meta.url);
16193
16584
  const __dirname2 = path3.dirname(__filename2);
16194
16585
  let templatesScriptsDir;
@@ -16199,11 +16590,11 @@ ${variables.EDITOR ? `using ${variables.EDITOR} as primary AI subagent` : ""}
16199
16590
  } else {
16200
16591
  templatesScriptsDir = path3.join(__dirname2, "../../templates/scripts");
16201
16592
  }
16202
- if (!await fs2.pathExists(templatesScriptsDir)) {
16593
+ if (!await fs3.pathExists(templatesScriptsDir)) {
16203
16594
  console.log(chalk15.yellow(" \u26A0\uFE0F Template scripts directory not found, skipping script installation"));
16204
16595
  return;
16205
16596
  }
16206
- const scriptFiles = await fs2.readdir(templatesScriptsDir);
16597
+ const scriptFiles = await fs3.readdir(templatesScriptsDir);
16207
16598
  if (scriptFiles.length === 0) {
16208
16599
  console.log(chalk15.gray(" \u2139\uFE0F No template scripts found to install"));
16209
16600
  return;
@@ -16212,11 +16603,11 @@ ${variables.EDITOR ? `using ${variables.EDITOR} as primary AI subagent` : ""}
16212
16603
  for (const scriptFile of scriptFiles) {
16213
16604
  const sourcePath = path3.join(templatesScriptsDir, scriptFile);
16214
16605
  const destPath = path3.join(scriptsDir, scriptFile);
16215
- const stats = await fs2.stat(sourcePath);
16606
+ const stats = await fs3.stat(sourcePath);
16216
16607
  if (stats.isFile()) {
16217
- await fs2.copy(sourcePath, destPath);
16608
+ await fs3.copy(sourcePath, destPath);
16218
16609
  if (scriptFile.endsWith(".sh")) {
16219
- await fs2.chmod(destPath, 493);
16610
+ await fs3.chmod(destPath, 493);
16220
16611
  }
16221
16612
  copiedCount++;
16222
16613
  console.log(chalk15.green(` \u2713 Installed script: ${scriptFile}`));
@@ -16239,7 +16630,7 @@ ${variables.EDITOR ? `using ${variables.EDITOR} as primary AI subagent` : ""}
16239
16630
  try {
16240
16631
  const scriptsDir = path3.join(junoTaskDir, "scripts");
16241
16632
  const installScript = path3.join(scriptsDir, "install_requirements.sh");
16242
- if (!await fs2.pathExists(installScript)) {
16633
+ if (!await fs3.pathExists(installScript)) {
16243
16634
  console.log(chalk15.yellow(" \u26A0\uFE0F install_requirements.sh not found, skipping Python dependencies installation"));
16244
16635
  console.log(chalk15.gray(" You can install dependencies manually: juno-kanban, roundtable-ai"));
16245
16636
  return;
@@ -16536,20 +16927,20 @@ init_types();
16536
16927
  async function loadInitPrompt(directory) {
16537
16928
  const junoTaskDir = path3.join(directory, ".juno_task");
16538
16929
  const initFile = path3.join(junoTaskDir, "init.md");
16539
- if (!await fs2.pathExists(junoTaskDir)) {
16930
+ if (!await fs3.pathExists(junoTaskDir)) {
16540
16931
  throw new FileSystemError(
16541
16932
  'No .juno_task directory found. Run "juno-code init" first.',
16542
16933
  junoTaskDir
16543
16934
  );
16544
16935
  }
16545
- if (!await fs2.pathExists(initFile)) {
16936
+ if (!await fs3.pathExists(initFile)) {
16546
16937
  throw new FileSystemError(
16547
16938
  "No init.md file found in .juno_task directory",
16548
16939
  initFile
16549
16940
  );
16550
16941
  }
16551
16942
  try {
16552
- const content = await fs2.readFile(initFile, "utf-8");
16943
+ const content = await fs3.readFile(initFile, "utf-8");
16553
16944
  if (!content.trim()) {
16554
16945
  throw new FileSystemError(
16555
16946
  "init.md file is empty. Please add task instructions.",
@@ -17581,8 +17972,8 @@ Focus on ${request.intelligence === "basic" ? "basic functionality" : request.in
17581
17972
  for (const scenario of scenarios) {
17582
17973
  const testContent = await this.generateTestContent(request, scenario, template);
17583
17974
  const testFilePath = this.resolveTestFilePath(request, scenario);
17584
- await fs2.ensureDir(path3.dirname(testFilePath));
17585
- await fs2.writeFile(testFilePath, testContent);
17975
+ await fs3.ensureDir(path3.dirname(testFilePath));
17976
+ await fs3.writeFile(testFilePath, testContent);
17586
17977
  testFiles.push(testFilePath);
17587
17978
  }
17588
17979
  return testFiles;
@@ -17834,8 +18225,8 @@ var TestExecutionEngine = class {
17834
18225
  async collectCoverage(request) {
17835
18226
  const coverageFile = path3.join(request.workingDirectory, "coverage", "coverage-summary.json");
17836
18227
  try {
17837
- if (await fs2.pathExists(coverageFile)) {
17838
- const coverageData = await fs2.readJson(coverageFile);
18228
+ if (await fs3.pathExists(coverageFile)) {
18229
+ const coverageData = await fs3.readJson(coverageFile);
17839
18230
  return {
17840
18231
  lines: coverageData.total?.lines?.pct || 0,
17841
18232
  functions: coverageData.total?.functions?.pct || 0,
@@ -18018,8 +18409,8 @@ var TestReportEngine = class {
18018
18409
  suggestions: analysis.suggestions,
18019
18410
  recommendations: analysis.recommendations
18020
18411
  };
18021
- await fs2.ensureDir(path3.dirname(outputPath));
18022
- await fs2.writeJson(outputPath, report, { spaces: 2 });
18412
+ await fs3.ensureDir(path3.dirname(outputPath));
18413
+ await fs3.writeJson(outputPath, report, { spaces: 2 });
18023
18414
  return outputPath;
18024
18415
  }
18025
18416
  async generateHTMLReport(analysis, outputPath, includeVisualizations) {
@@ -18082,8 +18473,8 @@ var TestReportEngine = class {
18082
18473
  </body>
18083
18474
  </html>
18084
18475
  `.trim();
18085
- await fs2.ensureDir(path3.dirname(outputPath));
18086
- await fs2.writeFile(outputPath, html);
18476
+ await fs3.ensureDir(path3.dirname(outputPath));
18477
+ await fs3.writeFile(outputPath, html);
18087
18478
  return outputPath;
18088
18479
  }
18089
18480
  generateCharts(analysis) {
@@ -18140,8 +18531,8 @@ ${analysis.suggestions.map((suggestion) => `- ${suggestion}`).join("\n")}
18140
18531
 
18141
18532
  ${analysis.recommendations.map((rec) => `- ${rec}`).join("\n")}
18142
18533
  `.trim();
18143
- await fs2.ensureDir(path3.dirname(outputPath));
18144
- await fs2.writeFile(outputPath, markdown);
18534
+ await fs3.ensureDir(path3.dirname(outputPath));
18535
+ await fs3.writeFile(outputPath, markdown);
18145
18536
  return outputPath;
18146
18537
  }
18147
18538
  async displayConsoleReport(analysis) {
@@ -18481,16 +18872,16 @@ async function compactConfigFile(filePath, options = {}) {
18481
18872
  preserveDays = 30,
18482
18873
  preservePatterns = []
18483
18874
  } = options;
18484
- const originalContent = await fs2.readFile(filePath, "utf-8");
18875
+ const originalContent = await fs3.readFile(filePath, "utf-8");
18485
18876
  const originalSize = originalContent.length;
18486
18877
  let backupPath = "";
18487
18878
  if (createBackup && !dryRun) {
18488
18879
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
18489
18880
  const ext = path3.extname(filePath);
18490
18881
  const basename11 = path3.basename(filePath, ext);
18491
- const dirname12 = path3.dirname(filePath);
18492
- backupPath = path3.join(dirname12, `${basename11}.backup.${timestamp}${ext}`);
18493
- await fs2.writeFile(backupPath, originalContent, "utf-8");
18882
+ const dirname13 = path3.dirname(filePath);
18883
+ backupPath = path3.join(dirname13, `${basename11}.backup.${timestamp}${ext}`);
18884
+ await fs3.writeFile(backupPath, originalContent, "utf-8");
18494
18885
  }
18495
18886
  const compactionAnalysis = analyzeMarkdownStructure(originalContent);
18496
18887
  const compactedContent = compactMarkdownContent(
@@ -18505,7 +18896,7 @@ async function compactConfigFile(filePath, options = {}) {
18505
18896
  const compactedSize = finalContent.length;
18506
18897
  const reductionPercentage = Math.round((originalSize - compactedSize) / originalSize * 100);
18507
18898
  if (!dryRun) {
18508
- await fs2.writeFile(filePath, finalContent, "utf-8");
18899
+ await fs3.writeFile(filePath, finalContent, "utf-8");
18509
18900
  }
18510
18901
  return {
18511
18902
  originalSize,
@@ -18635,7 +19026,7 @@ function formatFileSize(bytes) {
18635
19026
  }
18636
19027
  async function shouldCompactFile(filePath, sizeThresholdKB = 50, ageThresholdDays = 30) {
18637
19028
  try {
18638
- const stats = await fs2.stat(filePath);
19029
+ const stats = await fs3.stat(filePath);
18639
19030
  const sizeKB = stats.size / 1024;
18640
19031
  if (sizeKB > sizeThresholdKB) {
18641
19032
  return true;
@@ -18657,19 +19048,19 @@ async function archiveResolvedIssues(options) {
18657
19048
  feedbackFile,
18658
19049
  archiveDir = path3.join(path3.dirname(feedbackFile), "archives"),
18659
19050
  openIssuesThreshold = 10} = options;
18660
- if (!await fs2.pathExists(feedbackFile)) {
19051
+ if (!await fs3.pathExists(feedbackFile)) {
18661
19052
  throw new Error(`Feedback file does not exist: ${feedbackFile}`);
18662
19053
  }
18663
- const content = await fs2.readFile(feedbackFile, "utf-8");
19054
+ const content = await fs3.readFile(feedbackFile, "utf-8");
18664
19055
  const parsed = parseUserFeedback(content);
18665
19056
  const warningsGenerated = [];
18666
19057
  if (parsed.openIssues.length > openIssuesThreshold) {
18667
19058
  const warning = `Found ${parsed.openIssues.length} open issues (threshold: ${openIssuesThreshold}). Consider reviewing and prioritizing.`;
18668
19059
  warningsGenerated.push(warning);
18669
19060
  const logFile = path3.join(path3.dirname(feedbackFile), "logs", "feedback-warnings.log");
18670
- await fs2.ensureDir(path3.dirname(logFile));
19061
+ await fs3.ensureDir(path3.dirname(logFile));
18671
19062
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
18672
- await fs2.appendFile(logFile, `[${timestamp}] ${warning}
19063
+ await fs3.appendFile(logFile, `[${timestamp}] ${warning}
18673
19064
  `);
18674
19065
  }
18675
19066
  if (parsed.resolvedIssues.length === 0) {
@@ -18686,10 +19077,10 @@ async function archiveResolvedIssues(options) {
18686
19077
  const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
18687
19078
  const archiveFile = path3.join(archiveDir, `USER_FEEDBACK_archive_${currentYear}.md`);
18688
19079
  {
18689
- await fs2.ensureDir(archiveDir);
19080
+ await fs3.ensureDir(archiveDir);
18690
19081
  await appendToArchive(archiveFile, parsed.resolvedIssues);
18691
19082
  const compactedContent = generateCompactedFeedback(parsed.openIssues, parsed.metadata);
18692
- await fs2.writeFile(feedbackFile, compactedContent, "utf-8");
19083
+ await fs3.writeFile(feedbackFile, compactedContent, "utf-8");
18693
19084
  }
18694
19085
  {
18695
19086
  console.log(chalk15.green(`\u2705 Archived ${parsed.resolvedIssues.length} resolved issues`));
@@ -18736,8 +19127,8 @@ function parseUserFeedback(content) {
18736
19127
  async function appendToArchive(archiveFile, resolvedIssues) {
18737
19128
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
18738
19129
  let archiveContent = "";
18739
- if (await fs2.pathExists(archiveFile)) {
18740
- archiveContent = await fs2.readFile(archiveFile, "utf-8");
19130
+ if (await fs3.pathExists(archiveFile)) {
19131
+ archiveContent = await fs3.readFile(archiveFile, "utf-8");
18741
19132
  } else {
18742
19133
  const year = path3.basename(archiveFile).match(/(\d{4})/)?.[1] || (/* @__PURE__ */ new Date()).getFullYear();
18743
19134
  archiveContent = `# User Feedback Archive ${year}
@@ -18771,7 +19162,7 @@ ${resolvedIssue}
18771
19162
  `- Last updated: ${timestamp}`
18772
19163
  );
18773
19164
  }
18774
- await fs2.writeFile(archiveFile, archiveContent, "utf-8");
19165
+ await fs3.writeFile(archiveFile, archiveContent, "utf-8");
18775
19166
  }
18776
19167
  function generateCompactedFeedback(openIssues, metadata) {
18777
19168
  let content = metadata.trim() + "\n";
@@ -18796,15 +19187,15 @@ async function shouldArchive(feedbackFile, options = {}) {
18796
19187
  // 50KB
18797
19188
  lineCountThreshold = 500
18798
19189
  } = options;
18799
- if (!await fs2.pathExists(feedbackFile)) {
19190
+ if (!await fs3.pathExists(feedbackFile)) {
18800
19191
  return {
18801
19192
  shouldArchive: false,
18802
19193
  reasons: [],
18803
19194
  stats: { openIssuesCount: 0, resolvedIssuesCount: 0, fileSizeBytes: 0, lineCount: 0 }
18804
19195
  };
18805
19196
  }
18806
- const content = await fs2.readFile(feedbackFile, "utf-8");
18807
- const stats = await fs2.stat(feedbackFile);
19197
+ const content = await fs3.readFile(feedbackFile, "utf-8");
19198
+ const stats = await fs3.stat(feedbackFile);
18808
19199
  const parsed = parseUserFeedback(content);
18809
19200
  const lineCount = content.split("\n").length;
18810
19201
  const reasons = [];
@@ -18878,16 +19269,16 @@ var EnhancedFeedbackFileManager = class {
18878
19269
  this.feedbackFile = feedbackFile;
18879
19270
  }
18880
19271
  async ensureExists() {
18881
- if (!await fs2.pathExists(this.feedbackFile)) {
19272
+ if (!await fs3.pathExists(this.feedbackFile)) {
18882
19273
  await this.createInitialFile();
18883
19274
  }
18884
19275
  }
18885
19276
  async addFeedback(issue, testCriteria) {
18886
19277
  await this.ensureExists();
18887
19278
  try {
18888
- const content = await fs2.readFile(this.feedbackFile, "utf-8");
19279
+ const content = await fs3.readFile(this.feedbackFile, "utf-8");
18889
19280
  const updatedContent = this.addIssueToContent(content, issue, testCriteria);
18890
- await fs2.writeFile(this.feedbackFile, updatedContent, "utf-8");
19281
+ await fs3.writeFile(this.feedbackFile, updatedContent, "utf-8");
18891
19282
  } catch (error) {
18892
19283
  throw new ValidationError(
18893
19284
  `Failed to save feedback: ${error}`,
@@ -18900,12 +19291,12 @@ var EnhancedFeedbackFileManager = class {
18900
19291
  */
18901
19292
  async repairMalformedFile() {
18902
19293
  try {
18903
- const content = await fs2.readFile(this.feedbackFile, "utf-8");
19294
+ const content = await fs3.readFile(this.feedbackFile, "utf-8");
18904
19295
  const hasOpenIssues = content.includes("<OPEN_ISSUES>");
18905
19296
  const hasClosingTag = content.includes("</OPEN_ISSUES>");
18906
19297
  if (!hasOpenIssues || !hasClosingTag) {
18907
19298
  const backupPath = this.feedbackFile + ".backup." + Date.now();
18908
- await fs2.writeFile(backupPath, content, "utf-8");
19299
+ await fs3.writeFile(backupPath, content, "utf-8");
18909
19300
  const existingIssues = this.extractIssuesFromMalformedContent(content);
18910
19301
  await this.createInitialFile(existingIssues);
18911
19302
  }
@@ -18937,8 +19328,8 @@ List any features you'd like to see added or bugs you've encountered.
18937
19328
 
18938
19329
  <!-- Resolved issues will be moved here -->
18939
19330
  `;
18940
- await fs2.ensureDir(path3.dirname(this.feedbackFile));
18941
- await fs2.writeFile(this.feedbackFile, initialContent, "utf-8");
19331
+ await fs3.ensureDir(path3.dirname(this.feedbackFile));
19332
+ await fs3.writeFile(this.feedbackFile, initialContent, "utf-8");
18942
19333
  }
18943
19334
  addIssueToContent(content, issue, testCriteria) {
18944
19335
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -19054,17 +19445,17 @@ async function handleCompactCommand(subArgs, options) {
19054
19445
  if (subArgs.length > 0) {
19055
19446
  for (const filePath of subArgs) {
19056
19447
  const resolvedPath = path3.resolve(filePath);
19057
- if (await fs2.pathExists(resolvedPath)) {
19448
+ if (await fs3.pathExists(resolvedPath)) {
19058
19449
  filesToCompact.push(resolvedPath);
19059
19450
  } else {
19060
19451
  console.warn(chalk15.yellow(`\u26A0\uFE0F File not found: ${filePath}`));
19061
19452
  }
19062
19453
  }
19063
19454
  } else {
19064
- if (await fs2.pathExists(defaultClaudeFile)) {
19455
+ if (await fs3.pathExists(defaultClaudeFile)) {
19065
19456
  filesToCompact.push(defaultClaudeFile);
19066
19457
  }
19067
- if (await fs2.pathExists(defaultAgentsFile)) {
19458
+ if (await fs3.pathExists(defaultAgentsFile)) {
19068
19459
  filesToCompact.push(defaultAgentsFile);
19069
19460
  }
19070
19461
  }
@@ -19998,11 +20389,11 @@ var GitManager = class {
19998
20389
  */
19999
20390
  async updateJunoTaskConfig(gitUrl) {
20000
20391
  const configPath = path3.join(this.workingDirectory, ".juno_task", "init.md");
20001
- if (!await fs2.pathExists(configPath)) {
20392
+ if (!await fs3.pathExists(configPath)) {
20002
20393
  return;
20003
20394
  }
20004
20395
  try {
20005
- let content = await fs2.readFile(configPath, "utf-8");
20396
+ let content = await fs3.readFile(configPath, "utf-8");
20006
20397
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
20007
20398
  if (frontmatterMatch) {
20008
20399
  let frontmatter = frontmatterMatch[1];
@@ -20023,7 +20414,7 @@ GIT_URL: ${gitUrl}
20023
20414
  `;
20024
20415
  content = frontmatter + content;
20025
20416
  }
20026
- await fs2.writeFile(configPath, content, "utf-8");
20417
+ await fs3.writeFile(configPath, content, "utf-8");
20027
20418
  } catch (error) {
20028
20419
  console.warn(`Warning: Failed to update juno-code configuration: ${error}`);
20029
20420
  }
@@ -20955,7 +21346,7 @@ var LogViewer = ({
20955
21346
  init_types();
20956
21347
  async function exportLogs(logger2, filepath, options) {
20957
21348
  try {
20958
- const fs21 = await import('fs-extra');
21349
+ const fs22 = await import('fs-extra');
20959
21350
  let entries = logger2.getRecentEntries(options.tail || 1e3);
20960
21351
  if (options.level) {
20961
21352
  const level = LogLevel[options.level.toUpperCase()];
@@ -20985,7 +21376,7 @@ async function exportLogs(logger2, filepath, options) {
20985
21376
  },
20986
21377
  entries
20987
21378
  };
20988
- await fs21.writeFile(filepath, JSON.stringify(exportData, null, 2));
21379
+ await fs22.writeFile(filepath, JSON.stringify(exportData, null, 2));
20989
21380
  console.log(chalk15.green(`\u2705 Exported ${entries.length} log entries to: ${filepath}`));
20990
21381
  } catch (error) {
20991
21382
  console.error(chalk15.red(`\u274C Failed to export logs: ${error}`));
@@ -22529,7 +22920,7 @@ function createServicesCommand() {
22529
22920
  const servicesCmd = new Command("services").description("Manage juno-code service scripts").addHelpText("after", `
22530
22921
  Examples:
22531
22922
  $ juno-code services install Install service scripts to ~/.juno_code/services/
22532
- $ juno-code services install --force Reinstall/refresh service scripts (codex.py/claude.py)
22923
+ $ juno-code services install --force Reinstall/refresh service scripts (codex.py/claude.py/gemini.py)
22533
22924
  $ juno-code services list List installed service scripts
22534
22925
  $ juno-code services status Check installation status
22535
22926
  $ juno-code services uninstall Remove all service scripts
@@ -22786,10 +23177,10 @@ autoload -U compinit && compinit`;
22786
23177
  */
22787
23178
  async isSourceCommandPresent(configPath, sourceCommand) {
22788
23179
  try {
22789
- if (!await fs2.pathExists(configPath)) {
23180
+ if (!await fs3.pathExists(configPath)) {
22790
23181
  return false;
22791
23182
  }
22792
- const content = await fs2.readFile(configPath, "utf-8");
23183
+ const content = await fs3.readFile(configPath, "utf-8");
22793
23184
  return content.includes("juno-code completion");
22794
23185
  } catch {
22795
23186
  return false;
@@ -22811,15 +23202,15 @@ autoload -U compinit && compinit`;
22811
23202
  continue;
22812
23203
  }
22813
23204
  try {
22814
- const completionExists = await fs2.pathExists(shell.completionPath);
22815
- const configExists = await fs2.pathExists(shell.configPath);
23205
+ const completionExists = await fs3.pathExists(shell.completionPath);
23206
+ const configExists = await fs3.pathExists(shell.configPath);
22816
23207
  const isSourced = configExists && await this.isSourceCommandPresent(
22817
23208
  shell.configPath,
22818
23209
  this.getSourceCommand(shell.name, shell.completionPath)
22819
23210
  );
22820
23211
  let lastInstalled;
22821
23212
  if (completionExists) {
22822
- const stats = await fs2.stat(shell.completionPath);
23213
+ const stats = await fs3.stat(shell.completionPath);
22823
23214
  lastInstalled = stats.mtime;
22824
23215
  }
22825
23216
  statuses.push({
@@ -22845,7 +23236,7 @@ autoload -U compinit && compinit`;
22845
23236
  async ensureCompletionDirectory(shell) {
22846
23237
  const completionPath = this.getCompletionPath(shell);
22847
23238
  const completionDir = path3.dirname(completionPath);
22848
- await fs2.ensureDir(completionDir);
23239
+ await fs3.ensureDir(completionDir);
22849
23240
  }
22850
23241
  /**
22851
23242
  * Ensure directory exists for shell configuration
@@ -22853,7 +23244,7 @@ autoload -U compinit && compinit`;
22853
23244
  async ensureConfigDirectory(shell) {
22854
23245
  const configPath = this.getConfigPath(shell);
22855
23246
  const configDir = path3.dirname(configPath);
22856
- await fs2.ensureDir(configDir);
23247
+ await fs3.ensureDir(configDir);
22857
23248
  }
22858
23249
  /**
22859
23250
  * Get shell version information
@@ -22877,15 +23268,15 @@ autoload -U compinit && compinit`;
22877
23268
  try {
22878
23269
  const configPath = this.getConfigPath(shell);
22879
23270
  const configDir = path3.dirname(configPath);
22880
- await fs2.access(configDir, fs2.constants.W_OK);
23271
+ await fs3.access(configDir, fs3.constants.W_OK);
22881
23272
  } catch {
22882
23273
  issues.push(`Cannot write to ${shell} configuration directory`);
22883
23274
  }
22884
23275
  try {
22885
23276
  const completionPath = this.getCompletionPath(shell);
22886
23277
  const completionDir = path3.dirname(completionPath);
22887
- await fs2.ensureDir(completionDir);
22888
- await fs2.access(completionDir, fs2.constants.W_OK);
23278
+ await fs3.ensureDir(completionDir);
23279
+ await fs3.access(completionDir, fs3.constants.W_OK);
22889
23280
  } catch {
22890
23281
  issues.push(`Cannot write to ${shell} completion directory`);
22891
23282
  }
@@ -22956,10 +23347,10 @@ var ContextAwareCompletion = class {
22956
23347
  async getSessionIds() {
22957
23348
  try {
22958
23349
  const sessionDir = path3.join(process.cwd(), ".juno_task", "sessions");
22959
- if (!await fs2.pathExists(sessionDir)) {
23350
+ if (!await fs3.pathExists(sessionDir)) {
22960
23351
  return [];
22961
23352
  }
22962
- const sessions = await fs2.readdir(sessionDir);
23353
+ const sessions = await fs3.readdir(sessionDir);
22963
23354
  return sessions.filter((name) => name.match(/^session_\d+$/)).map((name) => name.replace("session_", "")).sort((a, b) => parseInt(b) - parseInt(a));
22964
23355
  } catch {
22965
23356
  return [];
@@ -22973,8 +23364,8 @@ var ContextAwareCompletion = class {
22973
23364
  const builtinTemplates = ["basic", "advanced", "research", "development"];
22974
23365
  const customTemplatesDir = path3.join(process.cwd(), ".juno_task", "templates");
22975
23366
  let customTemplates = [];
22976
- if (await fs2.pathExists(customTemplatesDir)) {
22977
- const files = await fs2.readdir(customTemplatesDir);
23367
+ if (await fs3.pathExists(customTemplatesDir)) {
23368
+ const files = await fs3.readdir(customTemplatesDir);
22978
23369
  customTemplates = files.filter((name) => name.endsWith(".md") || name.endsWith(".hbs")).map((name) => path3.basename(name, path3.extname(name)));
22979
23370
  }
22980
23371
  return [...builtinTemplates, ...customTemplates].sort();
@@ -23051,7 +23442,7 @@ var CompletionInstaller = class {
23051
23442
  await this.shellDetector.ensureConfigDirectory(shell);
23052
23443
  const script = this.generateEnhancedCompletion(shell, "juno-code");
23053
23444
  const completionPath = this.shellDetector.getCompletionPath(shell);
23054
- await fs2.writeFile(completionPath, script, "utf-8");
23445
+ await fs3.writeFile(completionPath, script, "utf-8");
23055
23446
  const configPath = this.shellDetector.getConfigPath(shell);
23056
23447
  const sourceCommand = this.shellDetector.getSourceCommand(shell, completionPath);
23057
23448
  const warnings = [];
@@ -23059,7 +23450,7 @@ var CompletionInstaller = class {
23059
23450
  const isPresent = await this.shellDetector.isSourceCommandPresent(configPath, sourceCommand);
23060
23451
  if (!isPresent) {
23061
23452
  try {
23062
- await fs2.appendFile(configPath, `
23453
+ await fs3.appendFile(configPath, `
23063
23454
 
23064
23455
  ${sourceCommand}
23065
23456
  `);
@@ -23090,8 +23481,8 @@ ${sourceCommand}
23090
23481
  async uninstall(shell) {
23091
23482
  try {
23092
23483
  const completionPath = this.shellDetector.getCompletionPath(shell);
23093
- if (await fs2.pathExists(completionPath)) {
23094
- await fs2.remove(completionPath);
23484
+ if (await fs3.pathExists(completionPath)) {
23485
+ await fs3.remove(completionPath);
23095
23486
  return true;
23096
23487
  }
23097
23488
  return false;
@@ -23105,7 +23496,7 @@ ${sourceCommand}
23105
23496
  async isInstalled(shell) {
23106
23497
  try {
23107
23498
  const completionPath = this.shellDetector.getCompletionPath(shell);
23108
- return await fs2.pathExists(completionPath);
23499
+ return await fs3.pathExists(completionPath);
23109
23500
  } catch {
23110
23501
  return false;
23111
23502
  }
@@ -23906,11 +24297,11 @@ function setupMainCommand(program) {
23906
24297
  );
23907
24298
  const allOptions2 = { ...definedGlobalOptions, ...options };
23908
24299
  if (!globalOptions.subagent && !options.prompt && !options.interactive && !options.interactivePrompt) {
23909
- const fs21 = await import('fs-extra');
23910
- const path23 = await import('path');
24300
+ const fs22 = await import('fs-extra');
24301
+ const path24 = await import('path');
23911
24302
  const cwd2 = process.cwd();
23912
- const junoTaskDir = path23.join(cwd2, ".juno_task");
23913
- if (await fs21.pathExists(junoTaskDir)) {
24303
+ const junoTaskDir = path24.join(cwd2, ".juno_task");
24304
+ if (await fs22.pathExists(junoTaskDir)) {
23914
24305
  console.log(chalk15.blue.bold("\u{1F3AF} Juno Code - Auto-detected Initialized Project\n"));
23915
24306
  try {
23916
24307
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
@@ -23927,12 +24318,12 @@ function setupMainCommand(program) {
23927
24318
  allOptions2.subagent = config.defaultSubagent;
23928
24319
  console.log(chalk15.gray(`\u{1F916} Using configured subagent: ${chalk15.cyan(config.defaultSubagent)}`));
23929
24320
  }
23930
- const promptFile = path23.join(junoTaskDir, "prompt.md");
23931
- if (!allOptions2.prompt && await fs21.pathExists(promptFile)) {
24321
+ const promptFile = path24.join(junoTaskDir, "prompt.md");
24322
+ if (!allOptions2.prompt && await fs22.pathExists(promptFile)) {
23932
24323
  allOptions2.prompt = promptFile;
23933
24324
  console.log(chalk15.gray(`\u{1F4C4} Using default prompt: ${chalk15.cyan(".juno_task/prompt.md")}`));
23934
24325
  }
23935
- if (allOptions2.subagent && (allOptions2.prompt || await fs21.pathExists(promptFile))) {
24326
+ if (allOptions2.subagent && (allOptions2.prompt || await fs22.pathExists(promptFile))) {
23936
24327
  console.log(chalk15.green("\u2713 Auto-detected project configuration\n"));
23937
24328
  const { mainCommandHandler: mainCommandHandler3 } = await Promise.resolve().then(() => (init_main(), main_exports));
23938
24329
  await mainCommandHandler3([], allOptions2, command);
@@ -24089,6 +24480,17 @@ async function main() {
24089
24480
  console.error("[DEBUG] Service auto-update failed:", error instanceof Error ? error.message : String(error));
24090
24481
  }
24091
24482
  }
24483
+ try {
24484
+ const { ScriptInstaller: ScriptInstaller2 } = await Promise.resolve().then(() => (init_script_installer(), script_installer_exports));
24485
+ const updated = await ScriptInstaller2.autoUpdate(process.cwd(), true);
24486
+ if (updated && (process.argv.includes("--verbose") || process.argv.includes("-v") || process.env.JUNO_CODE_DEBUG === "1")) {
24487
+ console.error("[DEBUG] Project scripts auto-updated in .juno_task/scripts/");
24488
+ }
24489
+ } catch (error) {
24490
+ if (process.env.JUNO_CODE_DEBUG === "1") {
24491
+ console.error("[DEBUG] Script auto-update failed:", error instanceof Error ? error.message : String(error));
24492
+ }
24493
+ }
24092
24494
  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");
24093
24495
  setupGlobalOptions(program);
24094
24496
  const isVerbose = process.argv.includes("--verbose") || process.argv.includes("-v");
@@ -24096,10 +24498,10 @@ async function main() {
24096
24498
  const isHelpOrVersion = process.argv.includes("--help") || process.argv.includes("-h") || process.argv.includes("--version") || process.argv.includes("-V");
24097
24499
  const hasNoArguments = process.argv.length <= 2;
24098
24500
  const isInitCommand = process.argv.includes("init");
24099
- const fs21 = await import('fs-extra');
24100
- const path23 = await import('path');
24101
- const junoTaskDir = path23.join(process.cwd(), ".juno_task");
24102
- const isInitialized = await fs21.pathExists(junoTaskDir);
24501
+ const fs22 = await import('fs-extra');
24502
+ const path24 = await import('path');
24503
+ const junoTaskDir = path24.join(process.cwd(), ".juno_task");
24504
+ const isInitialized = await fs22.pathExists(junoTaskDir);
24103
24505
  if (!isHelpOrVersion && !hasNoArguments && !isInitCommand && isInitialized) {
24104
24506
  try {
24105
24507
  const { validateStartupConfigs: validateStartupConfigs2 } = await Promise.resolve().then(() => (init_startup_validation(), startup_validation_exports));