archondev 2.0.0 → 2.1.1
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/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
cloudLogs,
|
|
8
8
|
cloudStatus,
|
|
9
9
|
execute
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-XRWIU3RD.js";
|
|
11
11
|
import {
|
|
12
12
|
list
|
|
13
13
|
} from "./chunk-N73LAU7W.js";
|
|
@@ -71,6 +71,7 @@ import {
|
|
|
71
71
|
import "./chunk-A7QU6JC6.js";
|
|
72
72
|
import "./chunk-SMR7JQK6.js";
|
|
73
73
|
import {
|
|
74
|
+
getAuthToken,
|
|
74
75
|
loadConfig
|
|
75
76
|
} from "./chunk-IVY5AHPS.js";
|
|
76
77
|
import {
|
|
@@ -80,7 +81,7 @@ import "./chunk-QGM4M3NI.js";
|
|
|
80
81
|
|
|
81
82
|
// src/cli/index.ts
|
|
82
83
|
import { Command as Command4 } from "commander";
|
|
83
|
-
import
|
|
84
|
+
import chalk14 from "chalk";
|
|
84
85
|
import "dotenv/config";
|
|
85
86
|
|
|
86
87
|
// src/cli/promote.ts
|
|
@@ -299,10 +300,10 @@ function formatDateTime(date) {
|
|
|
299
300
|
}
|
|
300
301
|
|
|
301
302
|
// src/cli/start.ts
|
|
302
|
-
import
|
|
303
|
+
import chalk4 from "chalk";
|
|
303
304
|
import readline from "readline";
|
|
304
|
-
import { existsSync as
|
|
305
|
-
import { join as
|
|
305
|
+
import { existsSync as existsSync4, readFileSync, readdirSync as readdirSync2, appendFileSync } from "fs";
|
|
306
|
+
import { join as join4 } from "path";
|
|
306
307
|
|
|
307
308
|
// src/core/context/manager.ts
|
|
308
309
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -399,44 +400,479 @@ var ContextManager = class _ContextManager {
|
|
|
399
400
|
async clearPendingAtoms(cwd) {
|
|
400
401
|
const filePath = join2(cwd, _ContextManager.PENDING_ATOMS_FILE);
|
|
401
402
|
if (existsSync2(filePath)) {
|
|
402
|
-
const { unlink } = await import("fs/promises");
|
|
403
|
-
await
|
|
403
|
+
const { unlink: unlink2 } = await import("fs/promises");
|
|
404
|
+
await unlink2(filePath);
|
|
404
405
|
}
|
|
405
406
|
}
|
|
406
407
|
};
|
|
407
408
|
|
|
409
|
+
// src/cli/cleanup.ts
|
|
410
|
+
import chalk3 from "chalk";
|
|
411
|
+
import { existsSync as existsSync3, readdirSync, statSync } from "fs";
|
|
412
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2, unlink, readdir, stat } from "fs/promises";
|
|
413
|
+
import { join as join3 } from "path";
|
|
414
|
+
import yaml from "yaml";
|
|
415
|
+
import { execSync } from "child_process";
|
|
416
|
+
var CONFIG_PATH = ".archon/config.yaml";
|
|
417
|
+
var PROGRESS_FILE = "progress.txt";
|
|
418
|
+
var ARCHON_DIR = ".archon";
|
|
419
|
+
var CACHE_DIR = ".archon/cache";
|
|
420
|
+
var ARCHIVE_DIR = "docs/archive";
|
|
421
|
+
function formatBytes(bytes) {
|
|
422
|
+
if (bytes === 0) return "0 B";
|
|
423
|
+
const k = 1024;
|
|
424
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
425
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
426
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
427
|
+
}
|
|
428
|
+
function getDirSize(dirPath) {
|
|
429
|
+
if (!existsSync3(dirPath)) return 0;
|
|
430
|
+
let totalSize = 0;
|
|
431
|
+
try {
|
|
432
|
+
const files = readdirSync(dirPath, { withFileTypes: true });
|
|
433
|
+
for (const file of files) {
|
|
434
|
+
const filePath = join3(dirPath, file.name);
|
|
435
|
+
if (file.isDirectory()) {
|
|
436
|
+
totalSize += getDirSize(filePath);
|
|
437
|
+
} else {
|
|
438
|
+
try {
|
|
439
|
+
totalSize += statSync(filePath).size;
|
|
440
|
+
} catch {
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
} catch {
|
|
445
|
+
}
|
|
446
|
+
return totalSize;
|
|
447
|
+
}
|
|
448
|
+
async function loadCleanupConfig(cwd) {
|
|
449
|
+
const configPath = join3(cwd, CONFIG_PATH);
|
|
450
|
+
if (!existsSync3(configPath)) {
|
|
451
|
+
return void 0;
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
const content = await readFile3(configPath, "utf-8");
|
|
455
|
+
const config = yaml.parse(content);
|
|
456
|
+
return config.cleanup;
|
|
457
|
+
} catch {
|
|
458
|
+
return void 0;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async function saveCleanupConfig(cwd, cleanup) {
|
|
462
|
+
const configPath = join3(cwd, CONFIG_PATH);
|
|
463
|
+
const archonDir = join3(cwd, ARCHON_DIR);
|
|
464
|
+
if (!existsSync3(archonDir)) {
|
|
465
|
+
await mkdir2(archonDir, { recursive: true });
|
|
466
|
+
}
|
|
467
|
+
let existing = {};
|
|
468
|
+
if (existsSync3(configPath)) {
|
|
469
|
+
try {
|
|
470
|
+
const content = await readFile3(configPath, "utf-8");
|
|
471
|
+
existing = yaml.parse(content);
|
|
472
|
+
} catch {
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
existing.cleanup = cleanup;
|
|
476
|
+
await writeFile3(configPath, yaml.stringify(existing), "utf-8");
|
|
477
|
+
}
|
|
478
|
+
function getOrphanedWorktrees(cwd) {
|
|
479
|
+
const orphaned = [];
|
|
480
|
+
try {
|
|
481
|
+
const output = execSync("git worktree list --porcelain", {
|
|
482
|
+
cwd,
|
|
483
|
+
encoding: "utf-8"
|
|
484
|
+
});
|
|
485
|
+
const lines = output.split("\n");
|
|
486
|
+
let currentWorktree = "";
|
|
487
|
+
for (const line of lines) {
|
|
488
|
+
if (line.startsWith("worktree ")) {
|
|
489
|
+
currentWorktree = line.replace("worktree ", "");
|
|
490
|
+
} else if (line.startsWith("branch ")) {
|
|
491
|
+
if (currentWorktree.includes(".archon-worktree") || currentWorktree.includes("archon-parallel")) {
|
|
492
|
+
if (!existsSync3(join3(currentWorktree, ".git"))) {
|
|
493
|
+
orphaned.push(currentWorktree);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
} catch {
|
|
499
|
+
}
|
|
500
|
+
return orphaned;
|
|
501
|
+
}
|
|
502
|
+
async function getStaleFiles(dirPath, maxAgeDays) {
|
|
503
|
+
const staleFiles = [];
|
|
504
|
+
if (!existsSync3(dirPath)) return staleFiles;
|
|
505
|
+
const cutoffTime = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
506
|
+
try {
|
|
507
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
508
|
+
for (const entry of entries) {
|
|
509
|
+
const fullPath = join3(dirPath, entry.name);
|
|
510
|
+
if (entry.isFile()) {
|
|
511
|
+
try {
|
|
512
|
+
const stats = await stat(fullPath);
|
|
513
|
+
if (stats.mtimeMs < cutoffTime) {
|
|
514
|
+
staleFiles.push({ path: fullPath, size: stats.size });
|
|
515
|
+
}
|
|
516
|
+
} catch {
|
|
517
|
+
}
|
|
518
|
+
} else if (entry.isDirectory()) {
|
|
519
|
+
const nested = await getStaleFiles(fullPath, maxAgeDays);
|
|
520
|
+
staleFiles.push(...nested);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
} catch {
|
|
524
|
+
}
|
|
525
|
+
return staleFiles;
|
|
526
|
+
}
|
|
527
|
+
async function parseProgressEntries(content) {
|
|
528
|
+
const entries = [];
|
|
529
|
+
const lines = content.split("\n");
|
|
530
|
+
let currentEntry = "";
|
|
531
|
+
let currentDate = null;
|
|
532
|
+
const datePatterns = [
|
|
533
|
+
/^\[(\d{4}-\d{2}-\d{2})/,
|
|
534
|
+
/^## (\d{4}-\d{2}-\d{2})/,
|
|
535
|
+
/^### (\d{4}-\d{2}-\d{2})/,
|
|
536
|
+
/^(\d{4}-\d{2}-\d{2})/
|
|
537
|
+
];
|
|
538
|
+
for (const line of lines) {
|
|
539
|
+
let dateMatch = null;
|
|
540
|
+
for (const pattern of datePatterns) {
|
|
541
|
+
const match = line.match(pattern);
|
|
542
|
+
if (match?.[1]) {
|
|
543
|
+
dateMatch = match[1];
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (dateMatch) {
|
|
548
|
+
if (currentDate && currentEntry.trim()) {
|
|
549
|
+
entries.push({ date: currentDate, content: currentEntry.trim() });
|
|
550
|
+
}
|
|
551
|
+
currentDate = new Date(dateMatch);
|
|
552
|
+
currentEntry = line + "\n";
|
|
553
|
+
} else {
|
|
554
|
+
currentEntry += line + "\n";
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (currentDate && currentEntry.trim()) {
|
|
558
|
+
entries.push({ date: currentDate, content: currentEntry.trim() });
|
|
559
|
+
}
|
|
560
|
+
return entries;
|
|
561
|
+
}
|
|
562
|
+
async function cleanupCheck() {
|
|
563
|
+
const cwd = process.cwd();
|
|
564
|
+
const results = [];
|
|
565
|
+
console.log(chalk3.blue("\n\u{1F50D} Analyzing workspace for maintenance needs...\n"));
|
|
566
|
+
const progressPath = join3(cwd, PROGRESS_FILE);
|
|
567
|
+
if (existsSync3(progressPath)) {
|
|
568
|
+
const size = statSync(progressPath).size;
|
|
569
|
+
let status2 = "ok";
|
|
570
|
+
let recommendation;
|
|
571
|
+
if (size > 200 * 1024) {
|
|
572
|
+
status2 = "critical";
|
|
573
|
+
recommendation = "Archive old entries with: archon cleanup run";
|
|
574
|
+
} else if (size > 100 * 1024) {
|
|
575
|
+
status2 = "warn";
|
|
576
|
+
recommendation = "Consider archiving entries older than 30 days";
|
|
577
|
+
}
|
|
578
|
+
results.push({
|
|
579
|
+
name: "progress.txt",
|
|
580
|
+
size,
|
|
581
|
+
sizeFormatted: formatBytes(size),
|
|
582
|
+
status: status2,
|
|
583
|
+
recommendation
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
const archonPath = join3(cwd, ARCHON_DIR);
|
|
587
|
+
if (existsSync3(archonPath)) {
|
|
588
|
+
const size = getDirSize(archonPath);
|
|
589
|
+
let status2 = "ok";
|
|
590
|
+
let recommendation;
|
|
591
|
+
if (size > 10 * 1024 * 1024) {
|
|
592
|
+
status2 = "warn";
|
|
593
|
+
recommendation = "Clear stale cache files with: archon cleanup run";
|
|
594
|
+
}
|
|
595
|
+
results.push({
|
|
596
|
+
name: ".archon/",
|
|
597
|
+
size,
|
|
598
|
+
sizeFormatted: formatBytes(size),
|
|
599
|
+
status: status2,
|
|
600
|
+
recommendation
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
const nodeModulesPath = join3(cwd, "node_modules");
|
|
604
|
+
if (existsSync3(nodeModulesPath)) {
|
|
605
|
+
const size = getDirSize(nodeModulesPath);
|
|
606
|
+
results.push({
|
|
607
|
+
name: "node_modules/",
|
|
608
|
+
size,
|
|
609
|
+
sizeFormatted: formatBytes(size),
|
|
610
|
+
status: "ok",
|
|
611
|
+
recommendation: "Standard npm dependencies (informational)"
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
const orphanedWorktrees = getOrphanedWorktrees(cwd);
|
|
615
|
+
if (orphanedWorktrees.length > 0) {
|
|
616
|
+
results.push({
|
|
617
|
+
name: "Orphaned Worktrees",
|
|
618
|
+
size: orphanedWorktrees.length,
|
|
619
|
+
sizeFormatted: `${orphanedWorktrees.length} found`,
|
|
620
|
+
status: "warn",
|
|
621
|
+
recommendation: "Clean up with: archon cleanup run"
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
const cachePath = join3(cwd, CACHE_DIR);
|
|
625
|
+
if (existsSync3(cachePath)) {
|
|
626
|
+
const staleCache = await getStaleFiles(cachePath, 7);
|
|
627
|
+
if (staleCache.length > 0) {
|
|
628
|
+
const totalSize = staleCache.reduce((sum, f) => sum + f.size, 0);
|
|
629
|
+
results.push({
|
|
630
|
+
name: "Stale Cache Files",
|
|
631
|
+
size: totalSize,
|
|
632
|
+
sizeFormatted: `${staleCache.length} files (${formatBytes(totalSize)})`,
|
|
633
|
+
status: "warn",
|
|
634
|
+
recommendation: "Clean up with: archon cleanup run"
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
console.log(chalk3.bold("Workspace Analysis Summary\n"));
|
|
639
|
+
console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
640
|
+
console.log("\u2502 Item \u2502 Size \u2502 Status \u2502");
|
|
641
|
+
console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
642
|
+
for (const result of results) {
|
|
643
|
+
const statusIcon = result.status === "critical" ? chalk3.red("\u2717") : result.status === "warn" ? chalk3.yellow("!") : chalk3.green("\u2713");
|
|
644
|
+
const name = result.name.padEnd(23);
|
|
645
|
+
const size = result.sizeFormatted.padEnd(14);
|
|
646
|
+
console.log(`\u2502 ${name} \u2502 ${size} \u2502 ${statusIcon} \u2502`);
|
|
647
|
+
}
|
|
648
|
+
console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
649
|
+
console.log();
|
|
650
|
+
const recommendations = results.filter((r) => r.recommendation && r.status !== "ok");
|
|
651
|
+
if (recommendations.length > 0) {
|
|
652
|
+
console.log(chalk3.yellow("Recommendations:\n"));
|
|
653
|
+
for (const rec of recommendations) {
|
|
654
|
+
console.log(` ${chalk3.dim("\u2022")} ${rec.name}: ${rec.recommendation}`);
|
|
655
|
+
}
|
|
656
|
+
console.log();
|
|
657
|
+
} else {
|
|
658
|
+
console.log(chalk3.green("\u2713 Workspace is in good shape!\n"));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
async function cleanupRun() {
|
|
662
|
+
const cwd = process.cwd();
|
|
663
|
+
const results = [];
|
|
664
|
+
let totalSaved = 0;
|
|
665
|
+
console.log(chalk3.blue("\n\u{1F9F9} Running workspace cleanup...\n"));
|
|
666
|
+
const progressPath = join3(cwd, PROGRESS_FILE);
|
|
667
|
+
if (existsSync3(progressPath)) {
|
|
668
|
+
const content = await readFile3(progressPath, "utf-8");
|
|
669
|
+
const entries = await parseProgressEntries(content);
|
|
670
|
+
const cutoffDate = /* @__PURE__ */ new Date();
|
|
671
|
+
cutoffDate.setDate(cutoffDate.getDate() - 30);
|
|
672
|
+
const oldEntries = entries.filter((e) => e.date < cutoffDate);
|
|
673
|
+
const recentEntries = entries.filter((e) => e.date >= cutoffDate);
|
|
674
|
+
if (oldEntries.length > 0) {
|
|
675
|
+
const archiveDir = join3(cwd, ARCHIVE_DIR);
|
|
676
|
+
if (!existsSync3(archiveDir)) {
|
|
677
|
+
await mkdir2(archiveDir, { recursive: true });
|
|
678
|
+
}
|
|
679
|
+
const archiveFileName = `progress-archive-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.txt`;
|
|
680
|
+
const archivePath = join3(archiveDir, archiveFileName);
|
|
681
|
+
const archiveContent = oldEntries.map((e) => e.content).join("\n\n---\n\n");
|
|
682
|
+
await writeFile3(archivePath, archiveContent, "utf-8");
|
|
683
|
+
const originalSize = statSync(progressPath).size;
|
|
684
|
+
const newContent = recentEntries.map((e) => e.content).join("\n\n");
|
|
685
|
+
await writeFile3(progressPath, newContent, "utf-8");
|
|
686
|
+
const newSize = statSync(progressPath).size;
|
|
687
|
+
const saved = originalSize - newSize;
|
|
688
|
+
results.push({
|
|
689
|
+
name: "progress.txt",
|
|
690
|
+
action: `Archived ${oldEntries.length} entries to ${archiveFileName}`,
|
|
691
|
+
spaceSaved: saved,
|
|
692
|
+
spaceSavedFormatted: formatBytes(saved)
|
|
693
|
+
});
|
|
694
|
+
totalSaved += saved;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const orphanedWorktrees = getOrphanedWorktrees(cwd);
|
|
698
|
+
for (const worktree of orphanedWorktrees) {
|
|
699
|
+
try {
|
|
700
|
+
execSync(`git worktree remove --force "${worktree}"`, {
|
|
701
|
+
cwd,
|
|
702
|
+
encoding: "utf-8"
|
|
703
|
+
});
|
|
704
|
+
results.push({
|
|
705
|
+
name: "Worktree",
|
|
706
|
+
action: `Removed orphaned worktree: ${worktree}`,
|
|
707
|
+
spaceSaved: 0,
|
|
708
|
+
spaceSavedFormatted: "N/A"
|
|
709
|
+
});
|
|
710
|
+
} catch {
|
|
711
|
+
console.log(chalk3.yellow(` Could not remove worktree: ${worktree}`));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
const cachePath = join3(cwd, CACHE_DIR);
|
|
715
|
+
if (existsSync3(cachePath)) {
|
|
716
|
+
const staleCache = await getStaleFiles(cachePath, 7);
|
|
717
|
+
let cacheSaved = 0;
|
|
718
|
+
for (const file of staleCache) {
|
|
719
|
+
try {
|
|
720
|
+
await unlink(file.path);
|
|
721
|
+
cacheSaved += file.size;
|
|
722
|
+
} catch {
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (staleCache.length > 0) {
|
|
726
|
+
results.push({
|
|
727
|
+
name: "Cache",
|
|
728
|
+
action: `Removed ${staleCache.length} stale cache files`,
|
|
729
|
+
spaceSaved: cacheSaved,
|
|
730
|
+
spaceSavedFormatted: formatBytes(cacheSaved)
|
|
731
|
+
});
|
|
732
|
+
totalSaved += cacheSaved;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
const cloudLogsPath = join3(cwd, ".archon", "cloud-logs");
|
|
736
|
+
if (existsSync3(cloudLogsPath)) {
|
|
737
|
+
const staleLogs = await getStaleFiles(cloudLogsPath, 30);
|
|
738
|
+
let logsSaved = 0;
|
|
739
|
+
for (const file of staleLogs) {
|
|
740
|
+
try {
|
|
741
|
+
await unlink(file.path);
|
|
742
|
+
logsSaved += file.size;
|
|
743
|
+
} catch {
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (staleLogs.length > 0) {
|
|
747
|
+
results.push({
|
|
748
|
+
name: "Cloud Logs",
|
|
749
|
+
action: `Removed ${staleLogs.length} stale log files`,
|
|
750
|
+
spaceSaved: logsSaved,
|
|
751
|
+
spaceSavedFormatted: formatBytes(logsSaved)
|
|
752
|
+
});
|
|
753
|
+
totalSaved += logsSaved;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (results.length === 0) {
|
|
757
|
+
console.log(chalk3.green("\u2713 Nothing to clean up!\n"));
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
console.log(chalk3.bold("Cleanup Results\n"));
|
|
761
|
+
for (const result of results) {
|
|
762
|
+
console.log(` ${chalk3.green("\u2713")} ${result.name}: ${result.action}`);
|
|
763
|
+
if (result.spaceSaved > 0) {
|
|
764
|
+
console.log(chalk3.dim(` Space saved: ${result.spaceSavedFormatted}`));
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
console.log();
|
|
768
|
+
console.log(chalk3.green(`\u2713 Total space saved: ${formatBytes(totalSaved)}
|
|
769
|
+
`));
|
|
770
|
+
}
|
|
771
|
+
async function cleanupAuto(action) {
|
|
772
|
+
const cwd = process.cwd();
|
|
773
|
+
const config = await loadCleanupConfig(cwd);
|
|
774
|
+
if (action === "status") {
|
|
775
|
+
const enabled = config?.autoEnabled ?? false;
|
|
776
|
+
console.log(chalk3.blue("\n\u{1F527} Auto Cleanup Settings\n"));
|
|
777
|
+
console.log(
|
|
778
|
+
` Status: ${enabled ? chalk3.green("Enabled") : chalk3.dim("Disabled")}`
|
|
779
|
+
);
|
|
780
|
+
console.log(
|
|
781
|
+
chalk3.dim(
|
|
782
|
+
` Progress archive threshold: ${config?.progressArchiveDays ?? 30} days`
|
|
783
|
+
)
|
|
784
|
+
);
|
|
785
|
+
console.log(
|
|
786
|
+
chalk3.dim(` Cache retention: ${config?.cacheRetentionDays ?? 7} days`)
|
|
787
|
+
);
|
|
788
|
+
console.log(
|
|
789
|
+
chalk3.dim(
|
|
790
|
+
` Cloud log retention: ${config?.cloudLogRetentionDays ?? 30} days`
|
|
791
|
+
)
|
|
792
|
+
);
|
|
793
|
+
console.log();
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
const newConfig = {
|
|
797
|
+
autoEnabled: action === "enable",
|
|
798
|
+
progressArchiveDays: config?.progressArchiveDays ?? 30,
|
|
799
|
+
cacheRetentionDays: config?.cacheRetentionDays ?? 7,
|
|
800
|
+
cloudLogRetentionDays: config?.cloudLogRetentionDays ?? 30
|
|
801
|
+
};
|
|
802
|
+
await saveCleanupConfig(cwd, newConfig);
|
|
803
|
+
if (action === "enable") {
|
|
804
|
+
console.log(chalk3.green("\n\u2713 Auto cleanup enabled"));
|
|
805
|
+
console.log(
|
|
806
|
+
chalk3.dim(
|
|
807
|
+
' Cleanup check will run on "archon start" and warn if action needed\n'
|
|
808
|
+
)
|
|
809
|
+
);
|
|
810
|
+
} else {
|
|
811
|
+
console.log(chalk3.green("\n\u2713 Auto cleanup disabled\n"));
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
async function shouldRunAutoCleanup(cwd) {
|
|
815
|
+
const config = await loadCleanupConfig(cwd);
|
|
816
|
+
return config?.autoEnabled ?? false;
|
|
817
|
+
}
|
|
818
|
+
async function runAutoCleanupCheck(cwd) {
|
|
819
|
+
const progressPath = join3(cwd, PROGRESS_FILE);
|
|
820
|
+
const archonPath = join3(cwd, ARCHON_DIR);
|
|
821
|
+
let needsAttention = false;
|
|
822
|
+
if (existsSync3(progressPath)) {
|
|
823
|
+
const size = statSync(progressPath).size;
|
|
824
|
+
if (size > 100 * 1024) {
|
|
825
|
+
needsAttention = true;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
if (existsSync3(archonPath)) {
|
|
829
|
+
const size = getDirSize(archonPath);
|
|
830
|
+
if (size > 10 * 1024 * 1024) {
|
|
831
|
+
needsAttention = true;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const orphaned = getOrphanedWorktrees(cwd);
|
|
835
|
+
if (orphaned.length > 0) {
|
|
836
|
+
needsAttention = true;
|
|
837
|
+
}
|
|
838
|
+
return needsAttention;
|
|
839
|
+
}
|
|
840
|
+
|
|
408
841
|
// src/cli/start.ts
|
|
409
842
|
async function start() {
|
|
410
843
|
const cwd = process.cwd();
|
|
411
|
-
console.log(
|
|
412
|
-
console.log(
|
|
413
|
-
console.log(
|
|
844
|
+
console.log(chalk4.blue("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
845
|
+
console.log(chalk4.bold.white(" ArchonDev - AI-Powered Development Governance"));
|
|
846
|
+
console.log(chalk4.blue("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"));
|
|
414
847
|
const contextManager = new ContextManager();
|
|
415
848
|
const pendingAtomsData = await contextManager.getPendingAtomsData(cwd);
|
|
416
849
|
if (pendingAtomsData && pendingAtomsData.atoms.length > 0) {
|
|
417
|
-
console.log(
|
|
850
|
+
console.log(chalk4.yellow("\u26A1 Previous session had pending atoms:\n"));
|
|
418
851
|
for (const atomId of pendingAtomsData.atoms) {
|
|
419
|
-
console.log(
|
|
852
|
+
console.log(chalk4.dim(` \u2022 ${atomId}`));
|
|
420
853
|
}
|
|
421
854
|
console.log();
|
|
422
|
-
console.log(
|
|
423
|
-
console.log(
|
|
855
|
+
console.log(chalk4.dim(` Saved at: ${pendingAtomsData.savedAt}`));
|
|
856
|
+
console.log(chalk4.dim(` Context usage was: ${(pendingAtomsData.contextState.usagePercent * 100).toFixed(0)}%`));
|
|
424
857
|
console.log();
|
|
425
858
|
const resume = await promptYesNo("Resume with these atoms?", true);
|
|
426
859
|
if (resume) {
|
|
427
|
-
console.log(
|
|
860
|
+
console.log(chalk4.green("\n\u2713 Resuming with pending atoms...\n"));
|
|
428
861
|
await contextManager.clearPendingAtoms(cwd);
|
|
429
862
|
} else {
|
|
430
863
|
const clear = await promptYesNo("Clear pending atoms?", false);
|
|
431
864
|
if (clear) {
|
|
432
865
|
await contextManager.clearPendingAtoms(cwd);
|
|
433
|
-
console.log(
|
|
866
|
+
console.log(chalk4.dim("Cleared pending atoms.\n"));
|
|
434
867
|
}
|
|
435
868
|
}
|
|
436
869
|
}
|
|
437
870
|
const projectState = detectProjectState(cwd);
|
|
438
871
|
const governanceStatus = await gatherGovernanceStatus(cwd);
|
|
439
872
|
displayGovernanceBanner(governanceStatus);
|
|
873
|
+
if (await shouldRunAutoCleanup(cwd)) {
|
|
874
|
+
await runAutoCleanupCheck(cwd);
|
|
875
|
+
}
|
|
440
876
|
switch (projectState.scenario) {
|
|
441
877
|
case "NEW_PROJECT":
|
|
442
878
|
await handleNewProject(cwd, projectState);
|
|
@@ -454,14 +890,14 @@ function detectProjectState(cwd) {
|
|
|
454
890
|
const sourceExtensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java", ".rb", ".php"];
|
|
455
891
|
let hasSourceFiles = false;
|
|
456
892
|
for (const dir of sourceDirs) {
|
|
457
|
-
if (
|
|
893
|
+
if (existsSync4(join4(cwd, dir))) {
|
|
458
894
|
hasSourceFiles = true;
|
|
459
895
|
break;
|
|
460
896
|
}
|
|
461
897
|
}
|
|
462
898
|
if (!hasSourceFiles) {
|
|
463
899
|
try {
|
|
464
|
-
const files =
|
|
900
|
+
const files = readdirSync2(cwd);
|
|
465
901
|
hasSourceFiles = files.some(
|
|
466
902
|
(f) => sourceExtensions.some((ext) => f.endsWith(ext))
|
|
467
903
|
);
|
|
@@ -470,16 +906,16 @@ function detectProjectState(cwd) {
|
|
|
470
906
|
}
|
|
471
907
|
const projectMarkers = ["package.json", "Cargo.toml", "pyproject.toml", "go.mod", "pom.xml", "build.gradle"];
|
|
472
908
|
if (!hasSourceFiles) {
|
|
473
|
-
hasSourceFiles = projectMarkers.some((marker) =>
|
|
909
|
+
hasSourceFiles = projectMarkers.some((marker) => existsSync4(join4(cwd, marker)));
|
|
474
910
|
}
|
|
475
|
-
const hasArchitecture =
|
|
476
|
-
const hasProgress =
|
|
477
|
-
const hasReviewDb =
|
|
911
|
+
const hasArchitecture = existsSync4(join4(cwd, "ARCHITECTURE.md"));
|
|
912
|
+
const hasProgress = existsSync4(join4(cwd, "progress.txt"));
|
|
913
|
+
const hasReviewDb = existsSync4(join4(cwd, "docs", "code-review", "review-tasks.db"));
|
|
478
914
|
let hasProgressEntries = false;
|
|
479
915
|
let lastProgressEntry;
|
|
480
916
|
if (hasProgress) {
|
|
481
917
|
try {
|
|
482
|
-
const progressContent = readFileSync(
|
|
918
|
+
const progressContent = readFileSync(join4(cwd, "progress.txt"), "utf-8");
|
|
483
919
|
const entries = progressContent.match(/^## \d{4}-\d{2}-\d{2}/gm);
|
|
484
920
|
hasProgressEntries = entries !== null && entries.length > 0;
|
|
485
921
|
if (hasProgressEntries) {
|
|
@@ -518,8 +954,8 @@ async function gatherGovernanceStatus(cwd) {
|
|
|
518
954
|
dependencyRulesCount: 0,
|
|
519
955
|
pendingAtomsCount: 0
|
|
520
956
|
};
|
|
521
|
-
const archPath =
|
|
522
|
-
if (
|
|
957
|
+
const archPath = join4(cwd, "ARCHITECTURE.md");
|
|
958
|
+
if (existsSync4(archPath)) {
|
|
523
959
|
const parser = new ArchitectureParser(archPath);
|
|
524
960
|
const result = await parser.parse();
|
|
525
961
|
if (result.success && result.schema) {
|
|
@@ -536,8 +972,8 @@ async function gatherGovernanceStatus(cwd) {
|
|
|
536
972
|
status2.dependencyRulesCount = depResult.document.rules.length;
|
|
537
973
|
}
|
|
538
974
|
}
|
|
539
|
-
const progressPath =
|
|
540
|
-
if (
|
|
975
|
+
const progressPath = join4(cwd, "progress.txt");
|
|
976
|
+
if (existsSync4(progressPath)) {
|
|
541
977
|
try {
|
|
542
978
|
const content = readFileSync(progressPath, "utf-8");
|
|
543
979
|
const dateMatches = content.match(/^## (\d{4}-\d{2}-\d{2})/gm);
|
|
@@ -551,10 +987,10 @@ async function gatherGovernanceStatus(cwd) {
|
|
|
551
987
|
} catch {
|
|
552
988
|
}
|
|
553
989
|
}
|
|
554
|
-
const atomsDir =
|
|
555
|
-
if (
|
|
990
|
+
const atomsDir = join4(cwd, ".archon", "atoms");
|
|
991
|
+
if (existsSync4(atomsDir)) {
|
|
556
992
|
try {
|
|
557
|
-
const files =
|
|
993
|
+
const files = readdirSync2(atomsDir);
|
|
558
994
|
status2.pendingAtomsCount = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml")).length;
|
|
559
995
|
} catch {
|
|
560
996
|
}
|
|
@@ -562,35 +998,35 @@ async function gatherGovernanceStatus(cwd) {
|
|
|
562
998
|
return status2;
|
|
563
999
|
}
|
|
564
1000
|
function displayGovernanceBanner(status2) {
|
|
565
|
-
console.log(
|
|
566
|
-
console.log(
|
|
567
|
-
console.log(
|
|
1001
|
+
console.log(chalk4.blue("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
1002
|
+
console.log(chalk4.bold.white(" ArchonDev Governance Active"));
|
|
1003
|
+
console.log(chalk4.blue("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
568
1004
|
if (status2.hasArchitecture) {
|
|
569
|
-
console.log(
|
|
570
|
-
console.log(
|
|
571
|
-
console.log(
|
|
1005
|
+
console.log(chalk4.green(" \u2713") + ` ARCHITECTURE.md loaded (${status2.posture} posture)`);
|
|
1006
|
+
console.log(chalk4.green(" \u2713") + ` ${status2.invariantsCount} invariants enforced`);
|
|
1007
|
+
console.log(chalk4.green(" \u2713") + ` ${status2.protectedPathsCount} protected paths defined`);
|
|
572
1008
|
} else {
|
|
573
|
-
console.log(
|
|
1009
|
+
console.log(chalk4.yellow(" \u26A0") + " ARCHITECTURE.md not found - run " + chalk4.cyan("archon init"));
|
|
574
1010
|
}
|
|
575
|
-
console.log(
|
|
1011
|
+
console.log(chalk4.green(" \u2713") + ` ${status2.dependencyRulesCount} dependency rules active`);
|
|
576
1012
|
if (status2.lastSessionDate) {
|
|
577
|
-
console.log(
|
|
1013
|
+
console.log(chalk4.green(" \u2713") + ` Last session: ${status2.lastSessionDate}`);
|
|
578
1014
|
}
|
|
579
1015
|
if (status2.pendingAtomsCount > 0) {
|
|
580
1016
|
console.log();
|
|
581
|
-
console.log(
|
|
1017
|
+
console.log(chalk4.cyan(` Pending: ${status2.pendingAtomsCount} atoms ready for execution`));
|
|
582
1018
|
}
|
|
583
|
-
console.log(
|
|
1019
|
+
console.log(chalk4.blue("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"));
|
|
584
1020
|
}
|
|
585
1021
|
async function handleNewProject(cwd, state) {
|
|
586
|
-
console.log(
|
|
587
|
-
console.log(
|
|
588
|
-
console.log(
|
|
589
|
-
console.log(
|
|
590
|
-
console.log(` ${
|
|
591
|
-
console.log(` ${
|
|
592
|
-
console.log(` ${
|
|
593
|
-
console.log(` ${
|
|
1022
|
+
console.log(chalk4.yellow("\u{1F389}") + chalk4.bold(" Starting a new project? Great!\n"));
|
|
1023
|
+
console.log(chalk4.dim("I'll ask you a few quick questions to set things up right."));
|
|
1024
|
+
console.log(chalk4.dim("Answer as much or as little as you want \u2014 you can always refine later.\n"));
|
|
1025
|
+
console.log(chalk4.bold("What would you like to do?\n"));
|
|
1026
|
+
console.log(` ${chalk4.cyan("1")}) ${chalk4.bold("Start interview")} \u2014 I'll ask questions to understand your project`);
|
|
1027
|
+
console.log(` ${chalk4.cyan("2")}) ${chalk4.bold("Quick start")} \u2014 Just create basic governance files`);
|
|
1028
|
+
console.log(` ${chalk4.cyan("3")}) ${chalk4.bold("Import from template")} \u2014 Use a predefined project template`);
|
|
1029
|
+
console.log(` ${chalk4.cyan("q")}) ${chalk4.dim("Quit")}`);
|
|
594
1030
|
console.log();
|
|
595
1031
|
const choice = await prompt("Enter choice");
|
|
596
1032
|
switch (choice.toLowerCase()) {
|
|
@@ -601,19 +1037,19 @@ async function handleNewProject(cwd, state) {
|
|
|
601
1037
|
await quickStart(cwd);
|
|
602
1038
|
break;
|
|
603
1039
|
case "3":
|
|
604
|
-
console.log(
|
|
1040
|
+
console.log(chalk4.yellow("\nTemplates coming soon! Using quick start for now.\n"));
|
|
605
1041
|
await quickStart(cwd);
|
|
606
1042
|
break;
|
|
607
1043
|
case "q":
|
|
608
1044
|
process.exit(0);
|
|
609
1045
|
default:
|
|
610
|
-
console.log(
|
|
1046
|
+
console.log(chalk4.yellow("Invalid choice. Please try again."));
|
|
611
1047
|
await handleNewProject(cwd, state);
|
|
612
1048
|
}
|
|
613
1049
|
}
|
|
614
1050
|
async function runNewProjectInterview(cwd) {
|
|
615
|
-
console.log(
|
|
616
|
-
console.log(
|
|
1051
|
+
console.log(chalk4.blue("\n\u2501\u2501\u2501 Project Interview \u2501\u2501\u2501\n"));
|
|
1052
|
+
console.log(chalk4.bold("Phase 1: The Vision\n"));
|
|
617
1053
|
const projectName = await prompt("What's the project name?");
|
|
618
1054
|
const projectDescription = await prompt("In one sentence, what does this project do?");
|
|
619
1055
|
const audience = await promptChoice("Who is it for?", [
|
|
@@ -626,7 +1062,7 @@ async function runNewProjectInterview(cwd) {
|
|
|
626
1062
|
{ key: "2", label: "\u{1F7E1} Intermediate \u2014 I've done similar work" },
|
|
627
1063
|
{ key: "3", label: "\u{1F534} Learning \u2014 This is new to me" }
|
|
628
1064
|
]);
|
|
629
|
-
console.log(
|
|
1065
|
+
console.log(chalk4.bold("\nPhase 2: Tech Stack\n"));
|
|
630
1066
|
const language = await promptChoice("Primary language/framework?", [
|
|
631
1067
|
{ key: "1", label: "TypeScript / JavaScript" },
|
|
632
1068
|
{ key: "2", label: "Python" },
|
|
@@ -640,10 +1076,10 @@ async function runNewProjectInterview(cwd) {
|
|
|
640
1076
|
{ key: "3", label: "Full-stack (both)" },
|
|
641
1077
|
{ key: "4", label: "Library/package" }
|
|
642
1078
|
]);
|
|
643
|
-
console.log(
|
|
1079
|
+
console.log(chalk4.bold("\nPhase 3: Preferences ") + chalk4.dim("(press Enter to skip)\n"));
|
|
644
1080
|
const protectedFiles = await prompt("Any files AI should NEVER modify without asking? (comma-separated)");
|
|
645
1081
|
const noNoPatterns = await prompt('Anything AI should NEVER do? (e.g., "no console.log")');
|
|
646
|
-
console.log(
|
|
1082
|
+
console.log(chalk4.blue("\n\u2501\u2501\u2501 Generating Project Files \u2501\u2501\u2501\n"));
|
|
647
1083
|
const { init: init2 } = await import("./init-7EWVBX6O.js");
|
|
648
1084
|
await init2({ analyze: false, git: true });
|
|
649
1085
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -669,16 +1105,16 @@ ${noNoPatterns ? `- **Forbidden patterns:** ${noNoPatterns}` : "- No forbidden p
|
|
|
669
1105
|
- .archon/config.yaml
|
|
670
1106
|
- progress.txt
|
|
671
1107
|
`;
|
|
672
|
-
const progressPath =
|
|
673
|
-
if (!
|
|
1108
|
+
const progressPath = join4(cwd, "progress.txt");
|
|
1109
|
+
if (!existsSync4(progressPath)) {
|
|
674
1110
|
const { writeFileSync } = await import("fs");
|
|
675
1111
|
writeFileSync(progressPath, "# ArchonDev Progress Log\n\nThis file tracks learnings and decisions across sessions.\n");
|
|
676
1112
|
}
|
|
677
1113
|
appendFileSync(progressPath, progressEntry);
|
|
678
|
-
console.log(
|
|
679
|
-
console.log(
|
|
680
|
-
console.log(` 1. ${
|
|
681
|
-
console.log(` 2. ${
|
|
1114
|
+
console.log(chalk4.green("\n\u2713 Project initialized!\n"));
|
|
1115
|
+
console.log(chalk4.bold("Next steps:"));
|
|
1116
|
+
console.log(` 1. ${chalk4.cyan("Review")} ARCHITECTURE.md and customize if needed`);
|
|
1117
|
+
console.log(` 2. ${chalk4.cyan("Run")} ${chalk4.dim('archon plan "your first task"')} to create an atom`);
|
|
682
1118
|
console.log();
|
|
683
1119
|
const continueChoice = await promptYesNo("Would you like to plan your first task now?", true);
|
|
684
1120
|
if (continueChoice) {
|
|
@@ -690,12 +1126,12 @@ ${noNoPatterns ? `- **Forbidden patterns:** ${noNoPatterns}` : "- No forbidden p
|
|
|
690
1126
|
}
|
|
691
1127
|
}
|
|
692
1128
|
async function quickStart(cwd) {
|
|
693
|
-
console.log(
|
|
1129
|
+
console.log(chalk4.blue("\n\u2501\u2501\u2501 Quick Start \u2501\u2501\u2501\n"));
|
|
694
1130
|
const { init: init2 } = await import("./init-7EWVBX6O.js");
|
|
695
1131
|
await init2({ analyze: false, git: true });
|
|
696
1132
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
697
|
-
const progressPath =
|
|
698
|
-
if (!
|
|
1133
|
+
const progressPath = join4(cwd, "progress.txt");
|
|
1134
|
+
if (!existsSync4(progressPath)) {
|
|
699
1135
|
const { writeFileSync } = await import("fs");
|
|
700
1136
|
writeFileSync(progressPath, `# ArchonDev Progress Log
|
|
701
1137
|
|
|
@@ -717,15 +1153,15 @@ This file tracks learnings and decisions across sessions.
|
|
|
717
1153
|
await showMainMenu();
|
|
718
1154
|
}
|
|
719
1155
|
async function handleAdaptExisting(cwd, state) {
|
|
720
|
-
console.log(
|
|
721
|
-
console.log(
|
|
722
|
-
console.log(
|
|
723
|
-
console.log(
|
|
724
|
-
console.log(` ${
|
|
725
|
-
console.log(` ${
|
|
726
|
-
console.log(` ${
|
|
727
|
-
console.log(` ${
|
|
728
|
-
console.log(` ${
|
|
1156
|
+
console.log(chalk4.yellow("\u{1F4C1}") + chalk4.bold(" Existing project detected!\n"));
|
|
1157
|
+
console.log(chalk4.dim("I can analyze your codebase and adapt the governance files to match your structure."));
|
|
1158
|
+
console.log(chalk4.dim("This helps me understand your architecture without changing any code.\n"));
|
|
1159
|
+
console.log(chalk4.bold("What would you like to do?\n"));
|
|
1160
|
+
console.log(` ${chalk4.cyan("1")}) ${chalk4.bold("Analyze and adapt")} \u2014 I'll scan your project and update ARCHITECTURE.md`);
|
|
1161
|
+
console.log(` ${chalk4.cyan("2")}) ${chalk4.bold("Code review first")} \u2014 Review code for issues before setting up governance`);
|
|
1162
|
+
console.log(` ${chalk4.cyan("3")}) ${chalk4.bold("Manual setup")} \u2014 Keep template files, customize manually`);
|
|
1163
|
+
console.log(` ${chalk4.cyan("4")}) ${chalk4.bold("Just start working")} \u2014 Skip setup, use defaults`);
|
|
1164
|
+
console.log(` ${chalk4.cyan("q")}) ${chalk4.dim("Quit")}`);
|
|
729
1165
|
console.log();
|
|
730
1166
|
const choice = await prompt("Enter choice");
|
|
731
1167
|
switch (choice.toLowerCase()) {
|
|
@@ -744,17 +1180,17 @@ async function handleAdaptExisting(cwd, state) {
|
|
|
744
1180
|
case "q":
|
|
745
1181
|
process.exit(0);
|
|
746
1182
|
default:
|
|
747
|
-
console.log(
|
|
1183
|
+
console.log(chalk4.yellow("Invalid choice. Please try again."));
|
|
748
1184
|
await handleAdaptExisting(cwd, state);
|
|
749
1185
|
}
|
|
750
1186
|
}
|
|
751
1187
|
async function analyzeAndAdapt(cwd) {
|
|
752
|
-
console.log(
|
|
1188
|
+
console.log(chalk4.blue("\n\u2501\u2501\u2501 Analyzing Project \u2501\u2501\u2501\n"));
|
|
753
1189
|
const { init: init2 } = await import("./init-7EWVBX6O.js");
|
|
754
1190
|
await init2({ analyze: true, git: true });
|
|
755
1191
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
756
|
-
const progressPath =
|
|
757
|
-
if (!
|
|
1192
|
+
const progressPath = join4(cwd, "progress.txt");
|
|
1193
|
+
if (!existsSync4(progressPath)) {
|
|
758
1194
|
const { writeFileSync } = await import("fs");
|
|
759
1195
|
writeFileSync(progressPath, "# ArchonDev Progress Log\n\nThis file tracks learnings and decisions across sessions.\n");
|
|
760
1196
|
}
|
|
@@ -771,15 +1207,15 @@ async function analyzeAndAdapt(cwd) {
|
|
|
771
1207
|
- .archon/config.yaml - Build commands configured
|
|
772
1208
|
- progress.txt - This file
|
|
773
1209
|
`);
|
|
774
|
-
console.log(
|
|
1210
|
+
console.log(chalk4.green("\n\u2713 Governance files adapted!\n"));
|
|
775
1211
|
await showMainMenu();
|
|
776
1212
|
}
|
|
777
1213
|
async function codeReviewFirst(cwd) {
|
|
778
|
-
console.log(
|
|
779
|
-
console.log(
|
|
1214
|
+
console.log(chalk4.blue("\n\u2501\u2501\u2501 Code Review Mode \u2501\u2501\u2501\n"));
|
|
1215
|
+
console.log(chalk4.dim("I'll analyze your code for issues without making any changes.\n"));
|
|
780
1216
|
const { reviewInit: reviewInit2, reviewAnalyze: reviewAnalyze2, reviewRun: reviewRun2 } = await import("./review-3R6QXAXQ.js");
|
|
781
|
-
const reviewDbPath =
|
|
782
|
-
if (!
|
|
1217
|
+
const reviewDbPath = join4(cwd, "docs", "code-review", "review-tasks.db");
|
|
1218
|
+
if (!existsSync4(reviewDbPath)) {
|
|
783
1219
|
await reviewInit2();
|
|
784
1220
|
}
|
|
785
1221
|
await reviewAnalyze2();
|
|
@@ -787,27 +1223,27 @@ async function codeReviewFirst(cwd) {
|
|
|
787
1223
|
if (runReview) {
|
|
788
1224
|
await reviewRun2({ all: true });
|
|
789
1225
|
}
|
|
790
|
-
console.log(
|
|
1226
|
+
console.log(chalk4.dim("\nAfter reviewing, you can run ") + chalk4.cyan("archon") + chalk4.dim(" again to set up governance.\n"));
|
|
791
1227
|
}
|
|
792
1228
|
async function manualSetup(cwd) {
|
|
793
|
-
console.log(
|
|
794
|
-
console.log(
|
|
1229
|
+
console.log(chalk4.blue("\n\u2501\u2501\u2501 Manual Setup \u2501\u2501\u2501\n"));
|
|
1230
|
+
console.log(chalk4.dim("Creating template files. You can customize them manually.\n"));
|
|
795
1231
|
const { init: init2 } = await import("./init-7EWVBX6O.js");
|
|
796
1232
|
await init2({ analyze: false, git: true });
|
|
797
|
-
console.log(
|
|
798
|
-
console.log(` ${
|
|
799
|
-
console.log(` ${
|
|
800
|
-
console.log(` ${
|
|
1233
|
+
console.log(chalk4.bold("\nWhat to customize:\n"));
|
|
1234
|
+
console.log(` ${chalk4.cyan("1. ARCHITECTURE.md")} \u2014 Update components to match your folders`);
|
|
1235
|
+
console.log(` ${chalk4.cyan("2. .archon/config.yaml")} \u2014 Change build/test/lint commands`);
|
|
1236
|
+
console.log(` ${chalk4.cyan("3. progress.txt")} \u2014 Add project-specific patterns`);
|
|
801
1237
|
console.log();
|
|
802
1238
|
await showMainMenu();
|
|
803
1239
|
}
|
|
804
1240
|
async function quickAdapt(cwd) {
|
|
805
|
-
console.log(
|
|
1241
|
+
console.log(chalk4.blue("\n\u26A1 Using defaults \u2014 let's go!\n"));
|
|
806
1242
|
const { init: init2 } = await import("./init-7EWVBX6O.js");
|
|
807
1243
|
await init2({ analyze: true, git: true });
|
|
808
1244
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
809
|
-
const progressPath =
|
|
810
|
-
if (!
|
|
1245
|
+
const progressPath = join4(cwd, "progress.txt");
|
|
1246
|
+
if (!existsSync4(progressPath)) {
|
|
811
1247
|
const { writeFileSync } = await import("fs");
|
|
812
1248
|
writeFileSync(progressPath, "# ArchonDev Progress Log\n\nThis file tracks learnings and decisions across sessions.\n");
|
|
813
1249
|
}
|
|
@@ -821,20 +1257,20 @@ async function quickAdapt(cwd) {
|
|
|
821
1257
|
await showMainMenu();
|
|
822
1258
|
}
|
|
823
1259
|
async function handleContinueSession(cwd, state) {
|
|
824
|
-
console.log(
|
|
1260
|
+
console.log(chalk4.green("\u{1F44B}") + chalk4.bold(" Welcome back!\n"));
|
|
825
1261
|
if (state.lastProgressEntry) {
|
|
826
|
-
console.log(
|
|
827
|
-
console.log(
|
|
1262
|
+
console.log(chalk4.dim("Last activity:"));
|
|
1263
|
+
console.log(chalk4.dim(" " + state.lastProgressEntry.split("\n")[0]));
|
|
828
1264
|
console.log();
|
|
829
1265
|
}
|
|
830
1266
|
const handoff = checkForHandoff(cwd);
|
|
831
1267
|
if (handoff) {
|
|
832
|
-
console.log(
|
|
833
|
-
console.log(
|
|
1268
|
+
console.log(chalk4.yellow("\u{1F4CB} Found handoff from last session:\n"));
|
|
1269
|
+
console.log(chalk4.dim(handoff.nextSteps));
|
|
834
1270
|
console.log();
|
|
835
1271
|
const continueHandoff = await promptYesNo("Continue from handoff?", true);
|
|
836
1272
|
if (continueHandoff) {
|
|
837
|
-
console.log(
|
|
1273
|
+
console.log(chalk4.dim("\nPicking up where you left off...\n"));
|
|
838
1274
|
}
|
|
839
1275
|
}
|
|
840
1276
|
if (state.hasReviewDb) {
|
|
@@ -844,8 +1280,8 @@ async function handleContinueSession(cwd, state) {
|
|
|
844
1280
|
}
|
|
845
1281
|
function checkForHandoff(cwd) {
|
|
846
1282
|
try {
|
|
847
|
-
const progressPath =
|
|
848
|
-
if (!
|
|
1283
|
+
const progressPath = join4(cwd, "progress.txt");
|
|
1284
|
+
if (!existsSync4(progressPath)) return null;
|
|
849
1285
|
const content = readFileSync(progressPath, "utf-8");
|
|
850
1286
|
const handoffMatch = content.match(/## Context Handoff[^\n]*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
851
1287
|
if (handoffMatch && handoffMatch[1]) {
|
|
@@ -862,7 +1298,7 @@ function checkForHandoff(cwd) {
|
|
|
862
1298
|
async function showMainMenu() {
|
|
863
1299
|
const cwd = process.cwd();
|
|
864
1300
|
const state = detectProjectState(cwd);
|
|
865
|
-
console.log(
|
|
1301
|
+
console.log(chalk4.bold("What would you like to do?\n"));
|
|
866
1302
|
const choices = [
|
|
867
1303
|
{ key: "1", label: "Plan a new task", action: () => planTask() },
|
|
868
1304
|
{ key: "2", label: "List atoms", action: () => listAtoms() },
|
|
@@ -874,7 +1310,7 @@ async function showMainMenu() {
|
|
|
874
1310
|
{ key: "q", label: "Quit", action: async () => process.exit(0) }
|
|
875
1311
|
];
|
|
876
1312
|
for (const choice2 of choices) {
|
|
877
|
-
console.log(` ${
|
|
1313
|
+
console.log(` ${chalk4.cyan(choice2.key)}) ${choice2.label}`);
|
|
878
1314
|
}
|
|
879
1315
|
console.log();
|
|
880
1316
|
const selected = await prompt("Enter choice");
|
|
@@ -882,7 +1318,7 @@ async function showMainMenu() {
|
|
|
882
1318
|
if (choice) {
|
|
883
1319
|
await choice.action();
|
|
884
1320
|
} else {
|
|
885
|
-
console.log(
|
|
1321
|
+
console.log(chalk4.yellow("Invalid choice. Please try again."));
|
|
886
1322
|
await showMainMenu();
|
|
887
1323
|
}
|
|
888
1324
|
}
|
|
@@ -898,7 +1334,7 @@ async function showReviewProgress(cwd) {
|
|
|
898
1334
|
const pending = stats.pending + stats.inReview;
|
|
899
1335
|
const needsFix = stats.needsFix;
|
|
900
1336
|
console.log(
|
|
901
|
-
|
|
1337
|
+
chalk4.blue("\u{1F4CA} Review Progress:") + chalk4.dim(` ${completed}/${total} completed`) + (needsFix > 0 ? chalk4.red(` (${needsFix} need fixes)`) : "") + (pending > 0 ? chalk4.yellow(` (${pending} pending)`) : "")
|
|
902
1338
|
);
|
|
903
1339
|
console.log();
|
|
904
1340
|
} catch {
|
|
@@ -923,38 +1359,38 @@ async function executeNext() {
|
|
|
923
1359
|
const atoms = await listLocalAtoms2();
|
|
924
1360
|
const pendingAtoms = atoms.filter((a) => a.status === "READY" || a.status === "IN_PROGRESS");
|
|
925
1361
|
if (pendingAtoms.length === 0) {
|
|
926
|
-
console.log(
|
|
1362
|
+
console.log(chalk4.yellow('No pending atoms. Use "archon plan" to create one.'));
|
|
927
1363
|
return;
|
|
928
1364
|
}
|
|
929
1365
|
if (pendingAtoms.length > 1) {
|
|
930
1366
|
const analysis = analyzeProject(pendingAtoms);
|
|
931
1367
|
const prefs = await loadExecutionPreferences(cwd);
|
|
932
|
-
console.log(
|
|
933
|
-
console.log(` ${
|
|
934
|
-
console.log(` ${
|
|
935
|
-
console.log(` ${
|
|
936
|
-
console.log(` ${
|
|
1368
|
+
console.log(chalk4.blue("\n\u2501\u2501\u2501 Project Analysis \u2501\u2501\u2501\n"));
|
|
1369
|
+
console.log(` ${chalk4.bold("Atoms:")} ${analysis.atomCount}`);
|
|
1370
|
+
console.log(` ${chalk4.bold("Estimated:")} ${analysis.estimatedMinutes} minutes`);
|
|
1371
|
+
console.log(` ${chalk4.bold("Complexity:")} ${analysis.complexity} - ${getComplexityDescription(analysis.complexity)}`);
|
|
1372
|
+
console.log(` ${chalk4.bold("Suggested:")} ${analysis.suggestedMode} - ${getModeDescription(analysis.suggestedMode)}`);
|
|
937
1373
|
console.log();
|
|
938
1374
|
if (analysis.suggestedMode !== "sequential" && prefs.parallelMode === "ask") {
|
|
939
1375
|
const useParallel = await promptYesNo(`Use ${analysis.suggestedMode} execution?`, true);
|
|
940
1376
|
if (useParallel) {
|
|
941
|
-
console.log(
|
|
1377
|
+
console.log(chalk4.green(`
|
|
942
1378
|
\u2713 ${analysis.suggestedMode} execution selected`));
|
|
943
|
-
console.log(
|
|
1379
|
+
console.log(chalk4.dim("(Parallel/cloud execution coming soon - running sequentially for now)\n"));
|
|
944
1380
|
}
|
|
945
1381
|
} else if (prefs.parallelMode === "always" && analysis.suggestedMode !== "sequential") {
|
|
946
|
-
console.log(
|
|
1382
|
+
console.log(chalk4.green(`
|
|
947
1383
|
\u2713 Auto-selected ${analysis.suggestedMode} execution (preference: always)`));
|
|
948
|
-
console.log(
|
|
1384
|
+
console.log(chalk4.dim("(Parallel/cloud execution coming soon - running sequentially for now)\n"));
|
|
949
1385
|
}
|
|
950
1386
|
}
|
|
951
1387
|
const atomId = await prompt("Enter atom ID to execute (or press Enter for first pending)");
|
|
952
1388
|
const targetId = atomId.trim() || pendingAtoms[0]?.id;
|
|
953
1389
|
if (targetId) {
|
|
954
|
-
const { execute: execute2 } = await import("./execute-
|
|
1390
|
+
const { execute: execute2 } = await import("./execute-2S2UHTLN.js");
|
|
955
1391
|
await execute2(targetId, {});
|
|
956
1392
|
} else {
|
|
957
|
-
console.log(
|
|
1393
|
+
console.log(chalk4.yellow("No atom to execute."));
|
|
958
1394
|
}
|
|
959
1395
|
}
|
|
960
1396
|
async function reportBug() {
|
|
@@ -975,20 +1411,20 @@ async function settingsMenu() {
|
|
|
975
1411
|
}
|
|
976
1412
|
async function reviewCode() {
|
|
977
1413
|
const cwd = process.cwd();
|
|
978
|
-
const reviewDbPath =
|
|
979
|
-
if (!
|
|
980
|
-
console.log(
|
|
1414
|
+
const reviewDbPath = join4(cwd, "docs", "code-review", "review-tasks.db");
|
|
1415
|
+
if (!existsSync4(reviewDbPath)) {
|
|
1416
|
+
console.log(chalk4.dim("Code review not initialized. Starting setup...\n"));
|
|
981
1417
|
const { reviewInit: reviewInit2 } = await import("./review-3R6QXAXQ.js");
|
|
982
1418
|
await reviewInit2();
|
|
983
1419
|
console.log();
|
|
984
1420
|
}
|
|
985
|
-
console.log(
|
|
986
|
-
console.log(` ${
|
|
987
|
-
console.log(` ${
|
|
988
|
-
console.log(` ${
|
|
989
|
-
console.log(` ${
|
|
990
|
-
console.log(` ${
|
|
991
|
-
console.log(` ${
|
|
1421
|
+
console.log(chalk4.bold("\nCode Review Options:\n"));
|
|
1422
|
+
console.log(` ${chalk4.cyan("1")}) Analyze project`);
|
|
1423
|
+
console.log(` ${chalk4.cyan("2")}) Show review status`);
|
|
1424
|
+
console.log(` ${chalk4.cyan("3")}) Review next file`);
|
|
1425
|
+
console.log(` ${chalk4.cyan("4")}) List all tasks`);
|
|
1426
|
+
console.log(` ${chalk4.cyan("5")}) Run AI review on all pending`);
|
|
1427
|
+
console.log(` ${chalk4.cyan("b")}) Back to main menu`);
|
|
992
1428
|
console.log();
|
|
993
1429
|
const choice = await prompt("Enter choice");
|
|
994
1430
|
switch (choice.toLowerCase()) {
|
|
@@ -1021,7 +1457,7 @@ async function reviewCode() {
|
|
|
1021
1457
|
await showMainMenu();
|
|
1022
1458
|
return;
|
|
1023
1459
|
default:
|
|
1024
|
-
console.log(
|
|
1460
|
+
console.log(chalk4.yellow("Invalid choice."));
|
|
1025
1461
|
}
|
|
1026
1462
|
await reviewCode();
|
|
1027
1463
|
}
|
|
@@ -1031,7 +1467,7 @@ function prompt(question) {
|
|
|
1031
1467
|
input: process.stdin,
|
|
1032
1468
|
output: process.stdout
|
|
1033
1469
|
});
|
|
1034
|
-
rl.question(`${
|
|
1470
|
+
rl.question(`${chalk4.cyan("?")} ${question}: `, (answer) => {
|
|
1035
1471
|
rl.close();
|
|
1036
1472
|
resolve(answer);
|
|
1037
1473
|
});
|
|
@@ -1044,7 +1480,7 @@ function promptYesNo(question, defaultValue) {
|
|
|
1044
1480
|
output: process.stdout
|
|
1045
1481
|
});
|
|
1046
1482
|
const hint = defaultValue ? "(Y/n)" : "(y/N)";
|
|
1047
|
-
rl.question(`${
|
|
1483
|
+
rl.question(`${chalk4.cyan("?")} ${question} ${hint}: `, (answer) => {
|
|
1048
1484
|
rl.close();
|
|
1049
1485
|
if (answer.trim() === "") {
|
|
1050
1486
|
resolve(defaultValue);
|
|
@@ -1056,15 +1492,15 @@ function promptYesNo(question, defaultValue) {
|
|
|
1056
1492
|
}
|
|
1057
1493
|
function promptChoice(question, options) {
|
|
1058
1494
|
return new Promise((resolve) => {
|
|
1059
|
-
console.log(`${
|
|
1495
|
+
console.log(`${chalk4.cyan("?")} ${question}`);
|
|
1060
1496
|
for (const opt of options) {
|
|
1061
|
-
console.log(` ${
|
|
1497
|
+
console.log(` ${chalk4.dim(opt.key)}) ${opt.label}`);
|
|
1062
1498
|
}
|
|
1063
1499
|
const rl = readline.createInterface({
|
|
1064
1500
|
input: process.stdin,
|
|
1065
1501
|
output: process.stdout
|
|
1066
1502
|
});
|
|
1067
|
-
rl.question(` ${
|
|
1503
|
+
rl.question(` ${chalk4.dim("Enter choice")}: `, (answer) => {
|
|
1068
1504
|
rl.close();
|
|
1069
1505
|
resolve(answer.trim() || "1");
|
|
1070
1506
|
});
|
|
@@ -1072,7 +1508,7 @@ function promptChoice(question, options) {
|
|
|
1072
1508
|
}
|
|
1073
1509
|
|
|
1074
1510
|
// src/cli/credits.ts
|
|
1075
|
-
import
|
|
1511
|
+
import chalk5 from "chalk";
|
|
1076
1512
|
import ora from "ora";
|
|
1077
1513
|
import open from "open";
|
|
1078
1514
|
import { createClient } from "@supabase/supabase-js";
|
|
@@ -1098,21 +1534,21 @@ async function showCredits() {
|
|
|
1098
1534
|
const profile = data;
|
|
1099
1535
|
spinner.stop();
|
|
1100
1536
|
console.log();
|
|
1101
|
-
console.log(
|
|
1537
|
+
console.log(chalk5.bold("\u{1F4B0} Credit Balance"));
|
|
1102
1538
|
console.log();
|
|
1103
1539
|
const balance = (profile.credit_balance_cents || 0) / 100;
|
|
1104
1540
|
console.log(` Tier: ${formatTier(profile.tier)}`);
|
|
1105
|
-
console.log(` Balance: ${
|
|
1541
|
+
console.log(` Balance: ${chalk5.green(`$${balance.toFixed(2)}`)}`);
|
|
1106
1542
|
if (profile.tier === "FREE") {
|
|
1107
1543
|
console.log(` Atoms: ${profile.atoms_used_this_month}/10,000 this month`);
|
|
1108
1544
|
console.log();
|
|
1109
|
-
console.log(
|
|
1545
|
+
console.log(chalk5.dim(" Upgrade to Credits tier: archon credits add"));
|
|
1110
1546
|
} else if (profile.tier === "CREDITS") {
|
|
1111
1547
|
console.log();
|
|
1112
|
-
console.log(
|
|
1548
|
+
console.log(chalk5.dim(" Add more credits: archon credits add"));
|
|
1113
1549
|
} else if (profile.tier === "BYOK") {
|
|
1114
1550
|
console.log();
|
|
1115
|
-
console.log(
|
|
1551
|
+
console.log(chalk5.dim(" Using your own API keys - no credit charges"));
|
|
1116
1552
|
}
|
|
1117
1553
|
console.log();
|
|
1118
1554
|
} catch (err) {
|
|
@@ -1158,18 +1594,18 @@ async function addCredits(options = {}) {
|
|
|
1158
1594
|
}
|
|
1159
1595
|
spinner.succeed("Checkout ready");
|
|
1160
1596
|
console.log();
|
|
1161
|
-
console.log(
|
|
1597
|
+
console.log(chalk5.bold("\u{1F6D2} Add Credits"));
|
|
1162
1598
|
console.log();
|
|
1163
|
-
console.log(` Amount: ${
|
|
1599
|
+
console.log(` Amount: ${chalk5.green(`$${amountDollars.toFixed(2)}`)}`);
|
|
1164
1600
|
console.log();
|
|
1165
1601
|
console.log(" Opening checkout in browser...");
|
|
1166
1602
|
console.log();
|
|
1167
|
-
console.log(
|
|
1603
|
+
console.log(chalk5.dim(` Or visit: ${checkoutUrl}`));
|
|
1168
1604
|
console.log();
|
|
1169
1605
|
try {
|
|
1170
1606
|
await open(checkoutUrl);
|
|
1171
1607
|
} catch {
|
|
1172
|
-
console.log(
|
|
1608
|
+
console.log(chalk5.yellow(" Could not open browser. Please visit the URL above."));
|
|
1173
1609
|
}
|
|
1174
1610
|
} catch (err) {
|
|
1175
1611
|
spinner.fail("Error preparing checkout");
|
|
@@ -1194,15 +1630,15 @@ async function showHistory(options = {}) {
|
|
|
1194
1630
|
const usage = data;
|
|
1195
1631
|
spinner.stop();
|
|
1196
1632
|
console.log();
|
|
1197
|
-
console.log(
|
|
1633
|
+
console.log(chalk5.bold("\u{1F4CA} Usage History"));
|
|
1198
1634
|
console.log();
|
|
1199
1635
|
if (!usage || usage.length === 0) {
|
|
1200
|
-
console.log(
|
|
1636
|
+
console.log(chalk5.dim(" No usage recorded yet."));
|
|
1201
1637
|
console.log();
|
|
1202
1638
|
return;
|
|
1203
1639
|
}
|
|
1204
|
-
console.log(
|
|
1205
|
-
console.log(
|
|
1640
|
+
console.log(chalk5.dim(" Model Tokens Cost Date"));
|
|
1641
|
+
console.log(chalk5.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1206
1642
|
for (const row of usage) {
|
|
1207
1643
|
const model = row.model.padEnd(30).slice(0, 30);
|
|
1208
1644
|
const tokens = (row.input_tokens + row.output_tokens).toString().padStart(8);
|
|
@@ -1212,9 +1648,9 @@ async function showHistory(options = {}) {
|
|
|
1212
1648
|
}
|
|
1213
1649
|
const totalCost = usage.reduce((sum, r) => sum + r.base_cost, 0);
|
|
1214
1650
|
const totalTokens = usage.reduce((sum, r) => sum + r.input_tokens + r.output_tokens, 0);
|
|
1215
|
-
console.log(
|
|
1651
|
+
console.log(chalk5.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1216
1652
|
console.log(
|
|
1217
|
-
` ${"Total".padEnd(30)} ${totalTokens.toString().padStart(8)} ${
|
|
1653
|
+
` ${"Total".padEnd(30)} ${totalTokens.toString().padStart(8)} ${chalk5.green(`$${totalCost.toFixed(4)}`.padStart(10))}`
|
|
1218
1654
|
);
|
|
1219
1655
|
console.log();
|
|
1220
1656
|
} catch (err) {
|
|
@@ -1280,24 +1716,24 @@ async function manageBudget(options = {}) {
|
|
|
1280
1716
|
}
|
|
1281
1717
|
spinner.stop();
|
|
1282
1718
|
console.log();
|
|
1283
|
-
console.log(
|
|
1719
|
+
console.log(chalk5.bold("\u{1F4CA} Monthly Budget"));
|
|
1284
1720
|
console.log();
|
|
1285
1721
|
if (profile.monthly_budget_cents === null) {
|
|
1286
|
-
console.log(` Budget: ${
|
|
1722
|
+
console.log(` Budget: ${chalk5.dim("No limit set")}`);
|
|
1287
1723
|
} else {
|
|
1288
1724
|
const budget = profile.monthly_budget_cents / 100;
|
|
1289
1725
|
const spend = (profile.monthly_spend_cents || 0) / 100;
|
|
1290
1726
|
const remaining = budget - spend;
|
|
1291
1727
|
const percent = budget > 0 ? Math.round(spend / budget * 100) : 0;
|
|
1292
|
-
console.log(` Budget: ${
|
|
1728
|
+
console.log(` Budget: ${chalk5.green(`$${budget.toFixed(2)}`)} / month`);
|
|
1293
1729
|
console.log(` Spent: $${spend.toFixed(2)} (${percent}%)`);
|
|
1294
|
-
console.log(` Remaining: ${remaining >= 0 ?
|
|
1730
|
+
console.log(` Remaining: ${remaining >= 0 ? chalk5.green(`$${remaining.toFixed(2)}`) : chalk5.red(`-$${Math.abs(remaining).toFixed(2)}`)}`);
|
|
1295
1731
|
}
|
|
1296
1732
|
console.log(` Alert at: ${profile.budget_alert_threshold_percent}% of budget`);
|
|
1297
1733
|
console.log();
|
|
1298
|
-
console.log(
|
|
1299
|
-
console.log(
|
|
1300
|
-
console.log(
|
|
1734
|
+
console.log(chalk5.dim(" Set budget: archon credits budget --set 50"));
|
|
1735
|
+
console.log(chalk5.dim(" Clear budget: archon credits budget --clear"));
|
|
1736
|
+
console.log(chalk5.dim(" Set alert: archon credits budget --alert 80"));
|
|
1301
1737
|
console.log();
|
|
1302
1738
|
} catch (err) {
|
|
1303
1739
|
spinner.fail("Error managing budget");
|
|
@@ -1359,12 +1795,12 @@ async function manageAutoRecharge(options = {}) {
|
|
|
1359
1795
|
}
|
|
1360
1796
|
spinner.stop();
|
|
1361
1797
|
console.log();
|
|
1362
|
-
console.log(
|
|
1798
|
+
console.log(chalk5.bold("\u{1F504} Auto-Recharge"));
|
|
1363
1799
|
console.log();
|
|
1364
1800
|
if (!profile.auto_recharge_enabled) {
|
|
1365
|
-
console.log(` Status: ${
|
|
1801
|
+
console.log(` Status: ${chalk5.dim("Disabled")}`);
|
|
1366
1802
|
} else {
|
|
1367
|
-
console.log(` Status: ${
|
|
1803
|
+
console.log(` Status: ${chalk5.green("Enabled")}`);
|
|
1368
1804
|
if (profile.auto_recharge_threshold_cents !== null) {
|
|
1369
1805
|
console.log(` When: Balance drops below $${(profile.auto_recharge_threshold_cents / 100).toFixed(2)}`);
|
|
1370
1806
|
}
|
|
@@ -1372,10 +1808,10 @@ async function manageAutoRecharge(options = {}) {
|
|
|
1372
1808
|
console.log(` Amount: $${(profile.auto_recharge_amount_cents / 100).toFixed(2)}`);
|
|
1373
1809
|
}
|
|
1374
1810
|
}
|
|
1375
|
-
console.log(` Payment: ${profile.stripe_payment_method_id ?
|
|
1811
|
+
console.log(` Payment: ${profile.stripe_payment_method_id ? chalk5.green("Card saved") : chalk5.dim("No card saved")}`);
|
|
1376
1812
|
console.log();
|
|
1377
|
-
console.log(
|
|
1378
|
-
console.log(
|
|
1813
|
+
console.log(chalk5.dim(" Enable: archon credits auto-recharge --enable --threshold 5 --amount 20"));
|
|
1814
|
+
console.log(chalk5.dim(" Disable: archon credits auto-recharge --disable"));
|
|
1379
1815
|
console.log();
|
|
1380
1816
|
} catch (err) {
|
|
1381
1817
|
spinner.fail("Error managing auto-recharge");
|
|
@@ -1385,11 +1821,11 @@ async function manageAutoRecharge(options = {}) {
|
|
|
1385
1821
|
function formatTier(tier) {
|
|
1386
1822
|
switch (tier) {
|
|
1387
1823
|
case "FREE":
|
|
1388
|
-
return
|
|
1824
|
+
return chalk5.blue("Free (10k atoms/month)");
|
|
1389
1825
|
case "CREDITS":
|
|
1390
|
-
return
|
|
1826
|
+
return chalk5.green("Credits (Pay-as-you-go)");
|
|
1391
1827
|
case "BYOK":
|
|
1392
|
-
return
|
|
1828
|
+
return chalk5.magenta("BYOK (Bring Your Own Key)");
|
|
1393
1829
|
default:
|
|
1394
1830
|
return tier;
|
|
1395
1831
|
}
|
|
@@ -1666,9 +2102,9 @@ async function watch() {
|
|
|
1666
2102
|
|
|
1667
2103
|
// src/cli/deps.ts
|
|
1668
2104
|
import { Command } from "commander";
|
|
1669
|
-
import
|
|
1670
|
-
import { readFile as
|
|
1671
|
-
import { existsSync as
|
|
2105
|
+
import chalk6 from "chalk";
|
|
2106
|
+
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
2107
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1672
2108
|
var DEPENDENCIES_FILENAME = "DEPENDENCIES.md";
|
|
1673
2109
|
function createDepsCommand() {
|
|
1674
2110
|
const deps = new Command("deps").description("Manage file-level dependencies (DEPENDENCIES.md)").addHelpText(
|
|
@@ -1684,30 +2120,30 @@ Examples:
|
|
|
1684
2120
|
deps.command("list").description("List all dependency rules").option("-v, --verbose", "Show detailed information").action(async (options) => {
|
|
1685
2121
|
const parser = new DependencyParser();
|
|
1686
2122
|
if (!parser.exists()) {
|
|
1687
|
-
console.log(
|
|
1688
|
-
console.log(
|
|
2123
|
+
console.log(chalk6.yellow("No DEPENDENCIES.md found."));
|
|
2124
|
+
console.log(chalk6.dim("Create one with: archon deps add --source <path> --dependent <path>"));
|
|
1689
2125
|
return;
|
|
1690
2126
|
}
|
|
1691
2127
|
const result = await parser.parse();
|
|
1692
2128
|
if (!result.success) {
|
|
1693
|
-
console.log(
|
|
2129
|
+
console.log(chalk6.red(`Parse error: ${result.error}`));
|
|
1694
2130
|
return;
|
|
1695
2131
|
}
|
|
1696
2132
|
const rules = result.document?.rules ?? [];
|
|
1697
2133
|
if (rules.length === 0) {
|
|
1698
|
-
console.log(
|
|
2134
|
+
console.log(chalk6.dim("No dependency rules defined."));
|
|
1699
2135
|
return;
|
|
1700
2136
|
}
|
|
1701
|
-
console.log(
|
|
2137
|
+
console.log(chalk6.bold(`
|
|
1702
2138
|
\u{1F4E6} Dependency Rules (${rules.length})
|
|
1703
2139
|
`));
|
|
1704
2140
|
for (const rule of rules) {
|
|
1705
|
-
const severityColor = rule.severity === "BLOCKER" ?
|
|
1706
|
-
console.log(`${severityColor(`[${rule.severity}]`)} ${
|
|
1707
|
-
console.log(` Source: ${
|
|
1708
|
-
console.log(` Dependents: ${rule.dependents.map((d) =>
|
|
2141
|
+
const severityColor = rule.severity === "BLOCKER" ? chalk6.red : rule.severity === "WARNING" ? chalk6.yellow : chalk6.blue;
|
|
2142
|
+
console.log(`${severityColor(`[${rule.severity}]`)} ${chalk6.bold(rule.id)}`);
|
|
2143
|
+
console.log(` Source: ${chalk6.cyan(rule.source)}`);
|
|
2144
|
+
console.log(` Dependents: ${rule.dependents.map((d) => chalk6.dim(d)).join(", ")}`);
|
|
1709
2145
|
if (rule.reason && options.verbose) {
|
|
1710
|
-
console.log(` Reason: ${
|
|
2146
|
+
console.log(` Reason: ${chalk6.dim(rule.reason)}`);
|
|
1711
2147
|
}
|
|
1712
2148
|
if (rule.mustTest && options.verbose) {
|
|
1713
2149
|
console.log(` Must test: ${rule.mustTest.join(", ")}`);
|
|
@@ -1719,29 +2155,29 @@ Examples:
|
|
|
1719
2155
|
const files = options.files.split(",").map((f) => f.trim());
|
|
1720
2156
|
const parser = new DependencyParser();
|
|
1721
2157
|
if (!parser.exists()) {
|
|
1722
|
-
console.log(
|
|
2158
|
+
console.log(chalk6.dim("No DEPENDENCIES.md found. No dependency checks performed."));
|
|
1723
2159
|
process.exit(0);
|
|
1724
2160
|
}
|
|
1725
2161
|
const result = await parser.checkFiles(files);
|
|
1726
2162
|
if (result.impacts.length === 0) {
|
|
1727
|
-
console.log(
|
|
2163
|
+
console.log(chalk6.green("\u2705 No downstream dependency impacts found."));
|
|
1728
2164
|
process.exit(0);
|
|
1729
2165
|
}
|
|
1730
|
-
console.log(
|
|
2166
|
+
console.log(chalk6.yellow(`
|
|
1731
2167
|
\u26A0\uFE0F Found ${result.impacts.length} dependency impact(s):
|
|
1732
2168
|
`));
|
|
1733
2169
|
for (const impact of result.impacts) {
|
|
1734
|
-
const severityColor = impact.rule.severity === "BLOCKER" ?
|
|
2170
|
+
const severityColor = impact.rule.severity === "BLOCKER" ? chalk6.red : impact.rule.severity === "WARNING" ? chalk6.yellow : chalk6.blue;
|
|
1735
2171
|
console.log(severityColor(`[${impact.rule.severity}] ${impact.rule.id}`));
|
|
1736
|
-
console.log(` Changing: ${
|
|
2172
|
+
console.log(` Changing: ${chalk6.cyan(impact.matchedSource)}`);
|
|
1737
2173
|
console.log(` May impact: ${impact.affectedDependents.join(", ")}`);
|
|
1738
2174
|
if (impact.rule.reason) {
|
|
1739
|
-
console.log(` Reason: ${
|
|
2175
|
+
console.log(` Reason: ${chalk6.dim(impact.rule.reason)}`);
|
|
1740
2176
|
}
|
|
1741
2177
|
console.log("");
|
|
1742
2178
|
}
|
|
1743
2179
|
if (result.hasBlockers) {
|
|
1744
|
-
console.log(
|
|
2180
|
+
console.log(chalk6.red("\u274C BLOCKER-level impacts found. Review before proceeding."));
|
|
1745
2181
|
process.exit(1);
|
|
1746
2182
|
}
|
|
1747
2183
|
process.exit(0);
|
|
@@ -1755,7 +2191,7 @@ Examples:
|
|
|
1755
2191
|
let existingRules = [];
|
|
1756
2192
|
let markdownBody = "";
|
|
1757
2193
|
if (parser.exists()) {
|
|
1758
|
-
const content = await
|
|
2194
|
+
const content = await readFile4(DEPENDENCIES_FILENAME, "utf-8");
|
|
1759
2195
|
const result = await parser.parse();
|
|
1760
2196
|
if (result.success && result.document) {
|
|
1761
2197
|
existingRules = result.document.rules;
|
|
@@ -1783,7 +2219,7 @@ Examples:
|
|
|
1783
2219
|
(r) => r.source === source && r.dependents.includes(dependent)
|
|
1784
2220
|
);
|
|
1785
2221
|
if (existingRule) {
|
|
1786
|
-
console.log(
|
|
2222
|
+
console.log(chalk6.yellow(`Rule already exists: ${existingRule.id}`));
|
|
1787
2223
|
return;
|
|
1788
2224
|
}
|
|
1789
2225
|
const newRule = {
|
|
@@ -1794,23 +2230,23 @@ Examples:
|
|
|
1794
2230
|
reason
|
|
1795
2231
|
};
|
|
1796
2232
|
existingRules.push(newRule);
|
|
1797
|
-
const
|
|
1798
|
-
await
|
|
1799
|
-
${
|
|
1800
|
-
console.log(
|
|
1801
|
-
console.log(` Source: ${
|
|
1802
|
-
console.log(` Dependent: ${
|
|
2233
|
+
const yaml3 = generateYamlFrontmatter(existingRules);
|
|
2234
|
+
await writeFile4(DEPENDENCIES_FILENAME, `---
|
|
2235
|
+
${yaml3}---${markdownBody}`, "utf-8");
|
|
2236
|
+
console.log(chalk6.green(`\u2705 Added dependency rule: ${nextId}`));
|
|
2237
|
+
console.log(` Source: ${chalk6.cyan(source)}`);
|
|
2238
|
+
console.log(` Dependent: ${chalk6.dim(dependent)}`);
|
|
1803
2239
|
});
|
|
1804
2240
|
deps.command("graph").description("Generate Mermaid diagram of dependencies").option("--output <file>", "Write to file instead of stdout").action(async (options) => {
|
|
1805
2241
|
const parser = new DependencyParser();
|
|
1806
2242
|
if (!parser.exists()) {
|
|
1807
|
-
console.log(
|
|
2243
|
+
console.log(chalk6.yellow("No DEPENDENCIES.md found."));
|
|
1808
2244
|
return;
|
|
1809
2245
|
}
|
|
1810
2246
|
const mermaid = await parser.generateGraph();
|
|
1811
2247
|
if (options.output) {
|
|
1812
|
-
await
|
|
1813
|
-
console.log(
|
|
2248
|
+
await writeFile4(options.output, mermaid, "utf-8");
|
|
2249
|
+
console.log(chalk6.green(`\u2705 Graph written to ${options.output}`));
|
|
1814
2250
|
} else {
|
|
1815
2251
|
console.log("\n```mermaid");
|
|
1816
2252
|
console.log(mermaid);
|
|
@@ -1818,8 +2254,8 @@ ${yaml2}---${markdownBody}`, "utf-8");
|
|
|
1818
2254
|
}
|
|
1819
2255
|
});
|
|
1820
2256
|
deps.command("init").description("Create a starter DEPENDENCIES.md file").action(async () => {
|
|
1821
|
-
if (
|
|
1822
|
-
console.log(
|
|
2257
|
+
if (existsSync6(DEPENDENCIES_FILENAME)) {
|
|
2258
|
+
console.log(chalk6.yellow("DEPENDENCIES.md already exists."));
|
|
1823
2259
|
return;
|
|
1824
2260
|
}
|
|
1825
2261
|
const template = `---
|
|
@@ -1865,9 +2301,9 @@ rules:
|
|
|
1865
2301
|
|
|
1866
2302
|
*Powered by [ArchonDev](https://archondev.io)*
|
|
1867
2303
|
`;
|
|
1868
|
-
await
|
|
1869
|
-
console.log(
|
|
1870
|
-
console.log(
|
|
2304
|
+
await writeFile4(DEPENDENCIES_FILENAME, template, "utf-8");
|
|
2305
|
+
console.log(chalk6.green("\u2705 Created DEPENDENCIES.md"));
|
|
2306
|
+
console.log(chalk6.dim("Add your first rule with: archon deps add --source <path> --dependent <path>"));
|
|
1871
2307
|
});
|
|
1872
2308
|
return deps;
|
|
1873
2309
|
}
|
|
@@ -1904,10 +2340,10 @@ function generateYamlFrontmatter(rules) {
|
|
|
1904
2340
|
}
|
|
1905
2341
|
|
|
1906
2342
|
// src/cli/a11y.ts
|
|
1907
|
-
import
|
|
1908
|
-
import { existsSync as
|
|
1909
|
-
import { readFile as
|
|
1910
|
-
import { join as
|
|
2343
|
+
import chalk7 from "chalk";
|
|
2344
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2345
|
+
import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir3 } from "fs/promises";
|
|
2346
|
+
import { join as join6 } from "path";
|
|
1911
2347
|
import { createInterface } from "readline";
|
|
1912
2348
|
import { glob } from "glob";
|
|
1913
2349
|
var ACCESSIBILITY_CHECKS = {
|
|
@@ -1980,7 +2416,7 @@ async function scanForIssues(files) {
|
|
|
1980
2416
|
const issues = [];
|
|
1981
2417
|
for (const file of files) {
|
|
1982
2418
|
try {
|
|
1983
|
-
const content = await
|
|
2419
|
+
const content = await readFile5(file, "utf-8");
|
|
1984
2420
|
const lines = content.split("\n");
|
|
1985
2421
|
for (const [checkId, check] of Object.entries(ACCESSIBILITY_CHECKS)) {
|
|
1986
2422
|
for (const pattern of check.patterns) {
|
|
@@ -2007,8 +2443,8 @@ async function scanForIssues(files) {
|
|
|
2007
2443
|
return issues;
|
|
2008
2444
|
}
|
|
2009
2445
|
async function a11yCheck(options) {
|
|
2010
|
-
console.log(
|
|
2011
|
-
console.log(
|
|
2446
|
+
console.log(chalk7.blue("\n\u267F Pre-Deploy Accessibility Check\n"));
|
|
2447
|
+
console.log(chalk7.dim("Scanning for WCAG 2.2 AA compliance issues...\n"));
|
|
2012
2448
|
const patterns = [
|
|
2013
2449
|
"**/*.html",
|
|
2014
2450
|
"**/*.htm",
|
|
@@ -2022,13 +2458,13 @@ async function a11yCheck(options) {
|
|
|
2022
2458
|
let allFiles = [];
|
|
2023
2459
|
for (const pattern of patterns) {
|
|
2024
2460
|
const files = await glob(pattern, { cwd: process.cwd(), ignore: ignorePatterns });
|
|
2025
|
-
allFiles = allFiles.concat(files.map((f) =>
|
|
2461
|
+
allFiles = allFiles.concat(files.map((f) => join6(process.cwd(), f)));
|
|
2026
2462
|
}
|
|
2027
2463
|
if (allFiles.length === 0) {
|
|
2028
|
-
console.log(
|
|
2464
|
+
console.log(chalk7.yellow("No web files found to check."));
|
|
2029
2465
|
return;
|
|
2030
2466
|
}
|
|
2031
|
-
console.log(
|
|
2467
|
+
console.log(chalk7.dim(`Scanning ${allFiles.length} files...
|
|
2032
2468
|
`));
|
|
2033
2469
|
const issues = await scanForIssues(allFiles);
|
|
2034
2470
|
const report = {
|
|
@@ -2038,28 +2474,28 @@ async function a11yCheck(options) {
|
|
|
2038
2474
|
issues,
|
|
2039
2475
|
passed: issues.length === 0
|
|
2040
2476
|
};
|
|
2041
|
-
const archonDir =
|
|
2042
|
-
if (!
|
|
2043
|
-
await
|
|
2477
|
+
const archonDir = join6(process.cwd(), ".archon");
|
|
2478
|
+
if (!existsSync7(archonDir)) {
|
|
2479
|
+
await mkdir3(archonDir, { recursive: true });
|
|
2044
2480
|
}
|
|
2045
|
-
await
|
|
2481
|
+
await writeFile5(join6(archonDir, "a11y-report.json"), JSON.stringify(report, null, 2));
|
|
2046
2482
|
if (issues.length === 0) {
|
|
2047
|
-
console.log(
|
|
2048
|
-
console.log(
|
|
2049
|
-
console.log(
|
|
2483
|
+
console.log(chalk7.green("\u2705 Accessibility Audit Passed\n"));
|
|
2484
|
+
console.log(chalk7.dim("Your site meets WCAG 2.2 AA requirements based on static analysis."));
|
|
2485
|
+
console.log(chalk7.dim("Note: Run manual testing with screen readers for full compliance.\n"));
|
|
2050
2486
|
return;
|
|
2051
2487
|
}
|
|
2052
|
-
console.log(
|
|
2488
|
+
console.log(chalk7.red(`\u26A0\uFE0F ${issues.length} Accessibility Issues Found
|
|
2053
2489
|
`));
|
|
2054
2490
|
const criticalCount = issues.filter((i) => i.severity === "critical").length;
|
|
2055
2491
|
const majorCount = issues.filter((i) => i.severity === "major").length;
|
|
2056
2492
|
const minorCount = issues.filter((i) => i.severity === "minor").length;
|
|
2057
|
-
console.log(
|
|
2058
|
-
if (criticalCount > 0) console.log(
|
|
2059
|
-
if (majorCount > 0) console.log(
|
|
2060
|
-
if (minorCount > 0) console.log(
|
|
2493
|
+
console.log(chalk7.dim("Summary:"));
|
|
2494
|
+
if (criticalCount > 0) console.log(chalk7.red(` \u2022 ${criticalCount} critical`));
|
|
2495
|
+
if (majorCount > 0) console.log(chalk7.yellow(` \u2022 ${majorCount} major`));
|
|
2496
|
+
if (minorCount > 0) console.log(chalk7.dim(` \u2022 ${minorCount} minor`));
|
|
2061
2497
|
console.log();
|
|
2062
|
-
console.log(
|
|
2498
|
+
console.log(chalk7.bold("Issues:\n"));
|
|
2063
2499
|
const issuesByFile = /* @__PURE__ */ new Map();
|
|
2064
2500
|
for (const issue of issues) {
|
|
2065
2501
|
const existing = issuesByFile.get(issue.file) || [];
|
|
@@ -2067,43 +2503,43 @@ async function a11yCheck(options) {
|
|
|
2067
2503
|
issuesByFile.set(issue.file, existing);
|
|
2068
2504
|
}
|
|
2069
2505
|
for (const [file, fileIssues] of issuesByFile) {
|
|
2070
|
-
console.log(
|
|
2506
|
+
console.log(chalk7.cyan(` ${file}`));
|
|
2071
2507
|
for (const issue of fileIssues.slice(0, 5)) {
|
|
2072
|
-
const color = issue.severity === "critical" ?
|
|
2508
|
+
const color = issue.severity === "critical" ? chalk7.red : issue.severity === "major" ? chalk7.yellow : chalk7.dim;
|
|
2073
2509
|
console.log(color(` Line ${issue.line}: ${issue.message} (WCAG ${issue.wcag})`));
|
|
2074
2510
|
}
|
|
2075
2511
|
if (fileIssues.length > 5) {
|
|
2076
|
-
console.log(
|
|
2512
|
+
console.log(chalk7.dim(` ... and ${fileIssues.length - 5} more issues`));
|
|
2077
2513
|
}
|
|
2078
2514
|
console.log();
|
|
2079
2515
|
}
|
|
2080
|
-
console.log(
|
|
2081
|
-
console.log(
|
|
2082
|
-
console.log(
|
|
2083
|
-
console.log(
|
|
2084
|
-
console.log(
|
|
2085
|
-
console.log(
|
|
2516
|
+
console.log(chalk7.bold.yellow("\n\u2696\uFE0F Legal Notice\n"));
|
|
2517
|
+
console.log(chalk7.dim("Websites that don't meet accessibility standards may violate:"));
|
|
2518
|
+
console.log(chalk7.dim(" \u2022 ADA (Americans with Disabilities Act) \u2014 US"));
|
|
2519
|
+
console.log(chalk7.dim(" \u2022 EAA (European Accessibility Act) \u2014 EU, effective June 2025"));
|
|
2520
|
+
console.log(chalk7.dim(" \u2022 Section 508 \u2014 US federal agencies and contractors"));
|
|
2521
|
+
console.log(chalk7.dim(" \u2022 AODA (Accessibility for Ontarians) \u2014 Ontario, Canada"));
|
|
2086
2522
|
console.log();
|
|
2087
|
-
console.log(
|
|
2088
|
-
console.log(
|
|
2089
|
-
console.log(
|
|
2523
|
+
console.log(chalk7.dim("Non-compliance can result in lawsuits, fines, and reputational damage."));
|
|
2524
|
+
console.log(chalk7.dim("In 2023, over 4,600 ADA web accessibility lawsuits were filed in the US.\n"));
|
|
2525
|
+
console.log(chalk7.dim(`Full report saved to: .archon/a11y-report.json`));
|
|
2090
2526
|
}
|
|
2091
2527
|
async function a11yFix(options) {
|
|
2092
2528
|
const prompt2 = createPrompt();
|
|
2093
2529
|
try {
|
|
2094
|
-
console.log(
|
|
2095
|
-
const reportPath =
|
|
2096
|
-
if (!
|
|
2097
|
-
console.log(
|
|
2530
|
+
console.log(chalk7.blue("\n\u267F Accessibility Auto-Fix\n"));
|
|
2531
|
+
const reportPath = join6(process.cwd(), ".archon/a11y-report.json");
|
|
2532
|
+
if (!existsSync7(reportPath)) {
|
|
2533
|
+
console.log(chalk7.yellow("No accessibility report found. Running check first...\n"));
|
|
2098
2534
|
await a11yCheck({});
|
|
2099
2535
|
}
|
|
2100
|
-
const reportContent = await
|
|
2536
|
+
const reportContent = await readFile5(reportPath, "utf-8");
|
|
2101
2537
|
const report = JSON.parse(reportContent);
|
|
2102
2538
|
if (report.passed || report.issues.length === 0) {
|
|
2103
|
-
console.log(
|
|
2539
|
+
console.log(chalk7.green("No issues to fix!"));
|
|
2104
2540
|
return;
|
|
2105
2541
|
}
|
|
2106
|
-
console.log(
|
|
2542
|
+
console.log(chalk7.dim(`Found ${report.issues.length} issues from last check.
|
|
2107
2543
|
`));
|
|
2108
2544
|
const fixablePatterns = [
|
|
2109
2545
|
{
|
|
@@ -2134,19 +2570,19 @@ async function a11yFix(options) {
|
|
|
2134
2570
|
for (const pattern of patterns) {
|
|
2135
2571
|
const files = await glob(pattern, { cwd: process.cwd(), ignore: ignorePatterns });
|
|
2136
2572
|
for (const file of files) {
|
|
2137
|
-
const filePath =
|
|
2573
|
+
const filePath = join6(process.cwd(), file);
|
|
2138
2574
|
try {
|
|
2139
|
-
const content = await
|
|
2575
|
+
const content = await readFile5(filePath, "utf-8");
|
|
2140
2576
|
if (fix.pattern.test(content)) {
|
|
2141
2577
|
fix.pattern.lastIndex = 0;
|
|
2142
2578
|
const matches = content.match(fix.pattern);
|
|
2143
2579
|
const count = matches?.length || 0;
|
|
2144
2580
|
if (count > 0) {
|
|
2145
|
-
console.log(
|
|
2581
|
+
console.log(chalk7.cyan(` ${file}: ${count} fixes (${fix.description})`));
|
|
2146
2582
|
totalFixes += count;
|
|
2147
2583
|
if (!options.dryRun) {
|
|
2148
2584
|
const newContent = content.replace(fix.pattern, fix.replacement);
|
|
2149
|
-
await
|
|
2585
|
+
await writeFile5(filePath, newContent);
|
|
2150
2586
|
}
|
|
2151
2587
|
}
|
|
2152
2588
|
}
|
|
@@ -2156,21 +2592,21 @@ async function a11yFix(options) {
|
|
|
2156
2592
|
}
|
|
2157
2593
|
}
|
|
2158
2594
|
if (totalFixes === 0) {
|
|
2159
|
-
console.log(
|
|
2595
|
+
console.log(chalk7.dim("No auto-fixable issues found. Some issues require manual fixes."));
|
|
2160
2596
|
} else if (options.dryRun) {
|
|
2161
|
-
console.log(
|
|
2597
|
+
console.log(chalk7.yellow(`
|
|
2162
2598
|
${totalFixes} fixes would be applied. Run without --dry-run to apply.`));
|
|
2163
2599
|
} else {
|
|
2164
|
-
console.log(
|
|
2600
|
+
console.log(chalk7.green(`
|
|
2165
2601
|
\u2705 Applied ${totalFixes} fixes.`));
|
|
2166
|
-
console.log(
|
|
2602
|
+
console.log(chalk7.dim('Run "archon a11y check" to verify fixes.'));
|
|
2167
2603
|
}
|
|
2168
2604
|
} finally {
|
|
2169
2605
|
prompt2.close();
|
|
2170
2606
|
}
|
|
2171
2607
|
}
|
|
2172
2608
|
async function a11yBadge(options) {
|
|
2173
|
-
console.log(
|
|
2609
|
+
console.log(chalk7.blue("\n\u267F Accessibility Badge\n"));
|
|
2174
2610
|
const badgeHtml = `<!-- WCAG 2.2 AA Compliance Badge -->
|
|
2175
2611
|
<div class="flex items-center gap-2 text-xs text-text-muted dark:text-cream-300">
|
|
2176
2612
|
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
|
|
@@ -2185,35 +2621,35 @@ async function a11yBadge(options) {
|
|
|
2185
2621
|
footerFiles = footerFiles.concat(files);
|
|
2186
2622
|
}
|
|
2187
2623
|
if (footerFiles.length === 0) {
|
|
2188
|
-
console.log(
|
|
2189
|
-
console.log(
|
|
2624
|
+
console.log(chalk7.yellow("No footer files found."));
|
|
2625
|
+
console.log(chalk7.dim("\nManually add this badge to your footer:\n"));
|
|
2190
2626
|
console.log(badgeHtml);
|
|
2191
2627
|
return;
|
|
2192
2628
|
}
|
|
2193
|
-
console.log(
|
|
2629
|
+
console.log(chalk7.dim(`Found ${footerFiles.length} footer file(s):
|
|
2194
2630
|
`));
|
|
2195
2631
|
for (const file of footerFiles) {
|
|
2196
|
-
console.log(
|
|
2632
|
+
console.log(chalk7.cyan(` ${file}`));
|
|
2197
2633
|
}
|
|
2198
2634
|
if (options.remove) {
|
|
2199
|
-
console.log(
|
|
2200
|
-
console.log(
|
|
2635
|
+
console.log(chalk7.yellow("\nRemoving accessibility badge..."));
|
|
2636
|
+
console.log(chalk7.dim("Badge removal not yet implemented. Manually remove the WCAG badge comment block."));
|
|
2201
2637
|
} else {
|
|
2202
|
-
console.log(
|
|
2203
|
-
console.log(
|
|
2638
|
+
console.log(chalk7.dim("\nTo add the badge, insert this code before the closing </footer> tag:\n"));
|
|
2639
|
+
console.log(chalk7.green(badgeHtml));
|
|
2204
2640
|
}
|
|
2205
2641
|
}
|
|
2206
2642
|
async function a11yPreDeploy() {
|
|
2207
2643
|
const prompt2 = createPrompt();
|
|
2208
2644
|
try {
|
|
2209
|
-
console.log(
|
|
2210
|
-
console.log(
|
|
2645
|
+
console.log(chalk7.blue("\n\u26A0\uFE0F Pre-Deploy Accessibility Check\n"));
|
|
2646
|
+
console.log(chalk7.dim("Before deploying a live website, accessibility compliance is required.\n"));
|
|
2211
2647
|
await a11yCheck({});
|
|
2212
|
-
const reportPath =
|
|
2213
|
-
if (!
|
|
2648
|
+
const reportPath = join6(process.cwd(), ".archon/a11y-report.json");
|
|
2649
|
+
if (!existsSync7(reportPath)) {
|
|
2214
2650
|
return false;
|
|
2215
2651
|
}
|
|
2216
|
-
const reportContent = await
|
|
2652
|
+
const reportContent = await readFile5(reportPath, "utf-8");
|
|
2217
2653
|
const report = JSON.parse(reportContent);
|
|
2218
2654
|
if (report.passed) {
|
|
2219
2655
|
const addBadge = await prompt2.ask("\nWould you like to add a WCAG 2.2 AA badge to your footer? (y/N): ");
|
|
@@ -2222,7 +2658,7 @@ async function a11yPreDeploy() {
|
|
|
2222
2658
|
}
|
|
2223
2659
|
return true;
|
|
2224
2660
|
}
|
|
2225
|
-
console.log(
|
|
2661
|
+
console.log(chalk7.bold("\nOptions:\n"));
|
|
2226
2662
|
console.log(" 1) Fix issues now (recommended)");
|
|
2227
2663
|
console.log(" 2) Deploy anyway (not recommended)");
|
|
2228
2664
|
console.log(" 3) Cancel deployment\n");
|
|
@@ -2232,11 +2668,11 @@ async function a11yPreDeploy() {
|
|
|
2232
2668
|
await a11yCheck({});
|
|
2233
2669
|
return true;
|
|
2234
2670
|
} else if (choice === "2") {
|
|
2235
|
-
console.log(
|
|
2236
|
-
console.log(
|
|
2671
|
+
console.log(chalk7.yellow("\n\u26A0\uFE0F Acknowledged. Proceeding without full accessibility compliance."));
|
|
2672
|
+
console.log(chalk7.dim("Consider addressing these issues in a future session.\n"));
|
|
2237
2673
|
return true;
|
|
2238
2674
|
} else {
|
|
2239
|
-
console.log(
|
|
2675
|
+
console.log(chalk7.dim("\nDeployment cancelled."));
|
|
2240
2676
|
return false;
|
|
2241
2677
|
}
|
|
2242
2678
|
} finally {
|
|
@@ -2246,14 +2682,14 @@ async function a11yPreDeploy() {
|
|
|
2246
2682
|
|
|
2247
2683
|
// src/cli/geo.ts
|
|
2248
2684
|
import { Command as Command2 } from "commander";
|
|
2249
|
-
import
|
|
2250
|
-
import { readFile as
|
|
2251
|
-
import { existsSync as
|
|
2252
|
-
import { join as
|
|
2685
|
+
import chalk8 from "chalk";
|
|
2686
|
+
import { readFile as readFile6, writeFile as writeFile6, mkdir as mkdir4 } from "fs/promises";
|
|
2687
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2688
|
+
import { join as join7 } from "path";
|
|
2253
2689
|
import { createInterface as createInterface2 } from "readline";
|
|
2254
2690
|
import { glob as glob2 } from "glob";
|
|
2255
|
-
import * as
|
|
2256
|
-
var
|
|
2691
|
+
import * as yaml2 from "yaml";
|
|
2692
|
+
var CONFIG_PATH2 = ".archon/config.yaml";
|
|
2257
2693
|
function createPrompt2() {
|
|
2258
2694
|
const rl = createInterface2({
|
|
2259
2695
|
input: process.stdin,
|
|
@@ -2267,33 +2703,33 @@ function createPrompt2() {
|
|
|
2267
2703
|
};
|
|
2268
2704
|
}
|
|
2269
2705
|
async function loadGeoConfig(cwd) {
|
|
2270
|
-
const configPath =
|
|
2271
|
-
if (!
|
|
2706
|
+
const configPath = join7(cwd, CONFIG_PATH2);
|
|
2707
|
+
if (!existsSync8(configPath)) {
|
|
2272
2708
|
return {};
|
|
2273
2709
|
}
|
|
2274
2710
|
try {
|
|
2275
|
-
const content = await
|
|
2276
|
-
return
|
|
2711
|
+
const content = await readFile6(configPath, "utf-8");
|
|
2712
|
+
return yaml2.parse(content);
|
|
2277
2713
|
} catch {
|
|
2278
2714
|
return {};
|
|
2279
2715
|
}
|
|
2280
2716
|
}
|
|
2281
2717
|
async function saveGeoConfig(cwd, config) {
|
|
2282
|
-
const configPath =
|
|
2283
|
-
const archonDir =
|
|
2284
|
-
if (!
|
|
2285
|
-
await
|
|
2718
|
+
const configPath = join7(cwd, CONFIG_PATH2);
|
|
2719
|
+
const archonDir = join7(cwd, ".archon");
|
|
2720
|
+
if (!existsSync8(archonDir)) {
|
|
2721
|
+
await mkdir4(archonDir, { recursive: true });
|
|
2286
2722
|
}
|
|
2287
2723
|
let existing = {};
|
|
2288
|
-
if (
|
|
2724
|
+
if (existsSync8(configPath)) {
|
|
2289
2725
|
try {
|
|
2290
|
-
const content = await
|
|
2291
|
-
existing =
|
|
2726
|
+
const content = await readFile6(configPath, "utf-8");
|
|
2727
|
+
existing = yaml2.parse(content);
|
|
2292
2728
|
} catch {
|
|
2293
2729
|
}
|
|
2294
2730
|
}
|
|
2295
2731
|
const merged = { ...existing, ...config };
|
|
2296
|
-
await
|
|
2732
|
+
await writeFile6(configPath, yaml2.stringify(merged), "utf-8");
|
|
2297
2733
|
}
|
|
2298
2734
|
async function readPageContent(patterns) {
|
|
2299
2735
|
const cwd = process.cwd();
|
|
@@ -2302,7 +2738,7 @@ async function readPageContent(patterns) {
|
|
|
2302
2738
|
const files = await glob2(pattern, { cwd, ignore: ["**/node_modules/**", "**/dist/**"] });
|
|
2303
2739
|
for (const file of files.slice(0, 3)) {
|
|
2304
2740
|
try {
|
|
2305
|
-
const fileContent = await
|
|
2741
|
+
const fileContent = await readFile6(join7(cwd, file), "utf-8");
|
|
2306
2742
|
content += `
|
|
2307
2743
|
--- File: ${file} ---
|
|
2308
2744
|
${fileContent.slice(0, 5e3)}
|
|
@@ -2370,13 +2806,13 @@ async function geoIdentity() {
|
|
|
2370
2806
|
const cwd = process.cwd();
|
|
2371
2807
|
const prompt2 = createPrompt2();
|
|
2372
2808
|
try {
|
|
2373
|
-
console.log(
|
|
2809
|
+
console.log(chalk8.blue("\n\u{1F3AF} GEO Identity Generator\n"));
|
|
2374
2810
|
const { allowed, tier } = await checkStrongModelAccess();
|
|
2375
2811
|
if (!allowed) {
|
|
2376
|
-
console.log(
|
|
2377
|
-
console.log(
|
|
2812
|
+
console.log(chalk8.yellow(`\u26A0\uFE0F Your tier (${tier}) uses basic models.`));
|
|
2813
|
+
console.log(chalk8.dim("For better results, add credits or use BYOK mode.\n"));
|
|
2378
2814
|
}
|
|
2379
|
-
console.log(
|
|
2815
|
+
console.log(chalk8.dim("Reading homepage and about page content...\n"));
|
|
2380
2816
|
const pageContent = await readPageContent([
|
|
2381
2817
|
"**/index.html",
|
|
2382
2818
|
"**/index.{jsx,tsx,astro,svelte,vue}",
|
|
@@ -2387,44 +2823,44 @@ async function geoIdentity() {
|
|
|
2387
2823
|
"README.md"
|
|
2388
2824
|
]);
|
|
2389
2825
|
if (pageContent === "No content found.") {
|
|
2390
|
-
console.log(
|
|
2391
|
-
console.log(
|
|
2826
|
+
console.log(chalk8.yellow("No homepage or about page content found."));
|
|
2827
|
+
console.log(chalk8.dim("Create an index file or README.md first.\n"));
|
|
2392
2828
|
return;
|
|
2393
2829
|
}
|
|
2394
|
-
console.log(
|
|
2830
|
+
console.log(chalk8.dim("Generating identity candidates with AI...\n"));
|
|
2395
2831
|
const candidates = await generateIdentityCandidates(pageContent);
|
|
2396
|
-
console.log(
|
|
2832
|
+
console.log(chalk8.bold("\u{1F4CC} 7-Word Brand Phrases:\n"));
|
|
2397
2833
|
candidates.phrases.forEach((p, i) => {
|
|
2398
|
-
console.log(` ${
|
|
2399
|
-
console.log(` ${
|
|
2834
|
+
console.log(` ${chalk8.cyan(`${i + 1})`)} ${chalk8.bold(p.phrase)}`);
|
|
2835
|
+
console.log(` ${chalk8.dim(p.rationale)}
|
|
2400
2836
|
`);
|
|
2401
2837
|
});
|
|
2402
2838
|
const phraseChoice = await prompt2.ask('Select a phrase (1-3) or "r" to regenerate: ');
|
|
2403
2839
|
if (phraseChoice.toLowerCase() === "r") {
|
|
2404
|
-
console.log(
|
|
2840
|
+
console.log(chalk8.dim("\nRegenerating... Run the command again.\n"));
|
|
2405
2841
|
return;
|
|
2406
2842
|
}
|
|
2407
2843
|
const phraseIndex = parseInt(phraseChoice, 10) - 1;
|
|
2408
2844
|
const selectedPhrase = candidates.phrases[phraseIndex];
|
|
2409
2845
|
if (isNaN(phraseIndex) || phraseIndex < 0 || phraseIndex >= candidates.phrases.length || !selectedPhrase) {
|
|
2410
|
-
console.log(
|
|
2846
|
+
console.log(chalk8.red("Invalid selection."));
|
|
2411
2847
|
return;
|
|
2412
2848
|
}
|
|
2413
|
-
console.log(
|
|
2849
|
+
console.log(chalk8.bold("\n\u{1F4DD} 50-Word Descriptions:\n"));
|
|
2414
2850
|
candidates.descriptions.forEach((d, i) => {
|
|
2415
|
-
console.log(` ${
|
|
2416
|
-
console.log(` ${
|
|
2851
|
+
console.log(` ${chalk8.cyan(`${i + 1})`)} ${d.description}`);
|
|
2852
|
+
console.log(` ${chalk8.dim(d.rationale)}
|
|
2417
2853
|
`);
|
|
2418
2854
|
});
|
|
2419
2855
|
const descChoice = await prompt2.ask('Select a description (1-3) or "r" to regenerate: ');
|
|
2420
2856
|
if (descChoice.toLowerCase() === "r") {
|
|
2421
|
-
console.log(
|
|
2857
|
+
console.log(chalk8.dim("\nRegenerating... Run the command again.\n"));
|
|
2422
2858
|
return;
|
|
2423
2859
|
}
|
|
2424
2860
|
const descIndex = parseInt(descChoice, 10) - 1;
|
|
2425
2861
|
const selectedDescription = candidates.descriptions[descIndex];
|
|
2426
2862
|
if (isNaN(descIndex) || descIndex < 0 || descIndex >= candidates.descriptions.length || !selectedDescription) {
|
|
2427
|
-
console.log(
|
|
2863
|
+
console.log(chalk8.red("Invalid selection."));
|
|
2428
2864
|
return;
|
|
2429
2865
|
}
|
|
2430
2866
|
const geoConfig = {
|
|
@@ -2435,31 +2871,31 @@ async function geoIdentity() {
|
|
|
2435
2871
|
}
|
|
2436
2872
|
};
|
|
2437
2873
|
await saveGeoConfig(cwd, geoConfig);
|
|
2438
|
-
console.log(
|
|
2439
|
-
console.log(
|
|
2440
|
-
console.log(
|
|
2874
|
+
console.log(chalk8.green("\n\u2705 Identity saved!\n"));
|
|
2875
|
+
console.log(chalk8.dim(` Phrase: ${selectedPhrase.phrase}`));
|
|
2876
|
+
console.log(chalk8.dim(` Description: ${selectedDescription.description.slice(0, 60)}...`));
|
|
2441
2877
|
console.log();
|
|
2442
|
-
console.log(
|
|
2878
|
+
console.log(chalk8.cyan(`Use 'archon geo schema' to generate JSON-LD.`));
|
|
2443
2879
|
} finally {
|
|
2444
2880
|
prompt2.close();
|
|
2445
2881
|
}
|
|
2446
2882
|
}
|
|
2447
2883
|
async function geoSchema(options) {
|
|
2448
2884
|
const cwd = process.cwd();
|
|
2449
|
-
console.log(
|
|
2885
|
+
console.log(chalk8.blue("\n\u{1F4E6} JSON-LD Schema Generator\n"));
|
|
2450
2886
|
const config = await loadGeoConfig(cwd);
|
|
2451
2887
|
if (!config.geo?.identityPhrase || !config.geo?.shortDescription) {
|
|
2452
|
-
console.log(
|
|
2453
|
-
console.log(
|
|
2888
|
+
console.log(chalk8.yellow("No identity defined."));
|
|
2889
|
+
console.log(chalk8.dim(`Run 'archon geo identity' first.
|
|
2454
2890
|
`));
|
|
2455
2891
|
return;
|
|
2456
2892
|
}
|
|
2457
2893
|
const { identityPhrase, shortDescription } = config.geo;
|
|
2458
2894
|
let orgName = identityPhrase.split(" ").slice(0, 2).join(" ");
|
|
2459
2895
|
try {
|
|
2460
|
-
const pkgPath =
|
|
2461
|
-
if (
|
|
2462
|
-
const pkg = JSON.parse(await
|
|
2896
|
+
const pkgPath = join7(cwd, "package.json");
|
|
2897
|
+
if (existsSync8(pkgPath)) {
|
|
2898
|
+
const pkg = JSON.parse(await readFile6(pkgPath, "utf-8"));
|
|
2463
2899
|
if (pkg.name) {
|
|
2464
2900
|
orgName = pkg.name.replace(/-/g, " ").replace(/^\w/, (c) => c.toUpperCase());
|
|
2465
2901
|
}
|
|
@@ -2493,40 +2929,40 @@ async function geoSchema(options) {
|
|
|
2493
2929
|
};
|
|
2494
2930
|
const jsonLd = JSON.stringify(schema, null, 2);
|
|
2495
2931
|
if (options.output) {
|
|
2496
|
-
await
|
|
2497
|
-
console.log(
|
|
2932
|
+
await writeFile6(options.output, jsonLd, "utf-8");
|
|
2933
|
+
console.log(chalk8.green(`\u2705 Schema written to ${options.output}`));
|
|
2498
2934
|
} else {
|
|
2499
|
-
console.log(
|
|
2500
|
-
console.log(
|
|
2935
|
+
console.log(chalk8.bold("Generated JSON-LD:\n"));
|
|
2936
|
+
console.log(chalk8.cyan(jsonLd));
|
|
2501
2937
|
}
|
|
2502
2938
|
if (options.apply) {
|
|
2503
|
-
console.log(
|
|
2939
|
+
console.log(chalk8.dim("\nAttempting to insert into homepage <head>...\n"));
|
|
2504
2940
|
const indexFiles = await glob2("**/index.html", { cwd, ignore: ["**/node_modules/**", "**/dist/**"] });
|
|
2505
2941
|
if (indexFiles.length === 0) {
|
|
2506
|
-
console.log(
|
|
2507
|
-
console.log(
|
|
2942
|
+
console.log(chalk8.yellow("No index.html found. Manually add to your HTML <head>:"));
|
|
2943
|
+
console.log(chalk8.dim(`<script type="application/ld+json">
|
|
2508
2944
|
${jsonLd}
|
|
2509
2945
|
</script>`));
|
|
2510
2946
|
return;
|
|
2511
2947
|
}
|
|
2512
2948
|
const firstIndexFile = indexFiles[0];
|
|
2513
2949
|
if (!firstIndexFile) {
|
|
2514
|
-
console.log(
|
|
2950
|
+
console.log(chalk8.yellow("No index.html found."));
|
|
2515
2951
|
return;
|
|
2516
2952
|
}
|
|
2517
|
-
const indexPath =
|
|
2518
|
-
let indexContent = await
|
|
2953
|
+
const indexPath = join7(cwd, firstIndexFile);
|
|
2954
|
+
let indexContent = await readFile6(indexPath, "utf-8");
|
|
2519
2955
|
const scriptTag = `<script type="application/ld+json">
|
|
2520
2956
|
${jsonLd}
|
|
2521
2957
|
</script>`;
|
|
2522
2958
|
if (indexContent.includes("application/ld+json")) {
|
|
2523
|
-
console.log(
|
|
2959
|
+
console.log(chalk8.yellow("JSON-LD already exists in index.html. Remove it first or update manually."));
|
|
2524
2960
|
return;
|
|
2525
2961
|
}
|
|
2526
2962
|
indexContent = indexContent.replace("</head>", `${scriptTag}
|
|
2527
2963
|
</head>`);
|
|
2528
|
-
await
|
|
2529
|
-
console.log(
|
|
2964
|
+
await writeFile6(indexPath, indexContent, "utf-8");
|
|
2965
|
+
console.log(chalk8.green(`\u2705 JSON-LD inserted into ${firstIndexFile}`));
|
|
2530
2966
|
}
|
|
2531
2967
|
console.log();
|
|
2532
2968
|
}
|
|
@@ -2542,21 +2978,21 @@ Guidelines:
|
|
|
2542
2978
|
Output your response as valid JSON.`;
|
|
2543
2979
|
async function geoFaq(options) {
|
|
2544
2980
|
const cwd = process.cwd();
|
|
2545
|
-
console.log(
|
|
2981
|
+
console.log(chalk8.blue("\n\u2753 FAQ Schema Generator\n"));
|
|
2546
2982
|
const config = await loadGeoConfig(cwd);
|
|
2547
2983
|
if (!config.geo?.identityPhrase || !config.geo?.shortDescription) {
|
|
2548
|
-
console.log(
|
|
2549
|
-
console.log(
|
|
2984
|
+
console.log(chalk8.yellow("No identity defined."));
|
|
2985
|
+
console.log(chalk8.dim(`Run 'archon geo identity' first.
|
|
2550
2986
|
`));
|
|
2551
2987
|
return;
|
|
2552
2988
|
}
|
|
2553
2989
|
const { identityPhrase, shortDescription } = config.geo;
|
|
2554
2990
|
const { allowed, tier } = await checkStrongModelAccess();
|
|
2555
2991
|
if (!allowed) {
|
|
2556
|
-
console.log(
|
|
2557
|
-
console.log(
|
|
2992
|
+
console.log(chalk8.yellow(`\u26A0\uFE0F Your tier (${tier}) uses basic models.`));
|
|
2993
|
+
console.log(chalk8.dim("For better results, add credits or use BYOK mode.\n"));
|
|
2558
2994
|
}
|
|
2559
|
-
console.log(
|
|
2995
|
+
console.log(chalk8.dim("Generating FAQ content with AI...\n"));
|
|
2560
2996
|
const agent = new ArchitectAgent({ temperature: 0.7 });
|
|
2561
2997
|
const prompt2 = `Generate FAQ content for a product/service with:
|
|
2562
2998
|
- Brand phrase: "${identityPhrase}"
|
|
@@ -2576,7 +3012,7 @@ Generate 6-8 FAQs as JSON:
|
|
|
2576
3012
|
);
|
|
2577
3013
|
const jsonMatch = response.content.match(/\{[\s\S]*\}/);
|
|
2578
3014
|
if (!jsonMatch) {
|
|
2579
|
-
console.log(
|
|
3015
|
+
console.log(chalk8.red("Failed to generate FAQ content."));
|
|
2580
3016
|
return;
|
|
2581
3017
|
}
|
|
2582
3018
|
const parsed = JSON.parse(jsonMatch[0]);
|
|
@@ -2594,17 +3030,17 @@ Generate 6-8 FAQs as JSON:
|
|
|
2594
3030
|
};
|
|
2595
3031
|
const jsonLd = JSON.stringify(faqSchema, null, 2);
|
|
2596
3032
|
if (options.output) {
|
|
2597
|
-
await
|
|
2598
|
-
console.log(
|
|
3033
|
+
await writeFile6(options.output, jsonLd, "utf-8");
|
|
3034
|
+
console.log(chalk8.green(`\u2705 FAQ schema written to ${options.output}`));
|
|
2599
3035
|
} else {
|
|
2600
|
-
console.log(
|
|
2601
|
-
console.log(
|
|
3036
|
+
console.log(chalk8.bold("Generated FAQPage JSON-LD:\n"));
|
|
3037
|
+
console.log(chalk8.cyan(jsonLd));
|
|
2602
3038
|
}
|
|
2603
3039
|
console.log();
|
|
2604
3040
|
}
|
|
2605
3041
|
async function geoAudit() {
|
|
2606
3042
|
const cwd = process.cwd();
|
|
2607
|
-
console.log(
|
|
3043
|
+
console.log(chalk8.blue("\n\u{1F50D} GEO Audit\n"));
|
|
2608
3044
|
const result = {
|
|
2609
3045
|
identityDefined: false,
|
|
2610
3046
|
phraseInH1: false,
|
|
@@ -2617,75 +3053,75 @@ async function geoAudit() {
|
|
|
2617
3053
|
const config = await loadGeoConfig(cwd);
|
|
2618
3054
|
if (config.geo?.identityPhrase && config.geo?.shortDescription) {
|
|
2619
3055
|
result.identityDefined = true;
|
|
2620
|
-
console.log(
|
|
2621
|
-
console.log(
|
|
3056
|
+
console.log(chalk8.green("\u2705 Identity defined"));
|
|
3057
|
+
console.log(chalk8.dim(` Phrase: ${config.geo.identityPhrase}`));
|
|
2622
3058
|
} else {
|
|
2623
3059
|
result.issues.push('Identity not defined. Run "archon geo identity"');
|
|
2624
|
-
console.log(
|
|
3060
|
+
console.log(chalk8.red("\u274C Identity not defined"));
|
|
2625
3061
|
}
|
|
2626
3062
|
const htmlFiles = await glob2("**/index.html", { cwd, ignore: ["**/node_modules/**", "**/dist/**"] });
|
|
2627
3063
|
const firstHtmlFile = htmlFiles[0];
|
|
2628
3064
|
if (firstHtmlFile && config.geo?.identityPhrase) {
|
|
2629
|
-
const indexPath =
|
|
2630
|
-
const content = await
|
|
3065
|
+
const indexPath = join7(cwd, firstHtmlFile);
|
|
3066
|
+
const content = await readFile6(indexPath, "utf-8");
|
|
2631
3067
|
const identityPhrase = config.geo.identityPhrase;
|
|
2632
3068
|
const firstKeyword = identityPhrase.toLowerCase().split(" ")[0] ?? "";
|
|
2633
3069
|
const h1Match = content.match(/<h1[^>]*>(.*?)<\/h1>/is);
|
|
2634
3070
|
if (h1Match?.[1] && h1Match[1].toLowerCase().includes(firstKeyword)) {
|
|
2635
3071
|
result.phraseInH1 = true;
|
|
2636
|
-
console.log(
|
|
3072
|
+
console.log(chalk8.green("\u2705 Brand keyword in H1"));
|
|
2637
3073
|
} else {
|
|
2638
3074
|
result.issues.push("Brand phrase keyword not found in H1");
|
|
2639
|
-
console.log(
|
|
3075
|
+
console.log(chalk8.yellow("\u26A0\uFE0F Brand keyword not in H1"));
|
|
2640
3076
|
}
|
|
2641
3077
|
const metaMatch = content.match(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']*)["']/i);
|
|
2642
3078
|
if (metaMatch?.[1] && metaMatch[1].toLowerCase().includes(firstKeyword)) {
|
|
2643
3079
|
result.phraseInMetaDescription = true;
|
|
2644
|
-
console.log(
|
|
3080
|
+
console.log(chalk8.green("\u2705 Brand keyword in meta description"));
|
|
2645
3081
|
} else {
|
|
2646
3082
|
result.issues.push("Brand phrase keyword not found in meta description");
|
|
2647
|
-
console.log(
|
|
3083
|
+
console.log(chalk8.yellow("\u26A0\uFE0F Brand keyword not in meta description"));
|
|
2648
3084
|
}
|
|
2649
3085
|
if (content.includes("application/ld+json")) {
|
|
2650
3086
|
if (content.includes('"@type":"Organization"') || content.includes('"@type": "Organization"')) {
|
|
2651
3087
|
result.hasOrganizationSchema = true;
|
|
2652
|
-
console.log(
|
|
3088
|
+
console.log(chalk8.green("\u2705 Organization schema present"));
|
|
2653
3089
|
} else {
|
|
2654
3090
|
result.issues.push("Organization schema not found");
|
|
2655
|
-
console.log(
|
|
3091
|
+
console.log(chalk8.yellow("\u26A0\uFE0F Organization schema missing"));
|
|
2656
3092
|
}
|
|
2657
3093
|
if (content.includes('"@type":"Service"') || content.includes('"@type": "Service"')) {
|
|
2658
3094
|
result.hasServiceSchema = true;
|
|
2659
|
-
console.log(
|
|
3095
|
+
console.log(chalk8.green("\u2705 Service schema present"));
|
|
2660
3096
|
} else {
|
|
2661
3097
|
result.issues.push("Service schema not found");
|
|
2662
|
-
console.log(
|
|
3098
|
+
console.log(chalk8.yellow("\u26A0\uFE0F Service schema missing"));
|
|
2663
3099
|
}
|
|
2664
3100
|
if (content.includes('"@type":"FAQPage"') || content.includes('"@type": "FAQPage"')) {
|
|
2665
3101
|
result.hasFAQSchema = true;
|
|
2666
|
-
console.log(
|
|
3102
|
+
console.log(chalk8.green("\u2705 FAQPage schema present"));
|
|
2667
3103
|
} else {
|
|
2668
3104
|
result.issues.push("FAQPage schema not found");
|
|
2669
|
-
console.log(
|
|
3105
|
+
console.log(chalk8.yellow("\u26A0\uFE0F FAQPage schema missing"));
|
|
2670
3106
|
}
|
|
2671
3107
|
} else {
|
|
2672
3108
|
result.issues.push("No JSON-LD schemas found");
|
|
2673
|
-
console.log(
|
|
3109
|
+
console.log(chalk8.red("\u274C No JSON-LD schemas found"));
|
|
2674
3110
|
}
|
|
2675
3111
|
} else if (htmlFiles.length === 0) {
|
|
2676
3112
|
result.issues.push("No index.html found");
|
|
2677
|
-
console.log(
|
|
3113
|
+
console.log(chalk8.yellow("\u26A0\uFE0F No index.html found to audit"));
|
|
2678
3114
|
}
|
|
2679
3115
|
console.log();
|
|
2680
3116
|
const passed = result.issues.length === 0;
|
|
2681
3117
|
if (passed) {
|
|
2682
|
-
console.log(
|
|
3118
|
+
console.log(chalk8.green.bold("\u2705 GEO Audit Passed"));
|
|
2683
3119
|
} else {
|
|
2684
|
-
console.log(
|
|
3120
|
+
console.log(chalk8.yellow.bold(`\u26A0\uFE0F ${result.issues.length} issue(s) found`));
|
|
2685
3121
|
console.log();
|
|
2686
|
-
console.log(
|
|
3122
|
+
console.log(chalk8.bold("Recommendations:"));
|
|
2687
3123
|
result.issues.forEach((issue, i) => {
|
|
2688
|
-
console.log(
|
|
3124
|
+
console.log(chalk8.dim(` ${i + 1}. ${issue}`));
|
|
2689
3125
|
});
|
|
2690
3126
|
}
|
|
2691
3127
|
console.log();
|
|
@@ -2722,10 +3158,10 @@ Examples:
|
|
|
2722
3158
|
|
|
2723
3159
|
// src/cli/seo.ts
|
|
2724
3160
|
import { Command as Command3 } from "commander";
|
|
2725
|
-
import
|
|
2726
|
-
import { readFile as
|
|
2727
|
-
import { existsSync as
|
|
2728
|
-
import { join as
|
|
3161
|
+
import chalk9 from "chalk";
|
|
3162
|
+
import { readFile as readFile7, writeFile as writeFile7, mkdir as mkdir5 } from "fs/promises";
|
|
3163
|
+
import { existsSync as existsSync9 } from "fs";
|
|
3164
|
+
import { join as join8 } from "path";
|
|
2729
3165
|
import { createInterface as createInterface3 } from "readline";
|
|
2730
3166
|
import { glob as glob3 } from "glob";
|
|
2731
3167
|
var SEO_CHECKS = {
|
|
@@ -2834,7 +3270,7 @@ async function getWebFiles() {
|
|
|
2834
3270
|
let allFiles = [];
|
|
2835
3271
|
for (const pattern of patterns) {
|
|
2836
3272
|
const files = await glob3(pattern, { cwd: process.cwd(), ignore: ignorePatterns });
|
|
2837
|
-
allFiles = allFiles.concat(files.map((f) =>
|
|
3273
|
+
allFiles = allFiles.concat(files.map((f) => join8(process.cwd(), f)));
|
|
2838
3274
|
}
|
|
2839
3275
|
return allFiles;
|
|
2840
3276
|
}
|
|
@@ -2842,7 +3278,7 @@ async function scanForSeoIssues(files) {
|
|
|
2842
3278
|
const issues = [];
|
|
2843
3279
|
for (const file of files) {
|
|
2844
3280
|
try {
|
|
2845
|
-
const content = await
|
|
3281
|
+
const content = await readFile7(file, "utf-8");
|
|
2846
3282
|
const relativePath = file.replace(process.cwd() + "/", "");
|
|
2847
3283
|
for (const [checkId, check] of Object.entries(SEO_CHECKS)) {
|
|
2848
3284
|
const hasTag = check.regex.test(content);
|
|
@@ -2872,19 +3308,19 @@ function findHeadInsertionPoint(content) {
|
|
|
2872
3308
|
return null;
|
|
2873
3309
|
}
|
|
2874
3310
|
async function seoCheck(options) {
|
|
2875
|
-
console.log(
|
|
2876
|
-
console.log(
|
|
3311
|
+
console.log(chalk9.blue("\n\u{1F50D} SEO Check\n"));
|
|
3312
|
+
console.log(chalk9.dim("Scanning for SEO issues...\n"));
|
|
2877
3313
|
const files = await getWebFiles();
|
|
2878
3314
|
if (files.length === 0) {
|
|
2879
|
-
console.log(
|
|
3315
|
+
console.log(chalk9.yellow("No web files found to check."));
|
|
2880
3316
|
return;
|
|
2881
3317
|
}
|
|
2882
|
-
console.log(
|
|
3318
|
+
console.log(chalk9.dim(`Scanning ${files.length} files...
|
|
2883
3319
|
`));
|
|
2884
3320
|
const issues = await scanForSeoIssues(files);
|
|
2885
|
-
const archonDir =
|
|
2886
|
-
if (!
|
|
2887
|
-
await
|
|
3321
|
+
const archonDir = join8(process.cwd(), ".archon");
|
|
3322
|
+
if (!existsSync9(archonDir)) {
|
|
3323
|
+
await mkdir5(archonDir, { recursive: true });
|
|
2888
3324
|
}
|
|
2889
3325
|
const report = {
|
|
2890
3326
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2893,24 +3329,24 @@ async function seoCheck(options) {
|
|
|
2893
3329
|
issues,
|
|
2894
3330
|
passed: issues.length === 0
|
|
2895
3331
|
};
|
|
2896
|
-
await
|
|
3332
|
+
await writeFile7(join8(archonDir, "seo-report.json"), JSON.stringify(report, null, 2));
|
|
2897
3333
|
if (issues.length === 0) {
|
|
2898
|
-
console.log(
|
|
2899
|
-
console.log(
|
|
3334
|
+
console.log(chalk9.green("\u2705 SEO Check Passed\n"));
|
|
3335
|
+
console.log(chalk9.dim("All essential meta tags are present."));
|
|
2900
3336
|
return;
|
|
2901
3337
|
}
|
|
2902
|
-
console.log(
|
|
3338
|
+
console.log(chalk9.yellow(`\u26A0\uFE0F ${issues.length} SEO Issues Found
|
|
2903
3339
|
`));
|
|
2904
3340
|
const criticalCount = issues.filter((i) => i.severity === "critical").length;
|
|
2905
3341
|
const warningCount = issues.filter((i) => i.severity === "warning").length;
|
|
2906
3342
|
const infoCount = issues.filter((i) => i.severity === "info").length;
|
|
2907
|
-
console.log(
|
|
2908
|
-
if (criticalCount > 0) console.log(
|
|
2909
|
-
if (warningCount > 0) console.log(
|
|
2910
|
-
if (infoCount > 0) console.log(
|
|
3343
|
+
console.log(chalk9.dim("Summary:"));
|
|
3344
|
+
if (criticalCount > 0) console.log(chalk9.red(` \u2022 ${criticalCount} critical`));
|
|
3345
|
+
if (warningCount > 0) console.log(chalk9.yellow(` \u2022 ${warningCount} warning`));
|
|
3346
|
+
if (infoCount > 0) console.log(chalk9.dim(` \u2022 ${infoCount} info`));
|
|
2911
3347
|
console.log();
|
|
2912
|
-
console.log(
|
|
2913
|
-
console.log(
|
|
3348
|
+
console.log(chalk9.bold("File | Issue | Recommendation"));
|
|
3349
|
+
console.log(chalk9.dim("\u2500".repeat(100)));
|
|
2914
3350
|
const issuesByFile = /* @__PURE__ */ new Map();
|
|
2915
3351
|
for (const issue of issues) {
|
|
2916
3352
|
const existing = issuesByFile.get(issue.file) || [];
|
|
@@ -2920,31 +3356,31 @@ async function seoCheck(options) {
|
|
|
2920
3356
|
for (const [file, fileIssues] of issuesByFile) {
|
|
2921
3357
|
const displayFile = file.length > 30 ? "..." + file.slice(-27) : file.padEnd(30);
|
|
2922
3358
|
for (const issue of fileIssues.slice(0, options.verbose ? 20 : 5)) {
|
|
2923
|
-
const color = issue.severity === "critical" ?
|
|
3359
|
+
const color = issue.severity === "critical" ? chalk9.red : issue.severity === "warning" ? chalk9.yellow : chalk9.dim;
|
|
2924
3360
|
const displayIssue = issue.issue.padEnd(24).slice(0, 24);
|
|
2925
3361
|
const displayRec = issue.recommendation.slice(0, 50);
|
|
2926
3362
|
console.log(color(`${displayFile} | ${displayIssue} | ${displayRec}`));
|
|
2927
3363
|
}
|
|
2928
3364
|
if (!options.verbose && fileIssues.length > 5) {
|
|
2929
|
-
console.log(
|
|
3365
|
+
console.log(chalk9.dim(`${"".padEnd(30)} | ... and ${fileIssues.length - 5} more issues`));
|
|
2930
3366
|
}
|
|
2931
3367
|
}
|
|
2932
|
-
console.log(
|
|
3368
|
+
console.log(chalk9.dim(`
|
|
2933
3369
|
Full report saved to: .archon/seo-report.json`));
|
|
2934
3370
|
}
|
|
2935
3371
|
async function seoFix(options) {
|
|
2936
3372
|
const prompt2 = createPrompt3();
|
|
2937
3373
|
try {
|
|
2938
|
-
console.log(
|
|
2939
|
-
const reportPath =
|
|
2940
|
-
if (!
|
|
2941
|
-
console.log(
|
|
3374
|
+
console.log(chalk9.blue("\n\u{1F527} SEO Auto-Fix\n"));
|
|
3375
|
+
const reportPath = join8(process.cwd(), ".archon/seo-report.json");
|
|
3376
|
+
if (!existsSync9(reportPath)) {
|
|
3377
|
+
console.log(chalk9.yellow("No SEO report found. Running check first...\n"));
|
|
2942
3378
|
await seoCheck({});
|
|
2943
3379
|
}
|
|
2944
|
-
const reportContent = await
|
|
3380
|
+
const reportContent = await readFile7(reportPath, "utf-8");
|
|
2945
3381
|
const report = JSON.parse(reportContent);
|
|
2946
3382
|
if (report.passed || report.issues.length === 0) {
|
|
2947
|
-
console.log(
|
|
3383
|
+
console.log(chalk9.green("No issues to fix!"));
|
|
2948
3384
|
return;
|
|
2949
3385
|
}
|
|
2950
3386
|
const issuesByFile = /* @__PURE__ */ new Map();
|
|
@@ -2955,16 +3391,16 @@ async function seoFix(options) {
|
|
|
2955
3391
|
}
|
|
2956
3392
|
let totalFixes = 0;
|
|
2957
3393
|
for (const [file, fileIssues] of issuesByFile) {
|
|
2958
|
-
const filePath =
|
|
3394
|
+
const filePath = join8(process.cwd(), file);
|
|
2959
3395
|
let content;
|
|
2960
3396
|
try {
|
|
2961
|
-
content = await
|
|
3397
|
+
content = await readFile7(filePath, "utf-8");
|
|
2962
3398
|
} catch {
|
|
2963
3399
|
continue;
|
|
2964
3400
|
}
|
|
2965
3401
|
const insertPoint = findHeadInsertionPoint(content);
|
|
2966
3402
|
if (!insertPoint) {
|
|
2967
|
-
console.log(
|
|
3403
|
+
console.log(chalk9.yellow(` ${file}: No <head> tag found, skipping`));
|
|
2968
3404
|
continue;
|
|
2969
3405
|
}
|
|
2970
3406
|
const tagsToAdd = [];
|
|
@@ -2983,33 +3419,33 @@ async function seoFix(options) {
|
|
|
2983
3419
|
}
|
|
2984
3420
|
if (tagsToAdd.length === 0) continue;
|
|
2985
3421
|
const newContent = content.slice(0, insertPoint.index) + "\n" + tagsToAdd.map((tag) => insertPoint.indent + tag).join("\n") + content.slice(insertPoint.index);
|
|
2986
|
-
console.log(
|
|
3422
|
+
console.log(chalk9.cyan(`
|
|
2987
3423
|
${file}:`));
|
|
2988
3424
|
for (const tag of tagsToAdd) {
|
|
2989
|
-
console.log(
|
|
3425
|
+
console.log(chalk9.green(` + ${tag.slice(0, 70)}${tag.length > 70 ? "..." : ""}`));
|
|
2990
3426
|
}
|
|
2991
3427
|
if (!options.dryRun) {
|
|
2992
|
-
const confirm = await prompt2.ask(
|
|
3428
|
+
const confirm = await prompt2.ask(chalk9.dim(" Apply these changes? (y/N): "));
|
|
2993
3429
|
if (confirm.toLowerCase() === "y") {
|
|
2994
|
-
await
|
|
3430
|
+
await writeFile7(filePath, newContent);
|
|
2995
3431
|
totalFixes += tagsToAdd.length;
|
|
2996
|
-
console.log(
|
|
3432
|
+
console.log(chalk9.green(" \u2713 Applied"));
|
|
2997
3433
|
} else {
|
|
2998
|
-
console.log(
|
|
3434
|
+
console.log(chalk9.dim(" Skipped"));
|
|
2999
3435
|
}
|
|
3000
3436
|
} else {
|
|
3001
3437
|
totalFixes += tagsToAdd.length;
|
|
3002
3438
|
}
|
|
3003
3439
|
}
|
|
3004
3440
|
if (totalFixes === 0) {
|
|
3005
|
-
console.log(
|
|
3441
|
+
console.log(chalk9.dim("\nNo auto-fixable issues found. Some issues require manual configuration."));
|
|
3006
3442
|
} else if (options.dryRun) {
|
|
3007
|
-
console.log(
|
|
3443
|
+
console.log(chalk9.yellow(`
|
|
3008
3444
|
${totalFixes} fixes would be applied. Run without --dry-run to apply.`));
|
|
3009
3445
|
} else {
|
|
3010
|
-
console.log(
|
|
3446
|
+
console.log(chalk9.green(`
|
|
3011
3447
|
\u2705 Applied ${totalFixes} fixes.`));
|
|
3012
|
-
console.log(
|
|
3448
|
+
console.log(chalk9.dim('Run "archon seo check" to verify fixes.'));
|
|
3013
3449
|
}
|
|
3014
3450
|
} finally {
|
|
3015
3451
|
prompt2.close();
|
|
@@ -3018,33 +3454,33 @@ ${totalFixes} fixes would be applied. Run without --dry-run to apply.`));
|
|
|
3018
3454
|
async function seoOpenGraph(options) {
|
|
3019
3455
|
const prompt2 = createPrompt3();
|
|
3020
3456
|
try {
|
|
3021
|
-
console.log(
|
|
3457
|
+
console.log(chalk9.blue("\n\u{1F4F1} Add Open Graph Tags\n"));
|
|
3022
3458
|
let targetFile;
|
|
3023
3459
|
if (options.file) {
|
|
3024
|
-
targetFile = options.file.startsWith("/") ? options.file :
|
|
3460
|
+
targetFile = options.file.startsWith("/") ? options.file : join8(process.cwd(), options.file);
|
|
3025
3461
|
} else {
|
|
3026
3462
|
const files = await getWebFiles();
|
|
3027
3463
|
if (files.length === 0) {
|
|
3028
|
-
console.log(
|
|
3464
|
+
console.log(chalk9.yellow("No web files found."));
|
|
3029
3465
|
return;
|
|
3030
3466
|
}
|
|
3031
|
-
console.log(
|
|
3467
|
+
console.log(chalk9.dim("Available files:"));
|
|
3032
3468
|
files.slice(0, 10).forEach((f, i) => {
|
|
3033
3469
|
console.log(` ${i + 1}) ${f.replace(process.cwd() + "/", "")}`);
|
|
3034
3470
|
});
|
|
3035
3471
|
if (files.length > 10) {
|
|
3036
|
-
console.log(
|
|
3472
|
+
console.log(chalk9.dim(` ... and ${files.length - 10} more`));
|
|
3037
3473
|
}
|
|
3038
3474
|
const fileChoice = await prompt2.ask("\nEnter file path or number: ");
|
|
3039
3475
|
const num = parseInt(fileChoice, 10);
|
|
3040
3476
|
if (num > 0 && num <= files.length) {
|
|
3041
3477
|
targetFile = files[num - 1] ?? "";
|
|
3042
3478
|
} else {
|
|
3043
|
-
targetFile = fileChoice.startsWith("/") ? fileChoice :
|
|
3479
|
+
targetFile = fileChoice.startsWith("/") ? fileChoice : join8(process.cwd(), fileChoice);
|
|
3044
3480
|
}
|
|
3045
3481
|
}
|
|
3046
|
-
if (!
|
|
3047
|
-
console.log(
|
|
3482
|
+
if (!existsSync9(targetFile)) {
|
|
3483
|
+
console.log(chalk9.red(`File not found: ${targetFile}`));
|
|
3048
3484
|
return;
|
|
3049
3485
|
}
|
|
3050
3486
|
const ogTitle = await prompt2.ask("og:title (page title for social): ");
|
|
@@ -3058,22 +3494,22 @@ async function seoOpenGraph(options) {
|
|
|
3058
3494
|
`<meta property="og:image" content="${ogImage}">`,
|
|
3059
3495
|
`<meta property="og:url" content="${ogUrl}">`
|
|
3060
3496
|
];
|
|
3061
|
-
const content = await
|
|
3497
|
+
const content = await readFile7(targetFile, "utf-8");
|
|
3062
3498
|
const insertPoint = findHeadInsertionPoint(content);
|
|
3063
3499
|
if (!insertPoint) {
|
|
3064
|
-
console.log(
|
|
3065
|
-
tags.forEach((tag) => console.log(
|
|
3500
|
+
console.log(chalk9.yellow("No <head> tag found. Add these tags manually:"));
|
|
3501
|
+
tags.forEach((tag) => console.log(chalk9.cyan(` ${tag}`)));
|
|
3066
3502
|
return;
|
|
3067
3503
|
}
|
|
3068
|
-
console.log(
|
|
3069
|
-
tags.forEach((tag) => console.log(
|
|
3504
|
+
console.log(chalk9.dim("\nTags to add:"));
|
|
3505
|
+
tags.forEach((tag) => console.log(chalk9.green(` + ${tag}`)));
|
|
3070
3506
|
const confirm = await prompt2.ask("\nApply changes? (y/N): ");
|
|
3071
3507
|
if (confirm.toLowerCase() === "y") {
|
|
3072
3508
|
const newContent = content.slice(0, insertPoint.index) + "\n" + tags.map((tag) => insertPoint.indent + tag).join("\n") + content.slice(insertPoint.index);
|
|
3073
|
-
await
|
|
3074
|
-
console.log(
|
|
3509
|
+
await writeFile7(targetFile, newContent);
|
|
3510
|
+
console.log(chalk9.green("\n\u2705 Open Graph tags added."));
|
|
3075
3511
|
} else {
|
|
3076
|
-
console.log(
|
|
3512
|
+
console.log(chalk9.dim("Cancelled."));
|
|
3077
3513
|
}
|
|
3078
3514
|
} finally {
|
|
3079
3515
|
prompt2.close();
|
|
@@ -3082,36 +3518,36 @@ async function seoOpenGraph(options) {
|
|
|
3082
3518
|
async function seoTwitter(options) {
|
|
3083
3519
|
const prompt2 = createPrompt3();
|
|
3084
3520
|
try {
|
|
3085
|
-
console.log(
|
|
3521
|
+
console.log(chalk9.blue("\n\u{1F426} Add Twitter Card Tags\n"));
|
|
3086
3522
|
let targetFile;
|
|
3087
3523
|
if (options.file) {
|
|
3088
|
-
targetFile = options.file.startsWith("/") ? options.file :
|
|
3524
|
+
targetFile = options.file.startsWith("/") ? options.file : join8(process.cwd(), options.file);
|
|
3089
3525
|
} else {
|
|
3090
3526
|
const files = await getWebFiles();
|
|
3091
3527
|
if (files.length === 0) {
|
|
3092
|
-
console.log(
|
|
3528
|
+
console.log(chalk9.yellow("No web files found."));
|
|
3093
3529
|
return;
|
|
3094
3530
|
}
|
|
3095
|
-
console.log(
|
|
3531
|
+
console.log(chalk9.dim("Available files:"));
|
|
3096
3532
|
files.slice(0, 10).forEach((f, i) => {
|
|
3097
3533
|
console.log(` ${i + 1}) ${f.replace(process.cwd() + "/", "")}`);
|
|
3098
3534
|
});
|
|
3099
3535
|
if (files.length > 10) {
|
|
3100
|
-
console.log(
|
|
3536
|
+
console.log(chalk9.dim(` ... and ${files.length - 10} more`));
|
|
3101
3537
|
}
|
|
3102
3538
|
const fileChoice = await prompt2.ask("\nEnter file path or number: ");
|
|
3103
3539
|
const num = parseInt(fileChoice, 10);
|
|
3104
3540
|
if (num > 0 && num <= files.length) {
|
|
3105
3541
|
targetFile = files[num - 1] ?? "";
|
|
3106
3542
|
} else {
|
|
3107
|
-
targetFile = fileChoice.startsWith("/") ? fileChoice :
|
|
3543
|
+
targetFile = fileChoice.startsWith("/") ? fileChoice : join8(process.cwd(), fileChoice);
|
|
3108
3544
|
}
|
|
3109
3545
|
}
|
|
3110
|
-
if (!
|
|
3111
|
-
console.log(
|
|
3546
|
+
if (!existsSync9(targetFile)) {
|
|
3547
|
+
console.log(chalk9.red(`File not found: ${targetFile}`));
|
|
3112
3548
|
return;
|
|
3113
3549
|
}
|
|
3114
|
-
console.log(
|
|
3550
|
+
console.log(chalk9.dim("Card types: summary, summary_large_image, app, player"));
|
|
3115
3551
|
const cardType = await prompt2.ask("twitter:card type (default: summary_large_image): ") || "summary_large_image";
|
|
3116
3552
|
const twitterTitle = await prompt2.ask("twitter:title: ");
|
|
3117
3553
|
const twitterDescription = await prompt2.ask("twitter:description: ");
|
|
@@ -3122,22 +3558,22 @@ async function seoTwitter(options) {
|
|
|
3122
3558
|
`<meta name="twitter:description" content="${twitterDescription}">`,
|
|
3123
3559
|
`<meta name="twitter:image" content="${twitterImage}">`
|
|
3124
3560
|
];
|
|
3125
|
-
const content = await
|
|
3561
|
+
const content = await readFile7(targetFile, "utf-8");
|
|
3126
3562
|
const insertPoint = findHeadInsertionPoint(content);
|
|
3127
3563
|
if (!insertPoint) {
|
|
3128
|
-
console.log(
|
|
3129
|
-
tags.forEach((tag) => console.log(
|
|
3564
|
+
console.log(chalk9.yellow("No <head> tag found. Add these tags manually:"));
|
|
3565
|
+
tags.forEach((tag) => console.log(chalk9.cyan(` ${tag}`)));
|
|
3130
3566
|
return;
|
|
3131
3567
|
}
|
|
3132
|
-
console.log(
|
|
3133
|
-
tags.forEach((tag) => console.log(
|
|
3568
|
+
console.log(chalk9.dim("\nTags to add:"));
|
|
3569
|
+
tags.forEach((tag) => console.log(chalk9.green(` + ${tag}`)));
|
|
3134
3570
|
const confirm = await prompt2.ask("\nApply changes? (y/N): ");
|
|
3135
3571
|
if (confirm.toLowerCase() === "y") {
|
|
3136
3572
|
const newContent = content.slice(0, insertPoint.index) + "\n" + tags.map((tag) => insertPoint.indent + tag).join("\n") + content.slice(insertPoint.index);
|
|
3137
|
-
await
|
|
3138
|
-
console.log(
|
|
3573
|
+
await writeFile7(targetFile, newContent);
|
|
3574
|
+
console.log(chalk9.green("\n\u2705 Twitter Card tags added."));
|
|
3139
3575
|
} else {
|
|
3140
|
-
console.log(
|
|
3576
|
+
console.log(chalk9.dim("Cancelled."));
|
|
3141
3577
|
}
|
|
3142
3578
|
} finally {
|
|
3143
3579
|
prompt2.close();
|
|
@@ -3161,7 +3597,7 @@ Examples:
|
|
|
3161
3597
|
await seoCheck(options);
|
|
3162
3598
|
process.exit(0);
|
|
3163
3599
|
} catch (error) {
|
|
3164
|
-
console.error(
|
|
3600
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
3165
3601
|
process.exit(1);
|
|
3166
3602
|
}
|
|
3167
3603
|
});
|
|
@@ -3170,7 +3606,7 @@ Examples:
|
|
|
3170
3606
|
await seoFix(options);
|
|
3171
3607
|
process.exit(0);
|
|
3172
3608
|
} catch (error) {
|
|
3173
|
-
console.error(
|
|
3609
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
3174
3610
|
process.exit(1);
|
|
3175
3611
|
}
|
|
3176
3612
|
});
|
|
@@ -3179,7 +3615,7 @@ Examples:
|
|
|
3179
3615
|
await seoOpenGraph(options);
|
|
3180
3616
|
process.exit(0);
|
|
3181
3617
|
} catch (error) {
|
|
3182
|
-
console.error(
|
|
3618
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
3183
3619
|
process.exit(1);
|
|
3184
3620
|
}
|
|
3185
3621
|
});
|
|
@@ -3188,7 +3624,7 @@ Examples:
|
|
|
3188
3624
|
await seoTwitter(options);
|
|
3189
3625
|
process.exit(0);
|
|
3190
3626
|
} catch (error) {
|
|
3191
|
-
console.error(
|
|
3627
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
3192
3628
|
process.exit(1);
|
|
3193
3629
|
}
|
|
3194
3630
|
});
|
|
@@ -3196,12 +3632,12 @@ Examples:
|
|
|
3196
3632
|
}
|
|
3197
3633
|
|
|
3198
3634
|
// src/cli/session.ts
|
|
3199
|
-
import
|
|
3635
|
+
import chalk10 from "chalk";
|
|
3200
3636
|
import ora2 from "ora";
|
|
3201
3637
|
import os from "os";
|
|
3202
|
-
import { readFile as
|
|
3203
|
-
import { existsSync as
|
|
3204
|
-
import { join as
|
|
3638
|
+
import { readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
|
|
3639
|
+
import { existsSync as existsSync10 } from "fs";
|
|
3640
|
+
import { join as join9 } from "path";
|
|
3205
3641
|
import { createClient as createClient2 } from "@supabase/supabase-js";
|
|
3206
3642
|
function getSupabaseClient2(accessToken) {
|
|
3207
3643
|
return createClient2(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
|
@@ -3212,45 +3648,45 @@ function getDeviceName() {
|
|
|
3212
3648
|
return `${os.hostname()}-${os.platform()}-${os.arch()}`;
|
|
3213
3649
|
}
|
|
3214
3650
|
async function getProjectInfo(cwd) {
|
|
3215
|
-
const archonConfigPath =
|
|
3216
|
-
const packageJsonPath =
|
|
3651
|
+
const archonConfigPath = join9(cwd, ".archon", "config.yaml");
|
|
3652
|
+
const packageJsonPath = join9(cwd, "package.json");
|
|
3217
3653
|
let projectName = "Unknown Project";
|
|
3218
|
-
if (
|
|
3654
|
+
if (existsSync10(packageJsonPath)) {
|
|
3219
3655
|
try {
|
|
3220
|
-
const pkg = JSON.parse(await
|
|
3656
|
+
const pkg = JSON.parse(await readFile8(packageJsonPath, "utf-8"));
|
|
3221
3657
|
projectName = pkg.name || projectName;
|
|
3222
3658
|
} catch {
|
|
3223
3659
|
}
|
|
3224
3660
|
}
|
|
3225
|
-
if (!
|
|
3661
|
+
if (!existsSync10(archonConfigPath)) {
|
|
3226
3662
|
return null;
|
|
3227
3663
|
}
|
|
3228
3664
|
return { name: projectName, path: cwd };
|
|
3229
3665
|
}
|
|
3230
3666
|
async function getCurrentAtomId(cwd) {
|
|
3231
|
-
const stateFile =
|
|
3232
|
-
if (!
|
|
3667
|
+
const stateFile = join9(cwd, ".archon", "state.json");
|
|
3668
|
+
if (!existsSync10(stateFile)) return null;
|
|
3233
3669
|
try {
|
|
3234
|
-
const state = JSON.parse(await
|
|
3670
|
+
const state = JSON.parse(await readFile8(stateFile, "utf-8"));
|
|
3235
3671
|
return state.currentAtomId || null;
|
|
3236
3672
|
} catch {
|
|
3237
3673
|
return null;
|
|
3238
3674
|
}
|
|
3239
3675
|
}
|
|
3240
3676
|
async function getPendingAtoms(cwd) {
|
|
3241
|
-
const stateFile =
|
|
3242
|
-
if (!
|
|
3677
|
+
const stateFile = join9(cwd, ".archon", "state.json");
|
|
3678
|
+
if (!existsSync10(stateFile)) return [];
|
|
3243
3679
|
try {
|
|
3244
|
-
const state = JSON.parse(await
|
|
3680
|
+
const state = JSON.parse(await readFile8(stateFile, "utf-8"));
|
|
3245
3681
|
return state.pendingAtoms || [];
|
|
3246
3682
|
} catch {
|
|
3247
3683
|
return [];
|
|
3248
3684
|
}
|
|
3249
3685
|
}
|
|
3250
3686
|
async function getFileContent(path2) {
|
|
3251
|
-
if (!
|
|
3687
|
+
if (!existsSync10(path2)) return null;
|
|
3252
3688
|
try {
|
|
3253
|
-
return await
|
|
3689
|
+
return await readFile8(path2, "utf-8");
|
|
3254
3690
|
} catch {
|
|
3255
3691
|
return null;
|
|
3256
3692
|
}
|
|
@@ -3277,8 +3713,8 @@ async function saveSession(name) {
|
|
|
3277
3713
|
}
|
|
3278
3714
|
const currentAtomId = await getCurrentAtomId(cwd);
|
|
3279
3715
|
const pendingAtoms = await getPendingAtoms(cwd);
|
|
3280
|
-
const progressSnapshot = await getFileContent(
|
|
3281
|
-
const architectureSnapshot = await getFileContent(
|
|
3716
|
+
const progressSnapshot = await getFileContent(join9(cwd, "progress.txt"));
|
|
3717
|
+
const architectureSnapshot = await getFileContent(join9(cwd, "ARCHITECTURE.md"));
|
|
3282
3718
|
const sessionData = {
|
|
3283
3719
|
user_id: profile.id,
|
|
3284
3720
|
project_name: name || projectInfo.name,
|
|
@@ -3295,13 +3731,13 @@ async function saveSession(name) {
|
|
|
3295
3731
|
spinner.fail(`Failed to save session: ${error.message}`);
|
|
3296
3732
|
return;
|
|
3297
3733
|
}
|
|
3298
|
-
spinner.succeed(
|
|
3734
|
+
spinner.succeed(chalk10.green("Session saved!"));
|
|
3299
3735
|
console.log();
|
|
3300
|
-
console.log(` ID: ${
|
|
3736
|
+
console.log(` ID: ${chalk10.cyan(session.id)}`);
|
|
3301
3737
|
console.log(` Project: ${session.project_name}`);
|
|
3302
3738
|
console.log(` Device: ${session.last_device}`);
|
|
3303
3739
|
console.log();
|
|
3304
|
-
console.log(
|
|
3740
|
+
console.log(chalk10.dim(" Resume on another device: archon session resume " + session.id));
|
|
3305
3741
|
console.log();
|
|
3306
3742
|
} catch (err) {
|
|
3307
3743
|
spinner.fail("Error saving session");
|
|
@@ -3329,23 +3765,23 @@ async function listSessions() {
|
|
|
3329
3765
|
}
|
|
3330
3766
|
spinner.stop();
|
|
3331
3767
|
if (!sessions || sessions.length === 0) {
|
|
3332
|
-
console.log(
|
|
3333
|
-
console.log(
|
|
3768
|
+
console.log(chalk10.yellow("\nNo saved sessions found.\n"));
|
|
3769
|
+
console.log(chalk10.dim(" Save a session: archon session save [name]\n"));
|
|
3334
3770
|
return;
|
|
3335
3771
|
}
|
|
3336
3772
|
console.log();
|
|
3337
|
-
console.log(
|
|
3773
|
+
console.log(chalk10.bold("\u{1F4C2} Saved Sessions"));
|
|
3338
3774
|
console.log();
|
|
3339
3775
|
for (const session of sessions) {
|
|
3340
3776
|
const date = new Date(session.updated_at).toLocaleDateString();
|
|
3341
|
-
const atomInfo = session.current_atom_id ?
|
|
3777
|
+
const atomInfo = session.current_atom_id ? chalk10.dim(` (atom: ${session.current_atom_id})`) : "";
|
|
3342
3778
|
console.log(
|
|
3343
|
-
` ${
|
|
3779
|
+
` ${chalk10.cyan(session.id.slice(0, 8))} ${session.project_name}${atomInfo}`
|
|
3344
3780
|
);
|
|
3345
|
-
console.log(
|
|
3781
|
+
console.log(chalk10.dim(` ${date} from ${session.last_device || "unknown device"}`));
|
|
3346
3782
|
console.log();
|
|
3347
3783
|
}
|
|
3348
|
-
console.log(
|
|
3784
|
+
console.log(chalk10.dim(" Resume: archon session resume <id>\n"));
|
|
3349
3785
|
} catch (err) {
|
|
3350
3786
|
spinner.fail("Error fetching sessions");
|
|
3351
3787
|
console.error(err);
|
|
@@ -3372,30 +3808,30 @@ async function resumeSession(sessionId) {
|
|
|
3372
3808
|
return;
|
|
3373
3809
|
}
|
|
3374
3810
|
const session = sessions[0];
|
|
3375
|
-
const stateFile =
|
|
3811
|
+
const stateFile = join9(cwd, ".archon", "state.json");
|
|
3376
3812
|
const state = {
|
|
3377
3813
|
currentAtomId: session.current_atom_id,
|
|
3378
3814
|
pendingAtoms: session.pending_atoms,
|
|
3379
3815
|
resumedFrom: session.id,
|
|
3380
3816
|
resumedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3381
3817
|
};
|
|
3382
|
-
await
|
|
3818
|
+
await writeFile8(stateFile, JSON.stringify(state, null, 2));
|
|
3383
3819
|
if (session.progress_snapshot) {
|
|
3384
|
-
const progressPath =
|
|
3385
|
-
await
|
|
3820
|
+
const progressPath = join9(cwd, "progress.txt");
|
|
3821
|
+
await writeFile8(progressPath, session.progress_snapshot);
|
|
3386
3822
|
}
|
|
3387
3823
|
if (session.architecture_snapshot) {
|
|
3388
|
-
const archPath =
|
|
3389
|
-
await
|
|
3824
|
+
const archPath = join9(cwd, "ARCHITECTURE.md");
|
|
3825
|
+
await writeFile8(archPath, session.architecture_snapshot);
|
|
3390
3826
|
}
|
|
3391
3827
|
await supabase.from("sessions").update({ last_device: getDeviceName(), updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", session.id);
|
|
3392
|
-
spinner.succeed(
|
|
3828
|
+
spinner.succeed(chalk10.green("Session resumed!"));
|
|
3393
3829
|
console.log();
|
|
3394
3830
|
console.log(` Project: ${session.project_name}`);
|
|
3395
3831
|
console.log(` Current Atom: ${session.current_atom_id || "none"}`);
|
|
3396
3832
|
console.log(` Pending: ${session.pending_atoms.length} atoms`);
|
|
3397
3833
|
console.log();
|
|
3398
|
-
console.log(
|
|
3834
|
+
console.log(chalk10.dim(" Continue working: archon start"));
|
|
3399
3835
|
console.log();
|
|
3400
3836
|
} catch (err) {
|
|
3401
3837
|
spinner.fail("Error resuming session");
|
|
@@ -3411,12 +3847,12 @@ async function syncSession() {
|
|
|
3411
3847
|
spinner.fail("Not logged in. Run: archon login");
|
|
3412
3848
|
return;
|
|
3413
3849
|
}
|
|
3414
|
-
const stateFile =
|
|
3415
|
-
if (!
|
|
3850
|
+
const stateFile = join9(cwd, ".archon", "state.json");
|
|
3851
|
+
if (!existsSync10(stateFile)) {
|
|
3416
3852
|
spinner.info("No active session to sync");
|
|
3417
3853
|
return;
|
|
3418
3854
|
}
|
|
3419
|
-
const state = JSON.parse(await
|
|
3855
|
+
const state = JSON.parse(await readFile8(stateFile, "utf-8"));
|
|
3420
3856
|
if (!state.resumedFrom) {
|
|
3421
3857
|
spinner.info('Session was not resumed from cloud - use "archon session save" to create one');
|
|
3422
3858
|
return;
|
|
@@ -3424,8 +3860,8 @@ async function syncSession() {
|
|
|
3424
3860
|
const supabase = getSupabaseClient2(config.accessToken);
|
|
3425
3861
|
const currentAtomId = await getCurrentAtomId(cwd);
|
|
3426
3862
|
const pendingAtoms = await getPendingAtoms(cwd);
|
|
3427
|
-
const progressSnapshot = await getFileContent(
|
|
3428
|
-
const architectureSnapshot = await getFileContent(
|
|
3863
|
+
const progressSnapshot = await getFileContent(join9(cwd, "progress.txt"));
|
|
3864
|
+
const architectureSnapshot = await getFileContent(join9(cwd, "ARCHITECTURE.md"));
|
|
3429
3865
|
const { error } = await supabase.from("sessions").update({
|
|
3430
3866
|
current_atom_id: currentAtomId,
|
|
3431
3867
|
pending_atoms: pendingAtoms,
|
|
@@ -3438,7 +3874,7 @@ async function syncSession() {
|
|
|
3438
3874
|
spinner.fail(`Failed to sync: ${error.message}`);
|
|
3439
3875
|
return;
|
|
3440
3876
|
}
|
|
3441
|
-
spinner.succeed(
|
|
3877
|
+
spinner.succeed(chalk10.green("Session synced to cloud"));
|
|
3442
3878
|
} catch (err) {
|
|
3443
3879
|
spinner.fail("Error syncing session");
|
|
3444
3880
|
console.error(err);
|
|
@@ -3446,66 +3882,66 @@ async function syncSession() {
|
|
|
3446
3882
|
}
|
|
3447
3883
|
|
|
3448
3884
|
// src/cli/deploy.ts
|
|
3449
|
-
import
|
|
3450
|
-
import { existsSync as
|
|
3451
|
-
import { join as
|
|
3452
|
-
import { execSync } from "child_process";
|
|
3885
|
+
import chalk11 from "chalk";
|
|
3886
|
+
import { existsSync as existsSync11 } from "fs";
|
|
3887
|
+
import { join as join10 } from "path";
|
|
3888
|
+
import { execSync as execSync2 } from "child_process";
|
|
3453
3889
|
function detectPlatform(cwd) {
|
|
3454
|
-
if (
|
|
3455
|
-
if (
|
|
3456
|
-
if (
|
|
3457
|
-
if (
|
|
3458
|
-
if (
|
|
3459
|
-
if (
|
|
3890
|
+
if (existsSync11(join10(cwd, "fly.toml"))) return "fly";
|
|
3891
|
+
if (existsSync11(join10(cwd, "vercel.json"))) return "vercel";
|
|
3892
|
+
if (existsSync11(join10(cwd, "netlify.toml"))) return "netlify";
|
|
3893
|
+
if (existsSync11(join10(cwd, "railway.json"))) return "railway";
|
|
3894
|
+
if (existsSync11(join10(cwd, "render.yaml"))) return "render";
|
|
3895
|
+
if (existsSync11(join10(cwd, "Dockerfile"))) return "fly";
|
|
3460
3896
|
return "unknown";
|
|
3461
3897
|
}
|
|
3462
3898
|
async function deploy(options) {
|
|
3463
3899
|
const cwd = process.cwd();
|
|
3464
|
-
console.log(
|
|
3900
|
+
console.log(chalk11.blue("Running pre-deploy checks..."));
|
|
3465
3901
|
const platform = options.platform ?? detectPlatform(cwd);
|
|
3466
|
-
console.log(
|
|
3902
|
+
console.log(chalk11.dim(`Detected platform: ${platform}`));
|
|
3467
3903
|
if (options.dryRun) {
|
|
3468
|
-
console.log(
|
|
3904
|
+
console.log(chalk11.dim("Dry run mode - would deploy to:"), platform);
|
|
3469
3905
|
return;
|
|
3470
3906
|
}
|
|
3471
3907
|
switch (platform) {
|
|
3472
3908
|
case "fly":
|
|
3473
|
-
console.log(
|
|
3474
|
-
|
|
3909
|
+
console.log(chalk11.blue("Deploying to Fly.io..."));
|
|
3910
|
+
execSync2("fly deploy", { cwd, stdio: "inherit" });
|
|
3475
3911
|
break;
|
|
3476
3912
|
case "vercel": {
|
|
3477
|
-
console.log(
|
|
3913
|
+
console.log(chalk11.blue("Deploying to Vercel..."));
|
|
3478
3914
|
const cmd = options.preview ? "vercel" : "vercel --prod";
|
|
3479
|
-
|
|
3915
|
+
execSync2(cmd, { cwd, stdio: "inherit" });
|
|
3480
3916
|
break;
|
|
3481
3917
|
}
|
|
3482
3918
|
case "netlify": {
|
|
3483
|
-
console.log(
|
|
3919
|
+
console.log(chalk11.blue("Deploying to Netlify..."));
|
|
3484
3920
|
const netlifyCmd = options.preview ? "netlify deploy" : "netlify deploy --prod";
|
|
3485
|
-
|
|
3921
|
+
execSync2(netlifyCmd, { cwd, stdio: "inherit" });
|
|
3486
3922
|
break;
|
|
3487
3923
|
}
|
|
3488
3924
|
case "railway":
|
|
3489
|
-
console.log(
|
|
3490
|
-
|
|
3925
|
+
console.log(chalk11.blue("Deploying to Railway..."));
|
|
3926
|
+
execSync2("railway up", { cwd, stdio: "inherit" });
|
|
3491
3927
|
break;
|
|
3492
3928
|
case "render":
|
|
3493
|
-
console.log(
|
|
3494
|
-
console.log(
|
|
3929
|
+
console.log(chalk11.blue("Deploying to Render..."));
|
|
3930
|
+
console.log(chalk11.yellow("Render deploys via git push. Push to your connected branch."));
|
|
3495
3931
|
break;
|
|
3496
3932
|
default:
|
|
3497
|
-
console.log(
|
|
3498
|
-
console.log(
|
|
3933
|
+
console.log(chalk11.yellow("Platform not detected. Please specify with --platform"));
|
|
3934
|
+
console.log(chalk11.dim("Supported: fly, vercel, netlify, railway, render"));
|
|
3499
3935
|
}
|
|
3500
3936
|
}
|
|
3501
3937
|
|
|
3502
3938
|
// src/cli/index-cmd.ts
|
|
3503
|
-
import
|
|
3939
|
+
import chalk12 from "chalk";
|
|
3504
3940
|
|
|
3505
3941
|
// src/core/indexing/local.ts
|
|
3506
|
-
import { existsSync as
|
|
3507
|
-
import { readFile as
|
|
3508
|
-
import { join as
|
|
3942
|
+
import { existsSync as existsSync12, mkdirSync } from "fs";
|
|
3943
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
3944
|
+
import { join as join11, extname } from "path";
|
|
3509
3945
|
import Database from "better-sqlite3";
|
|
3510
3946
|
var CHUNK_SIZE = 1e3;
|
|
3511
3947
|
var CHUNK_OVERLAP = 200;
|
|
@@ -3552,11 +3988,11 @@ var LocalIndexer = class {
|
|
|
3552
3988
|
};
|
|
3553
3989
|
}
|
|
3554
3990
|
async init(cwd) {
|
|
3555
|
-
const archonDir =
|
|
3556
|
-
if (!
|
|
3991
|
+
const archonDir = join11(cwd, ".archon");
|
|
3992
|
+
if (!existsSync12(archonDir)) {
|
|
3557
3993
|
mkdirSync(archonDir, { recursive: true });
|
|
3558
3994
|
}
|
|
3559
|
-
this.db = new Database(
|
|
3995
|
+
this.db = new Database(join11(cwd, this.config.dbPath));
|
|
3560
3996
|
this.db.exec(`
|
|
3561
3997
|
CREATE TABLE IF NOT EXISTS embeddings (
|
|
3562
3998
|
id INTEGER PRIMARY KEY,
|
|
@@ -3606,11 +4042,11 @@ var LocalIndexer = class {
|
|
|
3606
4042
|
if (!this.isIndexableFile(filePath)) {
|
|
3607
4043
|
return 0;
|
|
3608
4044
|
}
|
|
3609
|
-
const fullPath =
|
|
3610
|
-
if (!
|
|
4045
|
+
const fullPath = join11(cwd, filePath);
|
|
4046
|
+
if (!existsSync12(fullPath)) {
|
|
3611
4047
|
return 0;
|
|
3612
4048
|
}
|
|
3613
|
-
const content = await
|
|
4049
|
+
const content = await readFile9(fullPath, "utf-8");
|
|
3614
4050
|
const chunks = this.chunkText(content);
|
|
3615
4051
|
const deleteStmt = this.db.prepare("DELETE FROM embeddings WHERE file_path = ?");
|
|
3616
4052
|
deleteStmt.run(filePath);
|
|
@@ -3686,16 +4122,287 @@ var LocalIndexer = class {
|
|
|
3686
4122
|
}
|
|
3687
4123
|
};
|
|
3688
4124
|
|
|
4125
|
+
// src/core/indexing/cloud.ts
|
|
4126
|
+
import { createClient as createClient3 } from "@supabase/supabase-js";
|
|
4127
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
4128
|
+
import { existsSync as existsSync13 } from "fs";
|
|
4129
|
+
import { join as join12, extname as extname2 } from "path";
|
|
4130
|
+
import { createHash } from "crypto";
|
|
4131
|
+
var CHUNK_SIZE2 = 1e3;
|
|
4132
|
+
var CHUNK_OVERLAP2 = 200;
|
|
4133
|
+
var INDEXABLE_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
4134
|
+
".ts",
|
|
4135
|
+
".tsx",
|
|
4136
|
+
".js",
|
|
4137
|
+
".jsx",
|
|
4138
|
+
".py",
|
|
4139
|
+
".rb",
|
|
4140
|
+
".go",
|
|
4141
|
+
".rs",
|
|
4142
|
+
".java",
|
|
4143
|
+
".c",
|
|
4144
|
+
".cpp",
|
|
4145
|
+
".h",
|
|
4146
|
+
".hpp",
|
|
4147
|
+
".cs",
|
|
4148
|
+
".swift",
|
|
4149
|
+
".kt",
|
|
4150
|
+
".scala",
|
|
4151
|
+
".md",
|
|
4152
|
+
".mdx",
|
|
4153
|
+
".txt",
|
|
4154
|
+
".json",
|
|
4155
|
+
".yaml",
|
|
4156
|
+
".yml",
|
|
4157
|
+
".toml",
|
|
4158
|
+
".html",
|
|
4159
|
+
".css",
|
|
4160
|
+
".scss",
|
|
4161
|
+
".less",
|
|
4162
|
+
".vue",
|
|
4163
|
+
".svelte"
|
|
4164
|
+
]);
|
|
4165
|
+
var CloudIndexer = class {
|
|
4166
|
+
config;
|
|
4167
|
+
client;
|
|
4168
|
+
userId = null;
|
|
4169
|
+
constructor(config) {
|
|
4170
|
+
this.config = config;
|
|
4171
|
+
this.client = createClient3(config.supabaseUrl, config.supabaseKey);
|
|
4172
|
+
}
|
|
4173
|
+
/**
|
|
4174
|
+
* Set the authenticated user ID
|
|
4175
|
+
*/
|
|
4176
|
+
setUserId(userId) {
|
|
4177
|
+
this.userId = userId;
|
|
4178
|
+
}
|
|
4179
|
+
/**
|
|
4180
|
+
* Generate embedding using OpenAI API
|
|
4181
|
+
*/
|
|
4182
|
+
async embedText(text) {
|
|
4183
|
+
if (this.config.embeddingProvider === "openai") {
|
|
4184
|
+
return this.embedWithOpenAI(text);
|
|
4185
|
+
}
|
|
4186
|
+
throw new Error(`Unsupported embedding provider: ${this.config.embeddingProvider}`);
|
|
4187
|
+
}
|
|
4188
|
+
async embedWithOpenAI(text) {
|
|
4189
|
+
const response = await fetch("https://api.openai.com/v1/embeddings", {
|
|
4190
|
+
method: "POST",
|
|
4191
|
+
headers: {
|
|
4192
|
+
"Authorization": `Bearer ${this.config.embeddingApiKey}`,
|
|
4193
|
+
"Content-Type": "application/json"
|
|
4194
|
+
},
|
|
4195
|
+
body: JSON.stringify({
|
|
4196
|
+
model: "text-embedding-ada-002",
|
|
4197
|
+
input: text.slice(0, 8e3)
|
|
4198
|
+
// Limit input size
|
|
4199
|
+
})
|
|
4200
|
+
});
|
|
4201
|
+
if (!response.ok) {
|
|
4202
|
+
const error = await response.json();
|
|
4203
|
+
throw new Error(`OpenAI embedding failed: ${error.error?.message ?? response.statusText}`);
|
|
4204
|
+
}
|
|
4205
|
+
const data = await response.json();
|
|
4206
|
+
const embedding = data.data[0]?.embedding;
|
|
4207
|
+
if (!embedding) {
|
|
4208
|
+
throw new Error("No embedding returned from OpenAI");
|
|
4209
|
+
}
|
|
4210
|
+
return embedding;
|
|
4211
|
+
}
|
|
4212
|
+
/**
|
|
4213
|
+
* Chunk text into smaller pieces for embedding
|
|
4214
|
+
*/
|
|
4215
|
+
chunkText(text, filePath) {
|
|
4216
|
+
const chunks = [];
|
|
4217
|
+
const header = `File: ${filePath}
|
|
4218
|
+
|
|
4219
|
+
`;
|
|
4220
|
+
let start2 = 0;
|
|
4221
|
+
let index = 0;
|
|
4222
|
+
while (start2 < text.length) {
|
|
4223
|
+
const end = Math.min(start2 + CHUNK_SIZE2, text.length);
|
|
4224
|
+
const chunkText = index === 0 ? header + text.slice(start2, end) : text.slice(start2, end);
|
|
4225
|
+
if (chunkText.trim().length > 0) {
|
|
4226
|
+
chunks.push({ text: chunkText, index });
|
|
4227
|
+
index++;
|
|
4228
|
+
}
|
|
4229
|
+
start2 += CHUNK_SIZE2 - CHUNK_OVERLAP2;
|
|
4230
|
+
}
|
|
4231
|
+
return chunks;
|
|
4232
|
+
}
|
|
4233
|
+
/**
|
|
4234
|
+
* Check if file should be indexed
|
|
4235
|
+
*/
|
|
4236
|
+
isIndexableFile(filePath) {
|
|
4237
|
+
const ext = extname2(filePath).toLowerCase();
|
|
4238
|
+
return INDEXABLE_EXTENSIONS2.has(ext);
|
|
4239
|
+
}
|
|
4240
|
+
/**
|
|
4241
|
+
* Compute file hash for change detection
|
|
4242
|
+
*/
|
|
4243
|
+
async computeFileHash(content) {
|
|
4244
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
4245
|
+
}
|
|
4246
|
+
/**
|
|
4247
|
+
* Index a single file
|
|
4248
|
+
*/
|
|
4249
|
+
async indexFile(cwd, filePath) {
|
|
4250
|
+
if (!this.userId) {
|
|
4251
|
+
throw new Error("User ID not set. Call setUserId() first.");
|
|
4252
|
+
}
|
|
4253
|
+
if (!this.isIndexableFile(filePath)) {
|
|
4254
|
+
return 0;
|
|
4255
|
+
}
|
|
4256
|
+
const fullPath = join12(cwd, filePath);
|
|
4257
|
+
if (!existsSync13(fullPath)) {
|
|
4258
|
+
return 0;
|
|
4259
|
+
}
|
|
4260
|
+
const content = await readFile10(fullPath, "utf-8");
|
|
4261
|
+
const fileHash = await this.computeFileHash(content);
|
|
4262
|
+
const { data: existing } = await this.client.from("code_embeddings").select("file_hash").eq("user_id", this.userId).eq("project_id", this.config.projectId).eq("file_path", filePath).eq("chunk_index", 0).single();
|
|
4263
|
+
if (existing && existing.file_hash === fileHash) {
|
|
4264
|
+
return 0;
|
|
4265
|
+
}
|
|
4266
|
+
await this.client.from("code_embeddings").delete().eq("user_id", this.userId).eq("project_id", this.config.projectId).eq("file_path", filePath);
|
|
4267
|
+
const chunks = this.chunkText(content, filePath);
|
|
4268
|
+
for (const chunk of chunks) {
|
|
4269
|
+
const embedding = await this.embedText(chunk.text);
|
|
4270
|
+
const embeddingStr = `[${embedding.join(",")}]`;
|
|
4271
|
+
await this.client.from("code_embeddings").insert({
|
|
4272
|
+
user_id: this.userId,
|
|
4273
|
+
project_id: this.config.projectId,
|
|
4274
|
+
file_path: filePath,
|
|
4275
|
+
chunk_index: chunk.index,
|
|
4276
|
+
chunk_text: chunk.text,
|
|
4277
|
+
embedding: embeddingStr,
|
|
4278
|
+
file_hash: fileHash
|
|
4279
|
+
});
|
|
4280
|
+
}
|
|
4281
|
+
return chunks.length;
|
|
4282
|
+
}
|
|
4283
|
+
/**
|
|
4284
|
+
* Search for similar code
|
|
4285
|
+
*/
|
|
4286
|
+
async search(query, limit = 10) {
|
|
4287
|
+
if (!this.userId) {
|
|
4288
|
+
throw new Error("User ID not set. Call setUserId() first.");
|
|
4289
|
+
}
|
|
4290
|
+
const queryEmbedding = await this.embedText(query);
|
|
4291
|
+
const embeddingStr = `[${queryEmbedding.join(",")}]`;
|
|
4292
|
+
const { data, error } = await this.client.rpc("search_code_embeddings", {
|
|
4293
|
+
p_user_id: this.userId,
|
|
4294
|
+
p_project_id: this.config.projectId,
|
|
4295
|
+
p_query_embedding: embeddingStr,
|
|
4296
|
+
p_limit: limit
|
|
4297
|
+
});
|
|
4298
|
+
if (error) {
|
|
4299
|
+
throw new Error(`Search failed: ${error.message}`);
|
|
4300
|
+
}
|
|
4301
|
+
return (data ?? []).map((row) => ({
|
|
4302
|
+
file: row.file_path,
|
|
4303
|
+
text: row.chunk_text,
|
|
4304
|
+
score: row.similarity
|
|
4305
|
+
}));
|
|
4306
|
+
}
|
|
4307
|
+
/**
|
|
4308
|
+
* Get indexing status for project
|
|
4309
|
+
*/
|
|
4310
|
+
async getStatus() {
|
|
4311
|
+
if (!this.userId) {
|
|
4312
|
+
throw new Error("User ID not set. Call setUserId() first.");
|
|
4313
|
+
}
|
|
4314
|
+
const { data: fileCount } = await this.client.from("code_embeddings").select("file_path", { count: "exact", head: true }).eq("user_id", this.userId).eq("project_id", this.config.projectId);
|
|
4315
|
+
const { data: chunkCount } = await this.client.from("code_embeddings").select("id", { count: "exact", head: true }).eq("user_id", this.userId).eq("project_id", this.config.projectId);
|
|
4316
|
+
const { data: lastUpdated } = await this.client.from("code_embeddings").select("updated_at").eq("user_id", this.userId).eq("project_id", this.config.projectId).order("updated_at", { ascending: false }).limit(1).single();
|
|
4317
|
+
const { data: job } = await this.client.from("indexing_jobs").select("status").eq("user_id", this.userId).eq("project_id", this.config.projectId).order("created_at", { ascending: false }).limit(1).single();
|
|
4318
|
+
return {
|
|
4319
|
+
projectId: this.config.projectId,
|
|
4320
|
+
fileCount: fileCount?.count ?? 0,
|
|
4321
|
+
chunkCount: chunkCount?.count ?? 0,
|
|
4322
|
+
lastUpdated: lastUpdated?.updated_at ?? null,
|
|
4323
|
+
jobStatus: job?.status
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
4326
|
+
/**
|
|
4327
|
+
* Remove all embeddings for this project
|
|
4328
|
+
*/
|
|
4329
|
+
async clearProject() {
|
|
4330
|
+
if (!this.userId) {
|
|
4331
|
+
throw new Error("User ID not set. Call setUserId() first.");
|
|
4332
|
+
}
|
|
4333
|
+
await this.client.from("code_embeddings").delete().eq("user_id", this.userId).eq("project_id", this.config.projectId);
|
|
4334
|
+
}
|
|
4335
|
+
/**
|
|
4336
|
+
* Remove a single file from the index
|
|
4337
|
+
*/
|
|
4338
|
+
async removeFile(filePath) {
|
|
4339
|
+
if (!this.userId) {
|
|
4340
|
+
throw new Error("User ID not set. Call setUserId() first.");
|
|
4341
|
+
}
|
|
4342
|
+
await this.client.from("code_embeddings").delete().eq("user_id", this.userId).eq("project_id", this.config.projectId).eq("file_path", filePath);
|
|
4343
|
+
}
|
|
4344
|
+
};
|
|
4345
|
+
|
|
3689
4346
|
// src/cli/index-cmd.ts
|
|
3690
4347
|
import { glob as glob4 } from "glob";
|
|
3691
|
-
import { join as
|
|
4348
|
+
import { join as join13, basename } from "path";
|
|
4349
|
+
async function getCloudIndexer(cwd) {
|
|
4350
|
+
const config = await loadConfig();
|
|
4351
|
+
const authToken = getAuthToken(config);
|
|
4352
|
+
if (!authToken) {
|
|
4353
|
+
console.error(chalk12.red('Not authenticated. Run "archon login" first.'));
|
|
4354
|
+
return null;
|
|
4355
|
+
}
|
|
4356
|
+
const openaiKey = process.env["OPENAI_API_KEY"];
|
|
4357
|
+
if (!openaiKey) {
|
|
4358
|
+
console.error(chalk12.red("OPENAI_API_KEY environment variable not set."));
|
|
4359
|
+
console.log(chalk12.dim("Cloud indexing requires an OpenAI API key for embeddings."));
|
|
4360
|
+
console.log(chalk12.dim("Set it with: export OPENAI_API_KEY=sk-..."));
|
|
4361
|
+
return null;
|
|
4362
|
+
}
|
|
4363
|
+
const projectId = basename(cwd);
|
|
4364
|
+
const indexer = new CloudIndexer({
|
|
4365
|
+
supabaseUrl: SUPABASE_URL,
|
|
4366
|
+
supabaseKey: SUPABASE_ANON_KEY,
|
|
4367
|
+
embeddingProvider: "openai",
|
|
4368
|
+
embeddingApiKey: openaiKey,
|
|
4369
|
+
projectId
|
|
4370
|
+
});
|
|
4371
|
+
const { createClient: createClient4 } = await import("@supabase/supabase-js");
|
|
4372
|
+
const client = createClient4(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
|
4373
|
+
global: { headers: { Authorization: `Bearer ${authToken}` } }
|
|
4374
|
+
});
|
|
4375
|
+
const { data: { user } } = await client.auth.getUser();
|
|
4376
|
+
if (!user) {
|
|
4377
|
+
console.error(chalk12.red("Failed to get user. Try logging in again."));
|
|
4378
|
+
return null;
|
|
4379
|
+
}
|
|
4380
|
+
const { data: profile } = await client.from("user_profiles").select("id").eq("auth_id", user.id).single();
|
|
4381
|
+
if (!profile) {
|
|
4382
|
+
console.error(chalk12.red("User profile not found."));
|
|
4383
|
+
return null;
|
|
4384
|
+
}
|
|
4385
|
+
indexer.setUserId(profile.id);
|
|
4386
|
+
return indexer;
|
|
4387
|
+
}
|
|
3692
4388
|
async function indexInit(options) {
|
|
3693
4389
|
const cwd = process.cwd();
|
|
3694
4390
|
if (options.cloud) {
|
|
3695
|
-
console.log(
|
|
4391
|
+
console.log(chalk12.blue("Initializing cloud semantic index..."));
|
|
4392
|
+
const indexer = await getCloudIndexer(cwd);
|
|
4393
|
+
if (!indexer) return;
|
|
4394
|
+
try {
|
|
4395
|
+
const status2 = await indexer.getStatus();
|
|
4396
|
+
console.log(chalk12.green("\u2713 Cloud indexing configured"));
|
|
4397
|
+
console.log(chalk12.green(`\u2713 Project ID: ${status2.projectId}`));
|
|
4398
|
+
console.log(chalk12.dim("\nRun `archon index update --cloud` to index your codebase."));
|
|
4399
|
+
} catch (error) {
|
|
4400
|
+
console.error(chalk12.red(`Failed to initialize cloud index: ${error instanceof Error ? error.message : String(error)}`));
|
|
4401
|
+
process.exit(1);
|
|
4402
|
+
}
|
|
3696
4403
|
return;
|
|
3697
4404
|
}
|
|
3698
|
-
console.log(
|
|
4405
|
+
console.log(chalk12.blue("Initializing local semantic index..."));
|
|
3699
4406
|
try {
|
|
3700
4407
|
const indexer = new LocalIndexer();
|
|
3701
4408
|
await indexer.init(cwd);
|
|
@@ -3703,24 +4410,67 @@ async function indexInit(options) {
|
|
|
3703
4410
|
if (!response.ok) {
|
|
3704
4411
|
throw new Error("Ollama not responding");
|
|
3705
4412
|
}
|
|
3706
|
-
console.log(
|
|
3707
|
-
console.log(
|
|
3708
|
-
console.log(
|
|
4413
|
+
console.log(chalk12.green("\u2713 Ollama connection verified"));
|
|
4414
|
+
console.log(chalk12.green("\u2713 Index database created at .archon/index.db"));
|
|
4415
|
+
console.log(chalk12.dim("\nRun `archon index update` to index your codebase."));
|
|
3709
4416
|
indexer.close();
|
|
3710
4417
|
} catch (error) {
|
|
3711
4418
|
if (error instanceof Error && error.message.includes("Ollama")) {
|
|
3712
|
-
console.log(
|
|
3713
|
-
console.log(
|
|
3714
|
-
console.log(
|
|
4419
|
+
console.log(chalk12.red("\n\u2717 Ollama is not running"));
|
|
4420
|
+
console.log(chalk12.dim("Start Ollama with: ollama serve"));
|
|
4421
|
+
console.log(chalk12.dim("Then pull the embedding model: ollama pull nomic-embed-text"));
|
|
4422
|
+
console.log(chalk12.dim("\nOr use cloud indexing: archon index init --cloud"));
|
|
3715
4423
|
} else {
|
|
3716
|
-
console.error(
|
|
4424
|
+
console.error(chalk12.red(`Failed to initialize index: ${error instanceof Error ? error.message : String(error)}`));
|
|
3717
4425
|
}
|
|
3718
4426
|
process.exit(1);
|
|
3719
4427
|
}
|
|
3720
4428
|
}
|
|
3721
|
-
async function indexUpdate() {
|
|
4429
|
+
async function indexUpdate(options) {
|
|
3722
4430
|
const cwd = process.cwd();
|
|
3723
|
-
|
|
4431
|
+
if (options?.cloud) {
|
|
4432
|
+
console.log(chalk12.blue("Updating cloud semantic index..."));
|
|
4433
|
+
const indexer = await getCloudIndexer(cwd);
|
|
4434
|
+
if (!indexer) return;
|
|
4435
|
+
try {
|
|
4436
|
+
const files = await glob4("**/*.{ts,tsx,js,jsx,py,rb,go,rs,java,md,json,yaml,yml}", {
|
|
4437
|
+
cwd,
|
|
4438
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**", "**/build/**", "**/coverage/**"]
|
|
4439
|
+
});
|
|
4440
|
+
console.log(chalk12.dim(`Found ${files.length} files to index...`));
|
|
4441
|
+
console.log(chalk12.dim("This may take a few minutes and will use OpenAI API credits.\n"));
|
|
4442
|
+
let totalChunks = 0;
|
|
4443
|
+
let indexedFiles = 0;
|
|
4444
|
+
let skippedFiles = 0;
|
|
4445
|
+
for (let i = 0; i < files.length; i++) {
|
|
4446
|
+
const file = files[i];
|
|
4447
|
+
if (!file) continue;
|
|
4448
|
+
process.stdout.write(`\r${chalk12.dim(`[${i + 1}/${files.length}] ${file.slice(0, 45).padEnd(45)}`)}`);
|
|
4449
|
+
try {
|
|
4450
|
+
const chunks = await indexer.indexFile(cwd, file);
|
|
4451
|
+
if (chunks > 0) {
|
|
4452
|
+
totalChunks += chunks;
|
|
4453
|
+
indexedFiles++;
|
|
4454
|
+
} else {
|
|
4455
|
+
skippedFiles++;
|
|
4456
|
+
}
|
|
4457
|
+
} catch (error) {
|
|
4458
|
+
console.log(`
|
|
4459
|
+
${chalk12.yellow(`\u26A0 Skipped ${file}: ${error instanceof Error ? error.message : "Unknown error"}`)}`);
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
console.log("\r" + " ".repeat(70));
|
|
4463
|
+
console.log(chalk12.green(`\u2713 Indexed ${indexedFiles} files (${totalChunks} chunks)`));
|
|
4464
|
+
if (skippedFiles > 0) {
|
|
4465
|
+
console.log(chalk12.dim(` Skipped ${skippedFiles} unchanged files`));
|
|
4466
|
+
}
|
|
4467
|
+
} catch (error) {
|
|
4468
|
+
console.error(chalk12.red(`Failed to update cloud index: ${error instanceof Error ? error.message : String(error)}`));
|
|
4469
|
+
process.exit(1);
|
|
4470
|
+
}
|
|
4471
|
+
return;
|
|
4472
|
+
}
|
|
4473
|
+
console.log(chalk12.blue("Updating local semantic index..."));
|
|
3724
4474
|
try {
|
|
3725
4475
|
const indexer = new LocalIndexer();
|
|
3726
4476
|
await indexer.init(cwd);
|
|
@@ -3728,11 +4478,12 @@ async function indexUpdate() {
|
|
|
3728
4478
|
cwd,
|
|
3729
4479
|
ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**", "**/build/**", "**/coverage/**"]
|
|
3730
4480
|
});
|
|
3731
|
-
console.log(
|
|
4481
|
+
console.log(chalk12.dim(`Found ${files.length} files to index...`));
|
|
3732
4482
|
let totalChunks = 0;
|
|
3733
4483
|
let indexedFiles = 0;
|
|
3734
4484
|
for (const file of files) {
|
|
3735
|
-
|
|
4485
|
+
if (!file) continue;
|
|
4486
|
+
process.stdout.write(`\r${chalk12.dim(`Indexing: ${file.slice(0, 50).padEnd(50)}`)}`);
|
|
3736
4487
|
const chunks = await indexer.indexFile(cwd, file);
|
|
3737
4488
|
if (chunks > 0) {
|
|
3738
4489
|
totalChunks += chunks;
|
|
@@ -3740,54 +4491,211 @@ async function indexUpdate() {
|
|
|
3740
4491
|
}
|
|
3741
4492
|
}
|
|
3742
4493
|
console.log("\r" + " ".repeat(60));
|
|
3743
|
-
console.log(
|
|
4494
|
+
console.log(chalk12.green(`\u2713 Indexed ${indexedFiles} files (${totalChunks} chunks)`));
|
|
3744
4495
|
indexer.close();
|
|
3745
4496
|
} catch (error) {
|
|
3746
|
-
console.error(
|
|
4497
|
+
console.error(chalk12.red(`Failed to update index: ${error instanceof Error ? error.message : String(error)}`));
|
|
3747
4498
|
process.exit(1);
|
|
3748
4499
|
}
|
|
3749
4500
|
}
|
|
3750
|
-
async function indexSearch(query) {
|
|
4501
|
+
async function indexSearch(query, options) {
|
|
3751
4502
|
const cwd = process.cwd();
|
|
4503
|
+
if (options?.cloud) {
|
|
4504
|
+
const indexer = await getCloudIndexer(cwd);
|
|
4505
|
+
if (!indexer) return;
|
|
4506
|
+
try {
|
|
4507
|
+
console.log(chalk12.dim("Searching cloud index..."));
|
|
4508
|
+
const results = await indexer.search(query, 10);
|
|
4509
|
+
if (results.length === 0) {
|
|
4510
|
+
console.log(chalk12.yellow("\nNo results found."));
|
|
4511
|
+
console.log(chalk12.dim("Try running `archon index update --cloud` first."));
|
|
4512
|
+
} else {
|
|
4513
|
+
console.log(chalk12.blue(`
|
|
4514
|
+
Top ${results.length} results for: "${query}"
|
|
4515
|
+
`));
|
|
4516
|
+
for (const result of results) {
|
|
4517
|
+
const score = (result.score * 100).toFixed(1);
|
|
4518
|
+
console.log(chalk12.green(`[${score}%] ${result.file}`));
|
|
4519
|
+
const preview = result.text.slice(0, 200).replace(/\n/g, " ").trim();
|
|
4520
|
+
console.log(chalk12.dim(` ${preview}${result.text.length > 200 ? "..." : ""}`));
|
|
4521
|
+
console.log();
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
} catch (error) {
|
|
4525
|
+
console.error(chalk12.red(`Cloud search failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
4526
|
+
process.exit(1);
|
|
4527
|
+
}
|
|
4528
|
+
return;
|
|
4529
|
+
}
|
|
3752
4530
|
try {
|
|
3753
4531
|
const indexer = new LocalIndexer();
|
|
3754
4532
|
await indexer.init(cwd);
|
|
3755
4533
|
const results = await indexer.search(query, 10);
|
|
3756
4534
|
if (results.length === 0) {
|
|
3757
|
-
console.log(
|
|
3758
|
-
console.log(
|
|
4535
|
+
console.log(chalk12.yellow("No results found."));
|
|
4536
|
+
console.log(chalk12.dim("Try running `archon index update` first."));
|
|
3759
4537
|
} else {
|
|
3760
|
-
console.log(
|
|
4538
|
+
console.log(chalk12.blue(`
|
|
3761
4539
|
Top ${results.length} results for: "${query}"
|
|
3762
4540
|
`));
|
|
3763
4541
|
for (const result of results) {
|
|
3764
4542
|
const score = (result.score * 100).toFixed(1);
|
|
3765
|
-
console.log(
|
|
4543
|
+
console.log(chalk12.green(`[${score}%] ${result.file}`));
|
|
3766
4544
|
const preview = result.text.slice(0, 200).replace(/\n/g, " ").trim();
|
|
3767
|
-
console.log(
|
|
4545
|
+
console.log(chalk12.dim(` ${preview}${result.text.length > 200 ? "..." : ""}`));
|
|
3768
4546
|
console.log();
|
|
3769
4547
|
}
|
|
3770
4548
|
}
|
|
3771
4549
|
indexer.close();
|
|
3772
4550
|
} catch (error) {
|
|
3773
|
-
console.error(
|
|
4551
|
+
console.error(chalk12.red(`Search failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
3774
4552
|
process.exit(1);
|
|
3775
4553
|
}
|
|
3776
4554
|
}
|
|
3777
|
-
async function indexStatus() {
|
|
4555
|
+
async function indexStatus(options) {
|
|
3778
4556
|
const cwd = process.cwd();
|
|
4557
|
+
if (options?.cloud) {
|
|
4558
|
+
const indexer = await getCloudIndexer(cwd);
|
|
4559
|
+
if (!indexer) return;
|
|
4560
|
+
try {
|
|
4561
|
+
const status2 = await indexer.getStatus();
|
|
4562
|
+
console.log(chalk12.blue("\nCloud Semantic Index Status\n"));
|
|
4563
|
+
console.log(` Project ID: ${chalk12.green(status2.projectId)}`);
|
|
4564
|
+
console.log(` Files indexed: ${chalk12.green(status2.fileCount)}`);
|
|
4565
|
+
console.log(` Total chunks: ${chalk12.green(status2.chunkCount)}`);
|
|
4566
|
+
console.log(` Last updated: ${status2.lastUpdated ? chalk12.dim(status2.lastUpdated) : chalk12.yellow("Never")}`);
|
|
4567
|
+
if (status2.jobStatus) {
|
|
4568
|
+
console.log(` Job status: ${chalk12.dim(status2.jobStatus)}`);
|
|
4569
|
+
}
|
|
4570
|
+
console.log(` Storage: ${chalk12.dim("Supabase pgvector")}`);
|
|
4571
|
+
} catch (error) {
|
|
4572
|
+
console.error(chalk12.red(`Failed to get cloud status: ${error instanceof Error ? error.message : String(error)}`));
|
|
4573
|
+
process.exit(1);
|
|
4574
|
+
}
|
|
4575
|
+
return;
|
|
4576
|
+
}
|
|
3779
4577
|
try {
|
|
3780
4578
|
const indexer = new LocalIndexer();
|
|
3781
4579
|
await indexer.init(cwd);
|
|
3782
4580
|
const status2 = await indexer.getStatus();
|
|
3783
|
-
console.log(
|
|
3784
|
-
console.log(` Files indexed: ${
|
|
3785
|
-
console.log(` Total chunks: ${
|
|
3786
|
-
console.log(` Last updated: ${status2.lastUpdated ?
|
|
3787
|
-
console.log(` Database: ${
|
|
4581
|
+
console.log(chalk12.blue("\nLocal Semantic Index Status\n"));
|
|
4582
|
+
console.log(` Files indexed: ${chalk12.green(status2.fileCount)}`);
|
|
4583
|
+
console.log(` Total chunks: ${chalk12.green(status2.chunkCount)}`);
|
|
4584
|
+
console.log(` Last updated: ${status2.lastUpdated ? chalk12.dim(status2.lastUpdated) : chalk12.yellow("Never")}`);
|
|
4585
|
+
console.log(` Database: ${chalk12.dim(join13(cwd, ".archon/index.db"))}`);
|
|
3788
4586
|
indexer.close();
|
|
3789
4587
|
} catch (error) {
|
|
3790
|
-
console.error(
|
|
4588
|
+
console.error(chalk12.red(`Failed to get status: ${error instanceof Error ? error.message : String(error)}`));
|
|
4589
|
+
process.exit(1);
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
async function indexClear(options) {
|
|
4593
|
+
const cwd = process.cwd();
|
|
4594
|
+
if (options?.cloud) {
|
|
4595
|
+
const indexer = await getCloudIndexer(cwd);
|
|
4596
|
+
if (!indexer) return;
|
|
4597
|
+
try {
|
|
4598
|
+
console.log(chalk12.yellow("Clearing cloud index..."));
|
|
4599
|
+
await indexer.clearProject();
|
|
4600
|
+
console.log(chalk12.green("\u2713 Cloud index cleared"));
|
|
4601
|
+
} catch (error) {
|
|
4602
|
+
console.error(chalk12.red(`Failed to clear cloud index: ${error instanceof Error ? error.message : String(error)}`));
|
|
4603
|
+
process.exit(1);
|
|
4604
|
+
}
|
|
4605
|
+
return;
|
|
4606
|
+
}
|
|
4607
|
+
console.log(chalk12.yellow("To clear local index, delete .archon/index.db"));
|
|
4608
|
+
}
|
|
4609
|
+
|
|
4610
|
+
// src/cli/github.ts
|
|
4611
|
+
import chalk13 from "chalk";
|
|
4612
|
+
import open2 from "open";
|
|
4613
|
+
var API_URL2 = process.env["ARCHONDEV_API_URL"] ?? "https://archondev-api.fly.dev";
|
|
4614
|
+
async function githubConnect() {
|
|
4615
|
+
const config = await loadConfig();
|
|
4616
|
+
const authToken = getAuthToken(config);
|
|
4617
|
+
if (!authToken) {
|
|
4618
|
+
console.error(chalk13.red('Not authenticated. Run "archon login" first.'));
|
|
4619
|
+
process.exit(1);
|
|
4620
|
+
}
|
|
4621
|
+
console.log(chalk13.dim("Starting GitHub connection..."));
|
|
4622
|
+
try {
|
|
4623
|
+
const response = await fetch(`${API_URL2}/api/github/connect`, {
|
|
4624
|
+
headers: {
|
|
4625
|
+
"Authorization": `Bearer ${authToken}`
|
|
4626
|
+
}
|
|
4627
|
+
});
|
|
4628
|
+
if (!response.ok) {
|
|
4629
|
+
const error = await response.json();
|
|
4630
|
+
console.error(chalk13.red(error.error ?? "Failed to start GitHub connection"));
|
|
4631
|
+
process.exit(1);
|
|
4632
|
+
}
|
|
4633
|
+
const data = await response.json();
|
|
4634
|
+
console.log(chalk13.dim("\nOpening browser for GitHub authorization..."));
|
|
4635
|
+
console.log(chalk13.dim("If browser does not open, visit:"));
|
|
4636
|
+
console.log(chalk13.blue(data.url));
|
|
4637
|
+
await open2(data.url);
|
|
4638
|
+
console.log(chalk13.dim("\nComplete the authorization in your browser."));
|
|
4639
|
+
console.log(chalk13.dim('Then run "archon github status" to verify connection.'));
|
|
4640
|
+
} catch (error) {
|
|
4641
|
+
console.error(chalk13.red(error instanceof Error ? error.message : "Failed to connect"));
|
|
4642
|
+
process.exit(1);
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
async function githubStatus() {
|
|
4646
|
+
const config = await loadConfig();
|
|
4647
|
+
const authToken = getAuthToken(config);
|
|
4648
|
+
if (!authToken) {
|
|
4649
|
+
console.error(chalk13.red('Not authenticated. Run "archon login" first.'));
|
|
4650
|
+
process.exit(1);
|
|
4651
|
+
}
|
|
4652
|
+
try {
|
|
4653
|
+
const response = await fetch(`${API_URL2}/api/github/status`, {
|
|
4654
|
+
headers: {
|
|
4655
|
+
"Authorization": `Bearer ${authToken}`
|
|
4656
|
+
}
|
|
4657
|
+
});
|
|
4658
|
+
if (!response.ok) {
|
|
4659
|
+
const error = await response.json();
|
|
4660
|
+
console.error(chalk13.red(error.error ?? "Failed to get GitHub status"));
|
|
4661
|
+
process.exit(1);
|
|
4662
|
+
}
|
|
4663
|
+
const data = await response.json();
|
|
4664
|
+
if (data.connected) {
|
|
4665
|
+
console.log(chalk13.green("\u2713 GitHub connected"));
|
|
4666
|
+
console.log(chalk13.dim(` Username: ${data.username}`));
|
|
4667
|
+
console.log(chalk13.dim(` Connected: ${data.connectedAt ? new Date(data.connectedAt).toLocaleDateString() : "Unknown"}`));
|
|
4668
|
+
} else {
|
|
4669
|
+
console.log(chalk13.yellow("GitHub not connected"));
|
|
4670
|
+
console.log(chalk13.dim('Run "archon github connect" to connect your GitHub account.'));
|
|
4671
|
+
}
|
|
4672
|
+
} catch (error) {
|
|
4673
|
+
console.error(chalk13.red(error instanceof Error ? error.message : "Failed to get status"));
|
|
4674
|
+
process.exit(1);
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
async function githubDisconnect() {
|
|
4678
|
+
const config = await loadConfig();
|
|
4679
|
+
const authToken = getAuthToken(config);
|
|
4680
|
+
if (!authToken) {
|
|
4681
|
+
console.error(chalk13.red('Not authenticated. Run "archon login" first.'));
|
|
4682
|
+
process.exit(1);
|
|
4683
|
+
}
|
|
4684
|
+
try {
|
|
4685
|
+
const response = await fetch(`${API_URL2}/api/github/disconnect`, {
|
|
4686
|
+
method: "DELETE",
|
|
4687
|
+
headers: {
|
|
4688
|
+
"Authorization": `Bearer ${authToken}`
|
|
4689
|
+
}
|
|
4690
|
+
});
|
|
4691
|
+
if (!response.ok) {
|
|
4692
|
+
const error = await response.json();
|
|
4693
|
+
console.error(chalk13.red(error.error ?? "Failed to disconnect GitHub"));
|
|
4694
|
+
process.exit(1);
|
|
4695
|
+
}
|
|
4696
|
+
console.log(chalk13.green("\u2713 GitHub disconnected"));
|
|
4697
|
+
} catch (error) {
|
|
4698
|
+
console.error(chalk13.red(error instanceof Error ? error.message : "Failed to disconnect"));
|
|
3791
4699
|
process.exit(1);
|
|
3792
4700
|
}
|
|
3793
4701
|
}
|
|
@@ -3797,7 +4705,7 @@ var program = new Command4();
|
|
|
3797
4705
|
program.name("archon").description("Local-first AI-powered development governance").version("1.1.0").action(async () => {
|
|
3798
4706
|
const cwd = process.cwd();
|
|
3799
4707
|
if (!isInitialized(cwd)) {
|
|
3800
|
-
console.log(
|
|
4708
|
+
console.log(chalk14.blue("\nArchonDev is not initialized in this folder.\n"));
|
|
3801
4709
|
await init({ analyze: true, git: true });
|
|
3802
4710
|
}
|
|
3803
4711
|
await start();
|
|
@@ -3805,7 +4713,7 @@ program.name("archon").description("Local-first AI-powered development governanc
|
|
|
3805
4713
|
program.command("login").description("Authenticate with ArchonDev").option("-p, --provider <provider>", "OAuth provider (github or google)", "github").action(async (options) => {
|
|
3806
4714
|
const provider = options.provider;
|
|
3807
4715
|
if (provider !== "github" && provider !== "google") {
|
|
3808
|
-
console.error(
|
|
4716
|
+
console.error(chalk14.red('Invalid provider. Use "github" or "google"'));
|
|
3809
4717
|
process.exit(1);
|
|
3810
4718
|
}
|
|
3811
4719
|
await login(provider);
|
|
@@ -3953,11 +4861,12 @@ a11yCommand.action(async () => {
|
|
|
3953
4861
|
program.addCommand(createSeoCommand());
|
|
3954
4862
|
program.addCommand(createGeoCommand());
|
|
3955
4863
|
program.command("deploy").description("Deploy application (auto-detects platform)").option("-p, --platform <name>", "Specify platform (fly/vercel/netlify/railway/render)").option("--preview", "Deploy to preview/staging environment").option("--dry-run", "Show what would happen without deploying").action(deploy);
|
|
3956
|
-
var indexCmd = program.command("index").description("Semantic codebase indexing
|
|
3957
|
-
indexCmd.command("init").description("Initialize semantic index").option("--local", "Use local Ollama for embeddings (default)").option("--cloud", "Use cloud
|
|
3958
|
-
indexCmd.command("update").description("Reindex changed files").action(indexUpdate);
|
|
3959
|
-
indexCmd.command("search <query>").description("Semantic search across codebase").action(indexSearch);
|
|
3960
|
-
indexCmd.command("status").description("Show index statistics").action(indexStatus);
|
|
4864
|
+
var indexCmd = program.command("index").description("Semantic codebase indexing (local Ollama or cloud pgvector)");
|
|
4865
|
+
indexCmd.command("init").description("Initialize semantic index").option("--local", "Use local Ollama for embeddings (default)").option("--cloud", "Use cloud pgvector with OpenAI embeddings").action(indexInit);
|
|
4866
|
+
indexCmd.command("update").description("Reindex changed files").option("--cloud", "Use cloud indexing").action(indexUpdate);
|
|
4867
|
+
indexCmd.command("search <query>").description("Semantic search across codebase").option("--cloud", "Search cloud index").action((query, options) => indexSearch(query, options));
|
|
4868
|
+
indexCmd.command("status").description("Show index statistics").option("--cloud", "Show cloud index status").action(indexStatus);
|
|
4869
|
+
indexCmd.command("clear").description("Clear the semantic index").option("--cloud", "Clear cloud index").action(indexClear);
|
|
3961
4870
|
indexCmd.action(indexStatus);
|
|
3962
4871
|
var sessionCmd = program.command("session").description("Cross-device session management");
|
|
3963
4872
|
sessionCmd.command("save [name]").description("Save current session").action(saveSession);
|
|
@@ -3975,4 +4884,20 @@ parallelCmd.command("status").description("Show status of all parallel execution
|
|
|
3975
4884
|
parallelCmd.command("merge [atomId]").description("Merge completed worktrees back to main").action(parallelMerge);
|
|
3976
4885
|
parallelCmd.command("clean").description("Clean up all parallel execution state and worktrees").action(parallelClean);
|
|
3977
4886
|
parallelCmd.action(parallelStatus);
|
|
4887
|
+
var githubCmd = program.command("github").description("GitHub integration for cloud execution");
|
|
4888
|
+
githubCmd.command("connect").description("Connect your GitHub account for cloud execution").action(githubConnect);
|
|
4889
|
+
githubCmd.command("status").description("Show GitHub connection status").action(githubStatus);
|
|
4890
|
+
githubCmd.command("disconnect").description("Disconnect your GitHub account").action(githubDisconnect);
|
|
4891
|
+
githubCmd.action(githubStatus);
|
|
4892
|
+
var cleanupCmd = program.command("cleanup").description("Workspace maintenance and file cleanup");
|
|
4893
|
+
cleanupCmd.command("check").description("Analyze workspace for bloat and maintenance needs").action(cleanupCheck);
|
|
4894
|
+
cleanupCmd.command("run").description("Execute cleanup (archive old entries, remove stale files)").action(cleanupRun);
|
|
4895
|
+
cleanupCmd.command("auto").description("Enable/disable automatic cleanup checks").argument("[action]", "enable, disable, or status", "status").action(async (action) => {
|
|
4896
|
+
if (action !== "enable" && action !== "disable" && action !== "status") {
|
|
4897
|
+
console.error(chalk14.red("Invalid action. Use: enable, disable, or status"));
|
|
4898
|
+
process.exit(1);
|
|
4899
|
+
}
|
|
4900
|
+
await cleanupAuto(action);
|
|
4901
|
+
});
|
|
4902
|
+
cleanupCmd.action(cleanupCheck);
|
|
3978
4903
|
program.parse();
|