@gowelle/stint-agent 1.0.3 → 1.0.4

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 CHANGED
@@ -62,11 +62,13 @@ stint daemon status
62
62
  | `stint logout` | Remove stored credentials |
63
63
  | `stint whoami` | Show current user and machine information |
64
64
 
65
- ### Daemon
65
+ ### Daemon Lifecycle
66
66
 
67
67
  | Command | Description |
68
68
  |---------|-------------|
69
- | `stint daemon start` | Start background daemon |
69
+ | `stint install` | Register daemon to run on system startup (Login required) |
70
+ | `stint uninstall` | Remove daemon from system startup |
71
+ | `stint daemon start` | Start background daemon manually |
70
72
  | `stint daemon stop` | Stop daemon gracefully |
71
73
  | `stint daemon status` | Check if daemon is running |
72
74
  | `stint daemon logs [--lines N]` | View daemon logs (default: 50 lines) |
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  apiService
3
- } from "./chunk-NMOUYAW2.js";
3
+ } from "./chunk-XZCWE3IA.js";
4
4
  export {
5
5
  apiService
6
6
  };
@@ -2,7 +2,7 @@ import {
2
2
  apiService,
3
3
  config,
4
4
  logger
5
- } from "./chunk-NMOUYAW2.js";
5
+ } from "./chunk-XZCWE3IA.js";
6
6
 
7
7
  // src/utils/process.ts
8
8
  import fs from "fs";
