@gowelle/stint-agent 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -34
- package/dist/StatusDashboard-7ZIGEOQ4.js +232 -0
- package/dist/api-HLTR2LTF.js +7 -0
- package/dist/{chunk-OHOFKJL7.js → chunk-FBQA4K5J.js} +95 -207
- package/dist/chunk-IJUJ6NEL.js +369 -0
- package/dist/chunk-RHMTZK2J.js +297 -0
- package/dist/{chunk-IAERVP6F.js → chunk-W4JGOGR7.js} +42 -291
- package/dist/daemon/runner.js +8 -236
- package/dist/index.js +577 -70
- package/package.json +23 -1
- package/dist/api-YI2HWZGL.js +0 -6
package/dist/index.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
commitQueue,
|
|
4
|
+
websocketService
|
|
5
|
+
} from "./chunk-IJUJ6NEL.js";
|
|
6
|
+
import {
|
|
7
|
+
apiService
|
|
8
|
+
} from "./chunk-W4JGOGR7.js";
|
|
9
|
+
import {
|
|
4
10
|
getPidFilePath,
|
|
5
11
|
gitService,
|
|
6
12
|
isProcessRunning,
|
|
@@ -8,18 +14,17 @@ import {
|
|
|
8
14
|
projectService,
|
|
9
15
|
spawnDetached,
|
|
10
16
|
validatePidFile
|
|
11
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-FBQA4K5J.js";
|
|
12
18
|
import {
|
|
13
|
-
apiService,
|
|
14
19
|
authService,
|
|
15
20
|
config,
|
|
16
21
|
logger
|
|
17
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-RHMTZK2J.js";
|
|
18
23
|
|
|
19
24
|
// src/index.ts
|
|
20
25
|
import "dotenv/config";
|
|
21
26
|
import { Command } from "commander";
|
|
22
|
-
import
|
|
27
|
+
import chalk13 from "chalk";
|
|
23
28
|
|
|
24
29
|
// src/commands/login.ts
|
|
25
30
|
import open from "open";
|
|
@@ -609,10 +614,22 @@ import process4 from "process";
|
|
|
609
614
|
import path2 from "path";
|
|
610
615
|
import os from "os";
|
|
611
616
|
function registerStatusCommand(program2) {
|
|
612
|
-
program2.command("status").description("Show linked project and connection status").action(async () => {
|
|
617
|
+
program2.command("status").description("Show linked project and connection status").option("-d, --dashboard", "Launch interactive TUI dashboard").action(async (options) => {
|
|
618
|
+
const cwd = process4.cwd();
|
|
619
|
+
if (options.dashboard) {
|
|
620
|
+
try {
|
|
621
|
+
const { render } = await import("ink");
|
|
622
|
+
const { createElement } = await import("react");
|
|
623
|
+
const { StatusDashboard } = await import("./StatusDashboard-7ZIGEOQ4.js");
|
|
624
|
+
render(createElement(StatusDashboard, { cwd }));
|
|
625
|
+
return;
|
|
626
|
+
} catch (error) {
|
|
627
|
+
console.error(chalk6.red(`Failed to start dashboard: ${error.message}`));
|
|
628
|
+
process4.exit(1);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
613
631
|
const spinner = ora6("Gathering status...").start();
|
|
614
632
|
try {
|
|
615
|
-
const cwd = process4.cwd();
|
|
616
633
|
const linkedProject = await projectService.getLinkedProject(cwd);
|
|
617
634
|
const user = await authService.validateToken();
|
|
618
635
|
spinner.stop();
|
|
@@ -705,13 +722,24 @@ function registerSyncCommand(program2) {
|
|
|
705
722
|
console.log(chalk7.gray('Run "stint link" first to link this directory.\n'));
|
|
706
723
|
process5.exit(1);
|
|
707
724
|
}
|
|
708
|
-
spinner.text = "
|
|
725
|
+
spinner.text = "Analyzing repository...";
|
|
726
|
+
const status = await gitService.getStatus(cwd);
|
|
727
|
+
const totalFiles = status.staged.length + status.unstaged.length + status.untracked.length;
|
|
728
|
+
spinner.text = `Found ${totalFiles} files to analyze...`;
|
|
729
|
+
spinner.text = "Getting branch information...";
|
|
709
730
|
const repoInfo = await gitService.getRepoInfo(cwd);
|
|
710
|
-
spinner.text = "
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
731
|
+
spinner.text = "Preparing sync payload...";
|
|
732
|
+
const syncSpinner = ora7("Connecting to server...").start();
|
|
733
|
+
try {
|
|
734
|
+
await apiService.syncProject(linkedProject.projectId, repoInfo);
|
|
735
|
+
syncSpinner.succeed("Server sync completed");
|
|
736
|
+
} catch (error) {
|
|
737
|
+
syncSpinner.fail("Server sync failed");
|
|
738
|
+
throw error;
|
|
739
|
+
}
|
|
740
|
+
console.log(chalk7.green("\n\u2713 Repository sync completed"));
|
|
714
741
|
console.log(chalk7.gray("\u2500".repeat(50)));
|
|
742
|
+
console.log(`${chalk7.bold("Files:")} ${totalFiles} total (${status.staged.length} staged, ${status.unstaged.length} modified, ${status.untracked.length} untracked)`);
|
|
715
743
|
console.log(`${chalk7.bold("Project ID:")} ${linkedProject.projectId}`);
|
|
716
744
|
console.log(`${chalk7.bold("Branch:")} ${repoInfo.currentBranch}`);
|
|
717
745
|
console.log(`${chalk7.bold("Commit:")} ${repoInfo.lastCommitSha.substring(0, 7)} - ${repoInfo.lastCommitMessage}`);
|
|
@@ -734,11 +762,160 @@ import ora8 from "ora";
|
|
|
734
762
|
import chalk8 from "chalk";
|
|
735
763
|
import fs from "fs";
|
|
736
764
|
import path3 from "path";
|
|
737
|
-
import
|
|
765
|
+
import os3 from "os";
|
|
738
766
|
import { fileURLToPath } from "url";
|
|
739
767
|
import { dirname } from "path";
|
|
768
|
+
import { createInterface } from "readline";
|
|
769
|
+
|
|
770
|
+
// src/utils/monitor.ts
|
|
771
|
+
import os2 from "os";
|
|
772
|
+
import { readFileSync } from "fs";
|
|
773
|
+
import { execSync } from "child_process";
|
|
774
|
+
function getProcessStats(pid) {
|
|
775
|
+
try {
|
|
776
|
+
const platform = os2.platform();
|
|
777
|
+
if (platform === "linux") {
|
|
778
|
+
return getLinuxStats(pid);
|
|
779
|
+
} else if (platform === "darwin") {
|
|
780
|
+
return getMacStats(pid);
|
|
781
|
+
} else if (platform === "win32") {
|
|
782
|
+
return getWindowsStats(pid);
|
|
783
|
+
}
|
|
784
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
785
|
+
} catch (error) {
|
|
786
|
+
logger.error("monitor", `Failed to get process stats for PID ${pid}`, error);
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function getLinuxStats(pid) {
|
|
791
|
+
const statContent = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
792
|
+
const statParts = statContent.split(" ");
|
|
793
|
+
const statusContent = readFileSync(`/proc/${pid}/status`, "utf8");
|
|
794
|
+
const vmRSS = parseInt(statusContent.match(/VmRSS:\s+(\d+)/)?.[1] || "0");
|
|
795
|
+
readFileSync("/proc/stat", "utf8");
|
|
796
|
+
const utime = parseInt(statParts[13]);
|
|
797
|
+
const stime = parseInt(statParts[14]);
|
|
798
|
+
const starttime = parseInt(statParts[21]);
|
|
799
|
+
const threads = parseInt(statParts[19]);
|
|
800
|
+
const totalTime = utime + stime;
|
|
801
|
+
const seconds = os2.uptime() - starttime / os2.cpus().length;
|
|
802
|
+
const cpuPercent = totalTime / seconds * 100 / os2.cpus().length;
|
|
803
|
+
return {
|
|
804
|
+
pid,
|
|
805
|
+
cpuPercent: Math.round(cpuPercent * 100) / 100,
|
|
806
|
+
memoryMB: Math.round(vmRSS / 1024 * 100) / 100,
|
|
807
|
+
uptime: Math.round(seconds),
|
|
808
|
+
threads
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
function getMacStats(pid) {
|
|
812
|
+
const psOutput = execSync(`ps -p ${pid} -o %cpu,%mem,etime,thcount`).toString();
|
|
813
|
+
const [cpu, mem, etime, threads] = psOutput.split("\n")[1].trim().split(/\s+/);
|
|
814
|
+
const uptimeSeconds = parseElapsedTime(etime);
|
|
815
|
+
const totalMem = os2.totalmem() / (1024 * 1024);
|
|
816
|
+
const memoryMB = parseFloat(mem) / 100 * totalMem;
|
|
817
|
+
return {
|
|
818
|
+
pid,
|
|
819
|
+
cpuPercent: Math.round(parseFloat(cpu) * 100) / 100,
|
|
820
|
+
memoryMB: Math.round(memoryMB * 100) / 100,
|
|
821
|
+
uptime: uptimeSeconds,
|
|
822
|
+
threads: parseInt(threads)
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
function getWindowsStats(pid) {
|
|
826
|
+
const wmicOutput = execSync(
|
|
827
|
+
`wmic path Win32_PerfFormattedData_PerfProc_Process WHERE IDProcess=${pid} get PercentProcessorTime,WorkingSetPrivate,ElapsedTime,ThreadCount /format:csv`
|
|
828
|
+
).toString();
|
|
829
|
+
const [, , data] = wmicOutput.trim().split("\n");
|
|
830
|
+
if (!data) {
|
|
831
|
+
throw new Error(`Process ${pid} not found`);
|
|
832
|
+
}
|
|
833
|
+
const [, cpu, workingSet, elapsedTime, threads] = data.split(",");
|
|
834
|
+
return {
|
|
835
|
+
pid,
|
|
836
|
+
cpuPercent: Math.round(parseInt(cpu) / os2.cpus().length * 100) / 100,
|
|
837
|
+
memoryMB: Math.round(parseInt(workingSet) / (1024 * 1024) * 100) / 100,
|
|
838
|
+
uptime: Math.round(parseInt(elapsedTime) / 1e3),
|
|
839
|
+
threads: parseInt(threads)
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
function parseElapsedTime(etime) {
|
|
843
|
+
const parts = etime.split("-");
|
|
844
|
+
const timeStr = parts[parts.length - 1];
|
|
845
|
+
const timeParts = timeStr.split(":");
|
|
846
|
+
let seconds = 0;
|
|
847
|
+
if (timeParts.length === 3) {
|
|
848
|
+
seconds = parseInt(timeParts[0]) * 3600 + parseInt(timeParts[1]) * 60 + parseInt(timeParts[2]);
|
|
849
|
+
} else if (timeParts.length === 2) {
|
|
850
|
+
seconds = parseInt(timeParts[0]) * 60 + parseInt(timeParts[1]);
|
|
851
|
+
} else {
|
|
852
|
+
seconds = parseInt(timeParts[0]);
|
|
853
|
+
}
|
|
854
|
+
if (parts.length === 2) {
|
|
855
|
+
seconds += parseInt(parts[0]) * 86400;
|
|
856
|
+
}
|
|
857
|
+
return seconds;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// src/commands/daemon.ts
|
|
740
861
|
var __filename = fileURLToPath(import.meta.url);
|
|
741
862
|
var __dirname = dirname(__filename);
|
|
863
|
+
function parseLogLine(line) {
|
|
864
|
+
const match = line.match(/\[(.*?)\] (\w+)\s+\[(.*?)\] (.*)/);
|
|
865
|
+
if (!match) return null;
|
|
866
|
+
const [, timestamp, level, category, message] = match;
|
|
867
|
+
return {
|
|
868
|
+
timestamp: new Date(timestamp),
|
|
869
|
+
level,
|
|
870
|
+
category,
|
|
871
|
+
message
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function shouldIncludeLine(parsed, filter) {
|
|
875
|
+
if (!parsed) return false;
|
|
876
|
+
if (filter.level && filter.level.toUpperCase() !== parsed.level) {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
if (filter.category && !parsed.category.toLowerCase().includes(filter.category.toLowerCase())) {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
if (filter.since && parsed.timestamp < filter.since) {
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
885
|
+
if (filter.until && parsed.timestamp > filter.until) {
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
if (filter.search && !parsed.message.toLowerCase().includes(filter.search.toLowerCase())) {
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
function formatUptime(seconds) {
|
|
894
|
+
const days = Math.floor(seconds / 86400);
|
|
895
|
+
const hours = Math.floor(seconds % 86400 / 3600);
|
|
896
|
+
const minutes = Math.floor(seconds % 3600 / 60);
|
|
897
|
+
const secs = seconds % 60;
|
|
898
|
+
const parts = [];
|
|
899
|
+
if (days > 0) parts.push(`${days}d`);
|
|
900
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
901
|
+
if (minutes > 0) parts.push(`${minutes}m`);
|
|
902
|
+
if (secs > 0 || parts.length === 0) parts.push(`${secs}s`);
|
|
903
|
+
return parts.join(" ");
|
|
904
|
+
}
|
|
905
|
+
function colorizeLevel(level) {
|
|
906
|
+
switch (level.trim()) {
|
|
907
|
+
case "ERROR":
|
|
908
|
+
return chalk8.red(level);
|
|
909
|
+
case "WARN":
|
|
910
|
+
return chalk8.yellow(level);
|
|
911
|
+
case "INFO":
|
|
912
|
+
return chalk8.blue(level);
|
|
913
|
+
case "DEBUG":
|
|
914
|
+
return chalk8.gray(level);
|
|
915
|
+
default:
|
|
916
|
+
return level;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
742
919
|
function registerDaemonCommands(program2) {
|
|
743
920
|
const daemon = program2.command("daemon").description("Manage the Stint daemon");
|
|
744
921
|
daemon.command("start").description("Start the daemon in the background").action(async () => {
|
|
@@ -774,7 +951,7 @@ function registerDaemonCommands(program2) {
|
|
|
774
951
|
console.log(chalk8.green(`
|
|
775
952
|
\u2713 Daemon is running in the background`));
|
|
776
953
|
console.log(chalk8.gray(`PID: ${daemonPid}`));
|
|
777
|
-
console.log(chalk8.gray(`Logs: ${path3.join(
|
|
954
|
+
console.log(chalk8.gray(`Logs: ${path3.join(os3.homedir(), ".config", "stint", "logs", "daemon.log")}
|
|
778
955
|
`));
|
|
779
956
|
logger.success("daemon", `Daemon started with PID ${daemonPid}`);
|
|
780
957
|
} catch (error) {
|
|
@@ -835,7 +1012,16 @@ function registerDaemonCommands(program2) {
|
|
|
835
1012
|
console.log(`${chalk8.bold("Status:")} ${chalk8.green("\u2713 Running")}`);
|
|
836
1013
|
console.log(`${chalk8.bold("PID:")} ${pid}`);
|
|
837
1014
|
console.log(`${chalk8.bold("PID File:")} ${getPidFilePath()}`);
|
|
838
|
-
console.log(`${chalk8.bold("Logs:")} ${path3.join(
|
|
1015
|
+
console.log(`${chalk8.bold("Logs:")} ${path3.join(os3.homedir(), ".config", "stint", "logs", "daemon.log")}`);
|
|
1016
|
+
const stats = await getProcessStats(pid);
|
|
1017
|
+
if (stats) {
|
|
1018
|
+
console.log(chalk8.blue("\n\u{1F4CA} Resource Usage:"));
|
|
1019
|
+
console.log(chalk8.gray("\u2500".repeat(50)));
|
|
1020
|
+
console.log(`${chalk8.bold("CPU:")} ${stats.cpuPercent}%`);
|
|
1021
|
+
console.log(`${chalk8.bold("Memory:")} ${stats.memoryMB} MB`);
|
|
1022
|
+
console.log(`${chalk8.bold("Threads:")} ${stats.threads}`);
|
|
1023
|
+
console.log(`${chalk8.bold("Uptime:")} ${formatUptime(stats.uptime)}`);
|
|
1024
|
+
}
|
|
839
1025
|
} else {
|
|
840
1026
|
console.log(`${chalk8.bold("Status:")} ${chalk8.yellow("Not running")}`);
|
|
841
1027
|
console.log(chalk8.gray('Run "stint daemon start" to start the daemon.'));
|
|
@@ -847,36 +1033,6 @@ function registerDaemonCommands(program2) {
|
|
|
847
1033
|
logger.error("daemon", "Status command failed", error);
|
|
848
1034
|
console.error(chalk8.red(`
|
|
849
1035
|
\u2716 Error: ${error.message}
|
|
850
|
-
`));
|
|
851
|
-
process.exit(1);
|
|
852
|
-
}
|
|
853
|
-
});
|
|
854
|
-
daemon.command("logs").description("Tail daemon logs").option("-n, --lines <number>", "Number of lines to show", "50").action(async (options) => {
|
|
855
|
-
try {
|
|
856
|
-
const logFile = path3.join(os2.homedir(), ".config", "stint", "logs", "daemon.log");
|
|
857
|
-
if (!fs.existsSync(logFile)) {
|
|
858
|
-
console.log(chalk8.yellow("\n\u26A0 No daemon logs found."));
|
|
859
|
-
console.log(chalk8.gray("The daemon has not been started yet.\n"));
|
|
860
|
-
return;
|
|
861
|
-
}
|
|
862
|
-
const lines = parseInt(options.lines, 10);
|
|
863
|
-
const content = fs.readFileSync(logFile, "utf8");
|
|
864
|
-
const logLines = content.split("\n").filter((line) => line.trim());
|
|
865
|
-
const lastLines = logLines.slice(-lines);
|
|
866
|
-
console.log(chalk8.blue(`
|
|
867
|
-
\u{1F4CB} Last ${lastLines.length} lines of daemon logs:
|
|
868
|
-
`));
|
|
869
|
-
console.log(chalk8.gray("\u2500".repeat(80)));
|
|
870
|
-
lastLines.forEach((line) => console.log(line));
|
|
871
|
-
console.log(chalk8.gray("\u2500".repeat(80)));
|
|
872
|
-
console.log(chalk8.gray(`
|
|
873
|
-
Log file: ${logFile}`));
|
|
874
|
-
console.log(chalk8.gray('Use "tail -f" to follow logs in real-time.\n'));
|
|
875
|
-
logger.info("daemon", "Logs command executed");
|
|
876
|
-
} catch (error) {
|
|
877
|
-
logger.error("daemon", "Logs command failed", error);
|
|
878
|
-
console.error(chalk8.red(`
|
|
879
|
-
\u2716 Error: ${error.message}
|
|
880
1036
|
`));
|
|
881
1037
|
process.exit(1);
|
|
882
1038
|
}
|
|
@@ -925,6 +1081,119 @@ Log file: ${logFile}`));
|
|
|
925
1081
|
throw error;
|
|
926
1082
|
}
|
|
927
1083
|
});
|
|
1084
|
+
daemon.command("logs").description("View and filter daemon logs").option("-l, --level <level>", "Filter by log level (INFO, WARN, ERROR, DEBUG)").option("-c, --category <category>", "Filter by log category").option("-s, --since <date>", 'Show logs since date/time (ISO format or relative time like "1h", "2d")').option("-u, --until <date>", 'Show logs until date/time (ISO format or relative time like "1h", "2d")').option("--search <text>", "Search for specific text in log messages").option("-f, --follow", "Follow log output in real time").option("-n, --lines <number>", "Number of lines to show", "50").action(async (command) => {
|
|
1085
|
+
const spinner = ora8("Loading logs...").start();
|
|
1086
|
+
try {
|
|
1087
|
+
const logPath = path3.join(os3.homedir(), ".config", "stint", "logs", "agent.log");
|
|
1088
|
+
if (!fs.existsSync(logPath)) {
|
|
1089
|
+
spinner.fail("No logs found");
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
const now = /* @__PURE__ */ new Date();
|
|
1093
|
+
let since;
|
|
1094
|
+
let until;
|
|
1095
|
+
if (command.since) {
|
|
1096
|
+
if (command.since.match(/^\d+[hdw]$/)) {
|
|
1097
|
+
const value = parseInt(command.since.slice(0, -1));
|
|
1098
|
+
const unit = command.since.slice(-1);
|
|
1099
|
+
const ms = value * {
|
|
1100
|
+
h: 60 * 60 * 1e3,
|
|
1101
|
+
d: 24 * 60 * 60 * 1e3,
|
|
1102
|
+
w: 7 * 24 * 60 * 60 * 1e3
|
|
1103
|
+
}[unit];
|
|
1104
|
+
since = new Date(now.getTime() - ms);
|
|
1105
|
+
} else {
|
|
1106
|
+
since = new Date(command.since);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
if (command.until) {
|
|
1110
|
+
if (command.until.match(/^\d+[hdw]$/)) {
|
|
1111
|
+
const value = parseInt(command.until.slice(0, -1));
|
|
1112
|
+
const unit = command.until.slice(-1);
|
|
1113
|
+
const ms = value * {
|
|
1114
|
+
h: 60 * 60 * 1e3,
|
|
1115
|
+
d: 24 * 60 * 60 * 1e3,
|
|
1116
|
+
w: 7 * 24 * 60 * 60 * 1e3
|
|
1117
|
+
}[unit];
|
|
1118
|
+
until = new Date(now.getTime() - ms);
|
|
1119
|
+
} else {
|
|
1120
|
+
until = new Date(command.until);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
const filter = {
|
|
1124
|
+
level: command.level?.toUpperCase(),
|
|
1125
|
+
category: command.category,
|
|
1126
|
+
since,
|
|
1127
|
+
until,
|
|
1128
|
+
search: command.search
|
|
1129
|
+
};
|
|
1130
|
+
const maxLines = command.follow ? 10 : parseInt(command.lines);
|
|
1131
|
+
const lines = [];
|
|
1132
|
+
const fileStream = fs.createReadStream(logPath, { encoding: "utf8" });
|
|
1133
|
+
const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
1134
|
+
spinner.stop();
|
|
1135
|
+
for await (const line of rl) {
|
|
1136
|
+
const parsed = parseLogLine(line);
|
|
1137
|
+
if (parsed && shouldIncludeLine(parsed, filter)) {
|
|
1138
|
+
lines.push(line);
|
|
1139
|
+
if (lines.length > maxLines && !command.follow) {
|
|
1140
|
+
lines.shift();
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (lines.length === 0) {
|
|
1145
|
+
console.log(chalk8.yellow("\nNo matching logs found\n"));
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
console.log();
|
|
1149
|
+
lines.forEach((line) => {
|
|
1150
|
+
const parsed = parseLogLine(line);
|
|
1151
|
+
if (parsed) {
|
|
1152
|
+
const { timestamp, level, category, message } = parsed;
|
|
1153
|
+
console.log(
|
|
1154
|
+
chalk8.gray(`[${timestamp.toISOString()}]`),
|
|
1155
|
+
colorizeLevel(level.padEnd(5)),
|
|
1156
|
+
chalk8.cyan(`[${category}]`),
|
|
1157
|
+
message
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
console.log();
|
|
1162
|
+
if (command.follow) {
|
|
1163
|
+
console.log(chalk8.gray("Following log output (Ctrl+C to exit)...\n"));
|
|
1164
|
+
const tail = fs.watch(logPath, (eventType) => {
|
|
1165
|
+
if (eventType === "change") {
|
|
1166
|
+
const newLines = fs.readFileSync(logPath, "utf8").split("\n").slice(-1);
|
|
1167
|
+
newLines.forEach((line) => {
|
|
1168
|
+
if (!line) return;
|
|
1169
|
+
const parsed = parseLogLine(line);
|
|
1170
|
+
if (parsed && shouldIncludeLine(parsed, filter)) {
|
|
1171
|
+
const { timestamp, level, category, message } = parsed;
|
|
1172
|
+
console.log(
|
|
1173
|
+
chalk8.gray(`[${timestamp.toISOString()}]`),
|
|
1174
|
+
colorizeLevel(level.padEnd(5)),
|
|
1175
|
+
chalk8.cyan(`[${category}]`),
|
|
1176
|
+
message
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
process.on("SIGINT", () => {
|
|
1183
|
+
tail.close();
|
|
1184
|
+
console.log(chalk8.gray("\nStopped following logs\n"));
|
|
1185
|
+
process.exit(0);
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
} catch (error) {
|
|
1189
|
+
spinner.fail("Failed to read logs");
|
|
1190
|
+
logger.error("daemon", "Logs command failed", error);
|
|
1191
|
+
console.error(chalk8.red(`
|
|
1192
|
+
\u2716 Error: ${error.message}
|
|
1193
|
+
`));
|
|
1194
|
+
process.exit(1);
|
|
1195
|
+
}
|
|
1196
|
+
});
|
|
928
1197
|
}
|
|
929
1198
|
|
|
930
1199
|
// src/commands/commit.ts
|
|
@@ -1022,7 +1291,7 @@ function registerCommitCommands(program2) {
|
|
|
1022
1291
|
console.log(chalk9.yellow("\nCommit cancelled.\n"));
|
|
1023
1292
|
return;
|
|
1024
1293
|
}
|
|
1025
|
-
const execSpinner = ora9("
|
|
1294
|
+
const execSpinner = ora9("Preparing commit...").start();
|
|
1026
1295
|
const project = {
|
|
1027
1296
|
id: linkedProject.projectId,
|
|
1028
1297
|
name: "Current Project",
|
|
@@ -1030,14 +1299,24 @@ function registerCommitCommands(program2) {
|
|
|
1030
1299
|
createdAt: "",
|
|
1031
1300
|
updatedAt: ""
|
|
1032
1301
|
};
|
|
1033
|
-
const sha = await commitQueue.executeCommit(commit, project)
|
|
1302
|
+
const sha = await commitQueue.executeCommit(commit, project, (stage) => {
|
|
1303
|
+
execSpinner.text = stage;
|
|
1304
|
+
});
|
|
1034
1305
|
execSpinner.succeed("Commit executed successfully!");
|
|
1035
1306
|
console.log(chalk9.green("\n\u2713 Commit executed"));
|
|
1036
1307
|
console.log(chalk9.gray("\u2500".repeat(50)));
|
|
1037
1308
|
console.log(`${chalk9.bold("Commit ID:")} ${commit.id}`);
|
|
1038
1309
|
console.log(`${chalk9.bold("Message:")} ${commit.message}`);
|
|
1039
1310
|
console.log(`${chalk9.bold("SHA:")} ${sha}`);
|
|
1311
|
+
console.log(`${chalk9.bold("Files:")} ${status.staged.length} files committed`);
|
|
1040
1312
|
console.log();
|
|
1313
|
+
if (status.staged.length > 0) {
|
|
1314
|
+
console.log(chalk9.gray("Committed files:"));
|
|
1315
|
+
status.staged.forEach((file) => {
|
|
1316
|
+
console.log(chalk9.green(` + ${file}`));
|
|
1317
|
+
});
|
|
1318
|
+
console.log();
|
|
1319
|
+
}
|
|
1041
1320
|
logger.success("commit", `Executed commit ${commit.id} -> ${sha}`);
|
|
1042
1321
|
} catch (error) {
|
|
1043
1322
|
if (ora9().isSpinning) {
|
|
@@ -1066,7 +1345,7 @@ import ora10 from "ora";
|
|
|
1066
1345
|
import chalk10 from "chalk";
|
|
1067
1346
|
import fs2 from "fs";
|
|
1068
1347
|
import path4 from "path";
|
|
1069
|
-
import
|
|
1348
|
+
import os4 from "os";
|
|
1070
1349
|
import { exec } from "child_process";
|
|
1071
1350
|
import { promisify } from "util";
|
|
1072
1351
|
var execAsync = promisify(exec);
|
|
@@ -1094,8 +1373,8 @@ async function uninstallWindows() {
|
|
|
1094
1373
|
}
|
|
1095
1374
|
function getMacPlistContent() {
|
|
1096
1375
|
const scriptPath = process.argv[1];
|
|
1097
|
-
const logPath = path4.join(
|
|
1098
|
-
const errorPath = path4.join(
|
|
1376
|
+
const logPath = path4.join(os4.homedir(), ".config", "stint", "logs", "launchd.log");
|
|
1377
|
+
const errorPath = path4.join(os4.homedir(), ".config", "stint", "logs", "launchd.error.log");
|
|
1099
1378
|
const logDir = path4.dirname(logPath);
|
|
1100
1379
|
if (!fs2.existsSync(logDir)) {
|
|
1101
1380
|
fs2.mkdirSync(logDir, { recursive: true });
|
|
@@ -1129,7 +1408,7 @@ function getMacPlistContent() {
|
|
|
1129
1408
|
}
|
|
1130
1409
|
async function installMac() {
|
|
1131
1410
|
const plistContent = getMacPlistContent();
|
|
1132
|
-
const launchAgentsDir = path4.join(
|
|
1411
|
+
const launchAgentsDir = path4.join(os4.homedir(), "Library", "LaunchAgents");
|
|
1133
1412
|
const plistPath = path4.join(launchAgentsDir, MAC_PLIST_NAME);
|
|
1134
1413
|
if (!fs2.existsSync(launchAgentsDir)) {
|
|
1135
1414
|
fs2.mkdirSync(launchAgentsDir, { recursive: true });
|
|
@@ -1142,7 +1421,7 @@ async function installMac() {
|
|
|
1142
1421
|
await execAsync(`launchctl load "${plistPath}"`);
|
|
1143
1422
|
}
|
|
1144
1423
|
async function uninstallMac() {
|
|
1145
|
-
const launchAgentsDir = path4.join(
|
|
1424
|
+
const launchAgentsDir = path4.join(os4.homedir(), "Library", "LaunchAgents");
|
|
1146
1425
|
const plistPath = path4.join(launchAgentsDir, MAC_PLIST_NAME);
|
|
1147
1426
|
if (fs2.existsSync(plistPath)) {
|
|
1148
1427
|
try {
|
|
@@ -1170,7 +1449,7 @@ StandardError=journal
|
|
|
1170
1449
|
WantedBy=default.target`;
|
|
1171
1450
|
}
|
|
1172
1451
|
async function installLinux() {
|
|
1173
|
-
const systemdDir = path4.join(
|
|
1452
|
+
const systemdDir = path4.join(os4.homedir(), ".config", "systemd", "user");
|
|
1174
1453
|
const servicePath = path4.join(systemdDir, SYSTEMD_SERVICE_NAME);
|
|
1175
1454
|
if (!fs2.existsSync(systemdDir)) {
|
|
1176
1455
|
fs2.mkdirSync(systemdDir, { recursive: true });
|
|
@@ -1182,7 +1461,7 @@ async function installLinux() {
|
|
|
1182
1461
|
await execAsync(`systemctl --user start ${SYSTEMD_SERVICE_NAME}`);
|
|
1183
1462
|
}
|
|
1184
1463
|
async function uninstallLinux() {
|
|
1185
|
-
const systemdDir = path4.join(
|
|
1464
|
+
const systemdDir = path4.join(os4.homedir(), ".config", "systemd", "user");
|
|
1186
1465
|
const servicePath = path4.join(systemdDir, SYSTEMD_SERVICE_NAME);
|
|
1187
1466
|
try {
|
|
1188
1467
|
await execAsync(`systemctl --user stop ${SYSTEMD_SERVICE_NAME}`);
|
|
@@ -1206,7 +1485,7 @@ function registerInstallCommand(program2) {
|
|
|
1206
1485
|
process.exit(1);
|
|
1207
1486
|
}
|
|
1208
1487
|
spinner.text = "Installing startup agent...";
|
|
1209
|
-
const platform =
|
|
1488
|
+
const platform = os4.platform();
|
|
1210
1489
|
if (platform === "win32") {
|
|
1211
1490
|
await installWindows();
|
|
1212
1491
|
} else if (platform === "darwin") {
|
|
@@ -1242,7 +1521,7 @@ function registerUninstallCommand(program2) {
|
|
|
1242
1521
|
program2.command("uninstall").description("Remove stint agent from system startup").action(async () => {
|
|
1243
1522
|
const spinner = ora10("Removing startup agent...").start();
|
|
1244
1523
|
try {
|
|
1245
|
-
const platform =
|
|
1524
|
+
const platform = os4.platform();
|
|
1246
1525
|
if (platform === "win32") {
|
|
1247
1526
|
await uninstallWindows();
|
|
1248
1527
|
} else if (platform === "darwin") {
|
|
@@ -1271,14 +1550,68 @@ import ora11 from "ora";
|
|
|
1271
1550
|
import chalk11 from "chalk";
|
|
1272
1551
|
import { exec as exec2 } from "child_process";
|
|
1273
1552
|
import { promisify as promisify2 } from "util";
|
|
1553
|
+
import { join } from "path";
|
|
1554
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1555
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1556
|
+
import { dirname as dirname2 } from "path";
|
|
1274
1557
|
var execAsync2 = promisify2(exec2);
|
|
1558
|
+
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
1559
|
+
function getChannelConfig() {
|
|
1560
|
+
try {
|
|
1561
|
+
const packagePath = join(__dirname2, "..", "..", "package.json");
|
|
1562
|
+
const packageJson = JSON.parse(readFileSync2(packagePath, "utf-8"));
|
|
1563
|
+
return packageJson.stint;
|
|
1564
|
+
} catch {
|
|
1565
|
+
return {
|
|
1566
|
+
channels: {
|
|
1567
|
+
stable: {
|
|
1568
|
+
pattern: "^\\d+\\.\\d+\\.\\d+$",
|
|
1569
|
+
description: "Production-ready releases"
|
|
1570
|
+
}
|
|
1571
|
+
},
|
|
1572
|
+
defaultChannel: "stable"
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
function getVersionPattern(channel) {
|
|
1577
|
+
const config2 = getChannelConfig();
|
|
1578
|
+
return config2.channels[channel]?.pattern || config2.channels[config2.defaultChannel].pattern;
|
|
1579
|
+
}
|
|
1580
|
+
async function getLatestVersionForChannel(channel) {
|
|
1581
|
+
const pattern = getVersionPattern(channel);
|
|
1582
|
+
const regex = new RegExp(pattern);
|
|
1583
|
+
const { stdout } = await execAsync2("npm view @gowelle/stint-agent versions --json");
|
|
1584
|
+
const versions = JSON.parse(stdout);
|
|
1585
|
+
const channelVersions = versions.filter((v) => regex.test(v)).sort((a, b) => {
|
|
1586
|
+
const [aMajor, aMinor, aPatch] = a.split(".").map((part) => parseInt(part.split("-")[0]));
|
|
1587
|
+
const [bMajor, bMinor, bPatch] = b.split(".").map((part) => parseInt(part.split("-")[0]));
|
|
1588
|
+
if (aMajor !== bMajor) return bMajor - aMajor;
|
|
1589
|
+
if (aMinor !== bMinor) return bMinor - aMinor;
|
|
1590
|
+
return bPatch - aPatch;
|
|
1591
|
+
});
|
|
1592
|
+
if (channelVersions.length === 0) {
|
|
1593
|
+
throw new Error(`No versions found for channel: ${channel}`);
|
|
1594
|
+
}
|
|
1595
|
+
return channelVersions[0];
|
|
1596
|
+
}
|
|
1275
1597
|
function registerUpdateCommand(program2) {
|
|
1276
|
-
program2.command("update").description("Update stint agent to the latest version").action(async () => {
|
|
1598
|
+
program2.command("update").description("Update stint agent to the latest version").option("-c, --channel <channel>", "Release channel (stable, beta, nightly)").action(async (command) => {
|
|
1277
1599
|
const spinner = ora11("Checking for updates...").start();
|
|
1278
1600
|
try {
|
|
1279
1601
|
const currentVersion = program2.version();
|
|
1280
|
-
const
|
|
1281
|
-
const
|
|
1602
|
+
const config2 = getChannelConfig();
|
|
1603
|
+
const channel = (command.opts().channel || config2.defaultChannel).toLowerCase();
|
|
1604
|
+
if (!config2.channels[channel]) {
|
|
1605
|
+
spinner.fail(`Invalid channel: ${channel}`);
|
|
1606
|
+
console.log(chalk11.gray("\nAvailable channels:"));
|
|
1607
|
+
Object.entries(config2.channels).forEach(([name, info]) => {
|
|
1608
|
+
console.log(chalk11.gray(` ${name.padEnd(10)} ${info.description}`));
|
|
1609
|
+
});
|
|
1610
|
+
console.log();
|
|
1611
|
+
process.exit(1);
|
|
1612
|
+
}
|
|
1613
|
+
spinner.text = `Checking for updates in ${channel} channel...`;
|
|
1614
|
+
const cleanLatestVersion = await getLatestVersionForChannel(channel);
|
|
1282
1615
|
if (currentVersion === cleanLatestVersion) {
|
|
1283
1616
|
spinner.succeed("Already up to date");
|
|
1284
1617
|
console.log(chalk11.gray(`
|
|
@@ -1289,7 +1622,7 @@ Current version: ${currentVersion}`));
|
|
|
1289
1622
|
}
|
|
1290
1623
|
spinner.info(`Update available: ${currentVersion} \u2192 ${cleanLatestVersion}`);
|
|
1291
1624
|
spinner.text = "Installing update...";
|
|
1292
|
-
await execAsync2(
|
|
1625
|
+
await execAsync2(`npm install -g @gowelle/stint-agent@${cleanLatestVersion}`);
|
|
1293
1626
|
spinner.succeed(`Updated to version ${cleanLatestVersion}`);
|
|
1294
1627
|
const { valid, pid } = validatePidFile();
|
|
1295
1628
|
if (valid && pid) {
|
|
@@ -1327,20 +1660,193 @@ Current version: ${currentVersion}`));
|
|
|
1327
1660
|
});
|
|
1328
1661
|
}
|
|
1329
1662
|
|
|
1663
|
+
// src/commands/doctor.ts
|
|
1664
|
+
import ora12 from "ora";
|
|
1665
|
+
import chalk12 from "chalk";
|
|
1666
|
+
import { exec as exec3 } from "child_process";
|
|
1667
|
+
import { promisify as promisify3 } from "util";
|
|
1668
|
+
import process7 from "process";
|
|
1669
|
+
var execAsync3 = promisify3(exec3);
|
|
1670
|
+
function registerDoctorCommand(program2) {
|
|
1671
|
+
program2.command("doctor").description("Run diagnostics to check environment health").action(async () => {
|
|
1672
|
+
const spinner = ora12("Running diagnostics...").start();
|
|
1673
|
+
let hasErrors = false;
|
|
1674
|
+
try {
|
|
1675
|
+
const checks = [
|
|
1676
|
+
{
|
|
1677
|
+
name: "Git Installation",
|
|
1678
|
+
check: async () => {
|
|
1679
|
+
try {
|
|
1680
|
+
const { stdout } = await execAsync3("git --version");
|
|
1681
|
+
return {
|
|
1682
|
+
success: true,
|
|
1683
|
+
message: `Git ${stdout.trim()} found`
|
|
1684
|
+
};
|
|
1685
|
+
} catch {
|
|
1686
|
+
return {
|
|
1687
|
+
success: false,
|
|
1688
|
+
message: "Git not found in PATH",
|
|
1689
|
+
details: [
|
|
1690
|
+
"Please install Git from https://git-scm.com",
|
|
1691
|
+
"Ensure git is added to your system PATH"
|
|
1692
|
+
]
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
},
|
|
1697
|
+
{
|
|
1698
|
+
name: "Git Configuration",
|
|
1699
|
+
check: async () => {
|
|
1700
|
+
try {
|
|
1701
|
+
const { stdout: userName } = await execAsync3("git config --global user.name");
|
|
1702
|
+
const { stdout: userEmail } = await execAsync3("git config --global user.email");
|
|
1703
|
+
if (!userName.trim() || !userEmail.trim()) {
|
|
1704
|
+
return {
|
|
1705
|
+
success: false,
|
|
1706
|
+
message: "Git user configuration missing",
|
|
1707
|
+
details: [
|
|
1708
|
+
'Run: git config --global user.name "Your Name"',
|
|
1709
|
+
'Run: git config --global user.email "your@email.com"'
|
|
1710
|
+
]
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
return {
|
|
1714
|
+
success: true,
|
|
1715
|
+
message: `Git configured for ${userName.trim()} <${userEmail.trim()}>`
|
|
1716
|
+
};
|
|
1717
|
+
} catch {
|
|
1718
|
+
return {
|
|
1719
|
+
success: false,
|
|
1720
|
+
message: "Failed to read Git configuration"
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
},
|
|
1725
|
+
{
|
|
1726
|
+
name: "Authentication",
|
|
1727
|
+
check: async () => {
|
|
1728
|
+
try {
|
|
1729
|
+
const user = await authService.validateToken();
|
|
1730
|
+
if (!user) {
|
|
1731
|
+
return {
|
|
1732
|
+
success: false,
|
|
1733
|
+
message: "Not authenticated",
|
|
1734
|
+
details: ['Run "stint login" to authenticate']
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
return {
|
|
1738
|
+
success: true,
|
|
1739
|
+
message: `Authenticated as ${user.email}`
|
|
1740
|
+
};
|
|
1741
|
+
} catch {
|
|
1742
|
+
return {
|
|
1743
|
+
success: false,
|
|
1744
|
+
message: "Authentication validation failed",
|
|
1745
|
+
details: ['Run "stint login" to re-authenticate']
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
},
|
|
1750
|
+
{
|
|
1751
|
+
name: "API Connectivity",
|
|
1752
|
+
check: async () => {
|
|
1753
|
+
try {
|
|
1754
|
+
await apiService.ping();
|
|
1755
|
+
return {
|
|
1756
|
+
success: true,
|
|
1757
|
+
message: "API connection successful"
|
|
1758
|
+
};
|
|
1759
|
+
} catch (error) {
|
|
1760
|
+
return {
|
|
1761
|
+
success: false,
|
|
1762
|
+
message: "API connection failed",
|
|
1763
|
+
details: [error.message]
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
},
|
|
1768
|
+
{
|
|
1769
|
+
name: "WebSocket Connectivity",
|
|
1770
|
+
check: async () => {
|
|
1771
|
+
try {
|
|
1772
|
+
await websocketService.connect();
|
|
1773
|
+
const isConnected = websocketService.isConnected();
|
|
1774
|
+
websocketService.disconnect();
|
|
1775
|
+
if (!isConnected) {
|
|
1776
|
+
return {
|
|
1777
|
+
success: false,
|
|
1778
|
+
message: "WebSocket connection failed",
|
|
1779
|
+
details: ["Connection established but not ready"]
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
return {
|
|
1783
|
+
success: true,
|
|
1784
|
+
message: "WebSocket connection successful"
|
|
1785
|
+
};
|
|
1786
|
+
} catch (error) {
|
|
1787
|
+
return {
|
|
1788
|
+
success: false,
|
|
1789
|
+
message: "WebSocket connection failed",
|
|
1790
|
+
details: [error.message]
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
];
|
|
1796
|
+
console.log(chalk12.blue("\n\u{1F50D} Running environment diagnostics...\n"));
|
|
1797
|
+
for (const check of checks) {
|
|
1798
|
+
spinner.text = `Checking ${check.name.toLowerCase()}...`;
|
|
1799
|
+
try {
|
|
1800
|
+
const result = await check.check();
|
|
1801
|
+
if (result.success) {
|
|
1802
|
+
console.log(`${chalk12.green("\u2713")} ${chalk12.bold(check.name)}: ${result.message}`);
|
|
1803
|
+
} else {
|
|
1804
|
+
hasErrors = true;
|
|
1805
|
+
console.log(`${chalk12.red("\u2716")} ${chalk12.bold(check.name)}: ${result.message}`);
|
|
1806
|
+
if (result.details) {
|
|
1807
|
+
result.details.forEach((detail) => {
|
|
1808
|
+
console.log(chalk12.gray(` ${detail}`));
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
} catch (error) {
|
|
1813
|
+
hasErrors = true;
|
|
1814
|
+
console.log(`${chalk12.red("\u2716")} ${chalk12.bold(check.name)}: Check failed - ${error.message}`);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
spinner.stop();
|
|
1818
|
+
console.log();
|
|
1819
|
+
if (hasErrors) {
|
|
1820
|
+
console.log(chalk12.yellow("Some checks failed. Please address the issues above."));
|
|
1821
|
+
process7.exit(1);
|
|
1822
|
+
} else {
|
|
1823
|
+
console.log(chalk12.green("All checks passed! Your environment is healthy."));
|
|
1824
|
+
}
|
|
1825
|
+
} catch (error) {
|
|
1826
|
+
spinner.fail("Diagnostics failed");
|
|
1827
|
+
logger.error("doctor", "Diagnostics failed", error);
|
|
1828
|
+
console.error(chalk12.red(`
|
|
1829
|
+
\u2716 Error: ${error.message}
|
|
1830
|
+
`));
|
|
1831
|
+
process7.exit(1);
|
|
1832
|
+
}
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1330
1836
|
// src/index.ts
|
|
1331
|
-
var AGENT_VERSION = "1.
|
|
1837
|
+
var AGENT_VERSION = "1.2.0";
|
|
1332
1838
|
var program = new Command();
|
|
1333
1839
|
program.name("stint").description("Stint Agent - Local daemon for Stint Project Assistant").version(AGENT_VERSION, "-v, --version", "output the current version").addHelpText("after", `
|
|
1334
|
-
${
|
|
1335
|
-
${
|
|
1336
|
-
${
|
|
1337
|
-
${
|
|
1338
|
-
${
|
|
1339
|
-
${
|
|
1340
|
-
${
|
|
1840
|
+
${chalk13.bold("Examples:")}
|
|
1841
|
+
${chalk13.cyan("$")} stint login ${chalk13.gray("# Authenticate with Stint")}
|
|
1842
|
+
${chalk13.cyan("$")} stint install ${chalk13.gray("# Install agent to run on startup")}
|
|
1843
|
+
${chalk13.cyan("$")} stint link ${chalk13.gray("# Link current directory to a project")}
|
|
1844
|
+
${chalk13.cyan("$")} stint daemon start ${chalk13.gray("# Start background daemon")}
|
|
1845
|
+
${chalk13.cyan("$")} stint status ${chalk13.gray("# Check status")}
|
|
1846
|
+
${chalk13.cyan("$")} stint commits ${chalk13.gray("# List pending commits")}
|
|
1341
1847
|
|
|
1342
|
-
${
|
|
1343
|
-
For more information, visit: ${
|
|
1848
|
+
${chalk13.bold("Documentation:")}
|
|
1849
|
+
For more information, visit: ${chalk13.blue("https://stint.codes/docs")}
|
|
1344
1850
|
`);
|
|
1345
1851
|
registerLoginCommand(program);
|
|
1346
1852
|
registerLogoutCommand(program);
|
|
@@ -1354,6 +1860,7 @@ registerCommitCommands(program);
|
|
|
1354
1860
|
registerInstallCommand(program);
|
|
1355
1861
|
registerUninstallCommand(program);
|
|
1356
1862
|
registerUpdateCommand(program);
|
|
1863
|
+
registerDoctorCommand(program);
|
|
1357
1864
|
program.exitOverride();
|
|
1358
1865
|
try {
|
|
1359
1866
|
await program.parseAsync(process.argv);
|
|
@@ -1361,7 +1868,7 @@ try {
|
|
|
1361
1868
|
const commanderError = error;
|
|
1362
1869
|
if (commanderError.code !== "commander.help" && commanderError.code !== "commander.version" && commanderError.code !== "commander.helpDisplayed") {
|
|
1363
1870
|
logger.error("cli", "Command execution failed", error);
|
|
1364
|
-
console.error(
|
|
1871
|
+
console.error(chalk13.red(`
|
|
1365
1872
|
\u2716 Error: ${error.message}
|
|
1366
1873
|
`));
|
|
1367
1874
|
process.exit(1);
|