@@ -121,6 +121,20 @@ var GitServiceImpl = class {
121
121
  const branches = branchSummary.all;
122
122
  const remotes = await git.getRemotes(true);
123
123
  const remoteUrl = remotes.length > 0 ? remotes[0].refs.fetch : null;
124
+ let defaultBranch = currentBranch;
125
+ try {
126
+ const result = await git.raw(["symbolic-ref", "refs/remotes/origin/HEAD"]);
127
+ const match = result.trim().match(/refs\/remotes\/origin\/(.+)/);
128
+ if (match) {
129
+ defaultBranch = match[1];
130
+ }
131
+ } catch {
132
+ if (branches.includes("main")) {
133
+ defaultBranch = "main";
134
+ } else if (branches.includes("master")) {
135
+ defaultBranch = "master";
136
+ }
137
+ }
124
138
  const status = await this.getStatus(path3);
125
139
  const log = await git.log({ maxCount: 1 });
126
140
  const lastCommit = log.latest;
@@ -128,7 +142,9 @@ var GitServiceImpl = class {
128
142
  throw new Error("No commits found in repository");
129
143
  }
130
144
  return {
145
+ repoPath: path3,
131
146
  currentBranch,
147
+ defaultBranch,
132
148
  branches,
133
149
  remoteUrl,
134
150
  status,
@@ -254,7 +254,7 @@ var AuthServiceImpl = class {
254
254
  return null;
255
255
  }
256
256
  try {
257
- const { apiService: apiService2 } = await import("./api-37DCZYLF.js");
257
+ const { apiService: apiService2 } = await import("./api-HRVUUV3V.js");
258
258
  const user = await apiService2.getCurrentUser();
259
259
  logger.info("auth", `Token validated for user: ${user.email}`);
260
260
  return user;
@@ -274,7 +274,7 @@ var AuthServiceImpl = class {
274
274
  var authService = new AuthServiceImpl();
275
275
 
276
276
  // src/services/api.ts
277
- var AGENT_VERSION = "1.0.3";
277
+ var AGENT_VERSION = "1.0.4";
278
278
  var ApiServiceImpl = class {
279
279
  sessionId = null;
280
280
  async getHeaders() {
@@ -432,9 +432,15 @@ var ApiServiceImpl = class {
432
432
  async syncProject(projectId, data) {
433
433
  logger.info("api", `Syncing project ${projectId}`);
434
434
  await this.withRetry(async () => {
435
+ const payload = {
436
+ repo_path: data.repoPath,
437
+ remote_url: data.remoteUrl,
438
+ default_branch: data.defaultBranch,
439
+ current_branch: data.currentBranch
440
+ };
435
441
  await this.request(`/api/agent/projects/${projectId}/sync`, {
436
442
  method: "POST",
437
- body: JSON.stringify(data)
443
+ body: JSON.stringify(payload)
438
444
  });
439
445
  logger.success("api", `Project ${projectId} synced`);
440
446
  }, "Sync project");
@@ -5,13 +5,13 @@ import {
5
5
  projectService,
6
6
  removePidFile,
7
7
  writePidFile
8
- } from "../chunk-EVWSJ3RU.js";
8
+ } from "../chunk-UGODXDEE.js";
9
9
  import {
10
10
  apiService,
11
11
  authService,
12
12
  config,
13
13
  logger
14
- } from "../chunk-NMOUYAW2.js";
14
+ } from "../chunk-XZCWE3IA.js";
15
15
 
16
16
  // src/daemon/runner.ts
17
17
  import "dotenv/config";
@@ -457,6 +457,10 @@ async function startDaemon() {
457
457
  try {
458
458
  const user = await authService.validateToken();
459
459
  if (!user) {
460
+ notify({
461
+ title: "Stint Agent",
462
+ message: 'Authentication expired. Please run "stint login" to reconnect.'
463
+ });
460
464
  throw new Error('Not authenticated. Please run "stint login" first.');
461
465
  }
462
466
  logger.info("daemon", `Authenticated as ${user.email}`);
package/dist/index.js CHANGED
@@ -8,18 +8,18 @@ import {
8
8
  projectService,
9
9
  spawnDetached,
10
10
  validatePidFile
11
- } from "./chunk-EVWSJ3RU.js";
11
+ } from "./chunk-UGODXDEE.js";
12
12
  import {
13
13
  apiService,
14
14
  authService,
15
15
  config,
16
16
  logger
17
- } from "./chunk-NMOUYAW2.js";
17
+ } from "./chunk-XZCWE3IA.js";
18
18
 
19
19
  // src/index.ts
20
20
  import "dotenv/config";
21
21
  import { Command } from "commander";
22
- import chalk10 from "chalk";
22
+ import chalk11 from "chalk";
23
23
 
24
24
  // src/commands/login.ts
25
25
  import open from "open";
@@ -1061,19 +1061,225 @@ function getTimeAgo(date) {
1061
1061
  return date.toLocaleDateString();
1062
1062
  }
1063
1063
 
1064
+ // src/commands/install.ts
1065
+ import ora10 from "ora";
1066
+ import chalk10 from "chalk";
1067
+ import fs2 from "fs";
1068
+ import path4 from "path";
1069
+ import os3 from "os";
1070
+ import { exec } from "child_process";
1071
+ import { promisify } from "util";
1072
+ var execAsync = promisify(exec);
1073
+ var WINDOWS_TASK_NAME = "StintAgentDaemon";
1074
+ var MAC_PLIST_NAME = "codes.stint.agent.plist";
1075
+ var SYSTEMD_SERVICE_NAME = "stint-agent.service";
1076
+ function getDaemonCommand() {
1077
+ const scriptPath = process.argv[1];
1078
+ return `"${process.execPath}" "${scriptPath}" daemon start`;
1079
+ }
1080
+ async function installWindows() {
1081
+ const command = getDaemonCommand();
1082
+ const escapedCommand = command.replace(/"/g, '\\"');
1083
+ try {
1084
+ await execAsync(`schtasks /Create /SC ONLOGON /TN "${WINDOWS_TASK_NAME}" /TR "${escapedCommand}" /F`);
1085
+ } catch (error) {
1086
+ if (error.message.includes("Access is denied")) {
1087
+ throw new Error("Access denied. Please run this command as Administrator (Right-click Terminal > Run as administrator).");
1088
+ }
1089
+ throw error;
1090
+ }
1091
+ }
1092
+ async function uninstallWindows() {
1093
+ await execAsync(`schtasks /Delete /TN "${WINDOWS_TASK_NAME}" /F`);
1094
+ }
1095
+ function getMacPlistContent() {
1096
+ const scriptPath = process.argv[1];
1097
+ const logPath = path4.join(os3.homedir(), ".config", "stint", "logs", "launchd.log");
1098
+ const errorPath = path4.join(os3.homedir(), ".config", "stint", "logs", "launchd.error.log");
1099
+ const logDir = path4.dirname(logPath);
1100
+ if (!fs2.existsSync(logDir)) {
1101
+ fs2.mkdirSync(logDir, { recursive: true });
1102
+ }
1103
+ return `<?xml version="1.0" encoding="UTF-8"?>
1104
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1105
+ <plist version="1.0">
1106
+ <dict>
1107
+ <key>Label</key>
1108
+ <string>codes.stint.agent</string>
1109
+ <key>ProgramArguments</key>
1110
+ <array>
1111
+ <string>${process.execPath}</string>
1112
+ <string>${scriptPath}</string>
1113
+ <string>daemon</string>
1114
+ <string>start</string>
1115
+ </array>
1116
+ <key>RunAtLoad</key>
1117
+ <true/>
1118
+ <key>StandardOutPath</key>
1119
+ <string>${logPath}</string>
1120
+ <key>StandardErrorPath</key>
1121
+ <string>${errorPath}</string>
1122
+ <key>EnvironmentVariables</key>
1123
+ <dict>
1124
+ <key>PATH</key>
1125
+ <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
1126
+ </dict>
1127
+ </dict>
1128
+ </plist>`;
1129
+ }
1130
+ async function installMac() {
1131
+ const plistContent = getMacPlistContent();
1132
+ const launchAgentsDir = path4.join(os3.homedir(), "Library", "LaunchAgents");
1133
+ const plistPath = path4.join(launchAgentsDir, MAC_PLIST_NAME);
1134
+ if (!fs2.existsSync(launchAgentsDir)) {
1135
+ fs2.mkdirSync(launchAgentsDir, { recursive: true });
1136
+ }
1137
+ fs2.writeFileSync(plistPath, plistContent);
1138
+ try {
1139
+ await execAsync(`launchctl unload "${plistPath}"`);
1140
+ } catch {
1141
+ }
1142
+ await execAsync(`launchctl load "${plistPath}"`);
1143
+ }
1144
+ async function uninstallMac() {
1145
+ const launchAgentsDir = path4.join(os3.homedir(), "Library", "LaunchAgents");
1146
+ const plistPath = path4.join(launchAgentsDir, MAC_PLIST_NAME);
1147
+ if (fs2.existsSync(plistPath)) {
1148
+ try {
1149
+ await execAsync(`launchctl unload "${plistPath}"`);
1150
+ } catch {
1151
+ }
1152
+ fs2.unlinkSync(plistPath);
1153
+ }
1154
+ }
1155
+ function getSystemdServiceContent() {
1156
+ const scriptPath = process.argv[1];
1157
+ return `[Unit]
1158
+ Description=Stint Agent (Project Assistant)
1159
+ After=network.target
1160
+
1161
+ [Service]
1162
+ Type=forking
1163
+ ExecStart=${process.execPath} "${scriptPath}" daemon start
1164
+ Restart=on-failure
1165
+ RestartSec=5
1166
+ StandardOutput=journal
1167
+ StandardError=journal
1168
+
1169
+ [Install]
1170
+ WantedBy=default.target`;
1171
+ }
1172
+ async function installLinux() {
1173
+ const systemdDir = path4.join(os3.homedir(), ".config", "systemd", "user");
1174
+ const servicePath = path4.join(systemdDir, SYSTEMD_SERVICE_NAME);
1175
+ if (!fs2.existsSync(systemdDir)) {
1176
+ fs2.mkdirSync(systemdDir, { recursive: true });
1177
+ }
1178
+ const serviceContent = getSystemdServiceContent();
1179
+ fs2.writeFileSync(servicePath, serviceContent);
1180
+ await execAsync("systemctl --user daemon-reload");
1181
+ await execAsync(`systemctl --user enable ${SYSTEMD_SERVICE_NAME}`);
1182
+ await execAsync(`systemctl --user start ${SYSTEMD_SERVICE_NAME}`);
1183
+ }
1184
+ async function uninstallLinux() {
1185
+ const systemdDir = path4.join(os3.homedir(), ".config", "systemd", "user");
1186
+ const servicePath = path4.join(systemdDir, SYSTEMD_SERVICE_NAME);
1187
+ try {
1188
+ await execAsync(`systemctl --user stop ${SYSTEMD_SERVICE_NAME}`);
1189
+ await execAsync(`systemctl --user disable ${SYSTEMD_SERVICE_NAME}`);
1190
+ } catch {
1191
+ }
1192
+ if (fs2.existsSync(servicePath)) {
1193
+ fs2.unlinkSync(servicePath);
1194
+ await execAsync("systemctl --user daemon-reload");
1195
+ }
1196
+ }
1197
+ function registerInstallCommand(program2) {
1198
+ program2.command("install").description("Install stint agent to run on system startup").action(async () => {
1199
+ const spinner = ora10("Checking authentication...").start();
1200
+ try {
1201
+ const user = await authService.validateToken();
1202
+ if (!user) {
1203
+ spinner.fail("Not authenticated");
1204
+ console.log(chalk10.red("\n\u2716 You must be logged in to install the background agent on startup."));
1205
+ console.log(chalk10.gray('Run "stint login" first.\n'));
1206
+ process.exit(1);
1207
+ }
1208
+ spinner.text = "Installing startup agent...";
1209
+ const platform = os3.platform();
1210
+ if (platform === "win32") {
1211
+ await installWindows();
1212
+ } else if (platform === "darwin") {
1213
+ await installMac();
1214
+ } else if (platform === "linux") {
1215
+ await installLinux();
1216
+ } else {
1217
+ throw new Error(`Unsupported platform: ${platform}`);
1218
+ }
1219
+ spinner.succeed("Installed successfully!");
1220
+ console.log(chalk10.green(`
1221
+ \u2713 Stint agent configured to start on login`));
1222
+ if (platform === "win32") {
1223
+ console.log(chalk10.gray(`Registered Task Scheduler task: ${WINDOWS_TASK_NAME}`));
1224
+ } else if (platform === "darwin") {
1225
+ console.log(chalk10.gray(`Created LaunchAgent: ~/Library/LaunchAgents/${MAC_PLIST_NAME}`));
1226
+ } else if (platform === "linux") {
1227
+ console.log(chalk10.gray(`Created systemd user service: ${SYSTEMD_SERVICE_NAME}`));
1228
+ }
1229
+ console.log();
1230
+ logger.success("install", `Agent installed on startup for ${platform}`);
1231
+ } catch (error) {
1232
+ spinner.fail("Installation failed");
1233
+ logger.error("install", "Install command failed", error);
1234
+ console.error(chalk10.red(`
1235
+ \u2716 Error: ${error.message}
1236
+ `));
1237
+ process.exit(1);
1238
+ }
1239
+ });
1240
+ }
1241
+ function registerUninstallCommand(program2) {
1242
+ program2.command("uninstall").description("Remove stint agent from system startup").action(async () => {
1243
+ const spinner = ora10("Removing startup agent...").start();
1244
+ try {
1245
+ const platform = os3.platform();
1246
+ if (platform === "win32") {
1247
+ await uninstallWindows();
1248
+ } else if (platform === "darwin") {
1249
+ await uninstallMac();
1250
+ } else if (platform === "linux") {
1251
+ await uninstallLinux();
1252
+ } else {
1253
+ throw new Error(`Unsupported platform: ${platform}`);
1254
+ }
1255
+ spinner.succeed("Uninstalled successfully");
1256
+ console.log(chalk10.gray("\nStint agent removed from system startup.\n"));
1257
+ logger.success("install", `Agent uninstalled from startup for ${platform}`);
1258
+ } catch (error) {
1259
+ spinner.fail("Uninstall failed");
1260
+ logger.error("install", "Uninstall command failed", error);
1261
+ console.error(chalk10.red(`
1262
+ \u2716 Error: ${error.message}
1263
+ `));
1264
+ process.exit(1);
1265
+ }
1266
+ });
1267
+ }
1268
+
1064
1269
  // src/index.ts
1065
- var AGENT_VERSION = "1.0.3";
1270
+ var AGENT_VERSION = "1.0.4";
1066
1271
  var program = new Command();
1067
1272
  program.name("stint").description("Stint Agent - Local daemon for Stint Project Assistant").version(AGENT_VERSION, "-V, --version", "output the current version").addHelpText("after", `
1068
- ${chalk10.bold("Examples:")}
1069
- ${chalk10.cyan("$")} stint login ${chalk10.gray("# Authenticate with Stint")}
1070
- ${chalk10.cyan("$")} stint link ${chalk10.gray("# Link current directory to a project")}
1071
- ${chalk10.cyan("$")} stint daemon start ${chalk10.gray("# Start background daemon")}
1072
- ${chalk10.cyan("$")} stint status ${chalk10.gray("# Check status")}
1073
- ${chalk10.cyan("$")} stint commits ${chalk10.gray("# List pending commits")}
1273
+ ${chalk11.bold("Examples:")}
1274
+ ${chalk11.cyan("$")} stint login ${chalk11.gray("# Authenticate with Stint")}
1275
+ ${chalk11.cyan("$")} stint install ${chalk11.gray("# Install agent to run on startup")}
1276
+ ${chalk11.cyan("$")} stint link ${chalk11.gray("# Link current directory to a project")}
1277
+ ${chalk11.cyan("$")} stint daemon start ${chalk11.gray("# Start background daemon")}
1278
+ ${chalk11.cyan("$")} stint status ${chalk11.gray("# Check status")}
1279
+ ${chalk11.cyan("$")} stint commits ${chalk11.gray("# List pending commits")}
1074
1280
 
1075
- ${chalk10.bold("Documentation:")}
1076
- For more information, visit: ${chalk10.blue("https://stint.codes/docs")}
1281
+ ${chalk11.bold("Documentation:")}
1282
+ For more information, visit: ${chalk11.blue("https://stint.codes/docs")}
1077
1283
  `);
1078
1284
  registerLoginCommand(program);
1079
1285
  registerLogoutCommand(program);
@@ -1084,6 +1290,8 @@ registerStatusCommand(program);
1084
1290
  registerSyncCommand(program);
1085
1291
  registerDaemonCommands(program);
1086
1292
  registerCommitCommands(program);
1293
+ registerInstallCommand(program);
1294
+ registerUninstallCommand(program);
1087
1295
  program.exitOverride();
1088
1296
  try {
1089
1297
  await program.parseAsync(process.argv);
@@ -1091,7 +1299,7 @@ try {
1091
1299
  const commanderError = error;
1092
1300
  if (commanderError.code !== "commander.help" && commanderError.code !== "commander.version") {
1093
1301
  logger.error("cli", "Command execution failed", error);
1094
- console.error(chalk10.red(`
1302
+ console.error(chalk11.red(`
1095
1303
  \u2716 Error: ${error.message}
1096
1304
  `));
1097
1305
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gowelle/stint-agent",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Local agent for Stint - Project Assistant",
5
5
  "author": "Gowelle John <gowelle.john@icloud.com>",
6
6
  "license": "MIT",
@@ -62,4 +62,4 @@
62
62
  "engines": {
63
63
  "node": ">=20.0.0"
64
64
  }
65
- }
65
+ }