@gowelle/stint-agent 1.0.1 → 1.0.2

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  apiService
3
- } from "./chunk-CCM25AHM.js";
3
+ } from "./chunk-5XNQJJVE.js";
4
4
  export {
5
5
  apiService
6
6
  };
@@ -254,7 +254,7 @@ var AuthServiceImpl = class {
254
254
  return null;
255
255
  }
256
256
  try {
257
- const { apiService: apiService2 } = await import("./api-GH4C2AFN.js");
257
+ const { apiService: apiService2 } = await import("./api-ZMYJOXXU.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.1";
277
+ var AGENT_VERSION = "1.0.2";
278
278
  var ApiServiceImpl = class {
279
279
  sessionId = null;
280
280
  async getHeaders() {
@@ -381,22 +381,40 @@ var ApiServiceImpl = class {
381
381
  }
382
382
  async getPendingCommits(projectId) {
383
383
  logger.info("api", `Fetching pending commits for project ${projectId}`);
384
- const commits = await this.request(
385
- `/api/agent/projects/${projectId}/pending-commits`
384
+ const response = await this.request(
385
+ `/api/agent/pending-commits?project_id=${projectId}`
386
386
  );
387
+ const commits = response.data.map((item) => ({
388
+ id: item.id,
389
+ projectId: item.project_id || item.projectId,
390
+ message: item.message,
391
+ files: item.files,
392
+ createdAt: item.created_at || item.createdAt
393
+ }));
387
394
  logger.info("api", `Found ${commits.length} pending commits`);
388
395
  return commits;
389
396
  }
390
397
  async markCommitExecuted(commitId, sha) {
391
398
  logger.info("api", `Marking commit ${commitId} as executed (SHA: ${sha})`);
392
399
  return this.withRetry(async () => {
393
- const commit = await this.request(
400
+ const response = await this.request(
394
401
  `/api/agent/commits/${commitId}/executed`,
395
402
  {
396
403
  method: "POST",
397
404
  body: JSON.stringify({ sha })
398
405
  }
399
406
  );
407
+ const data = response.data;
408
+ const commit = {
409
+ id: data.id,
410
+ projectId: data.project_id || data.projectId,
411
+ message: data.message,
412
+ sha: data.sha,
413
+ status: data.status,
414
+ createdAt: data.created_at || data.createdAt,
415
+ executedAt: data.executed_at || data.executedAt,
416
+ error: data.error
417
+ };
400
418
  logger.success("api", `Commit ${commitId} marked as executed`);
401
419
  return commit;
402
420
  }, "Mark commit executed");
@@ -422,7 +440,8 @@ var ApiServiceImpl = class {
422
440
  }
423
441
  async getLinkedProjects() {
424
442
  logger.info("api", "Fetching linked projects");
425
- const projects = await this.request("/api/agent/projects");
443
+ const response = await this.request("/api/agent/projects");
444
+ const projects = response.data;
426
445
  logger.info("api", `Found ${projects.length} linked projects`);
427
446
  return projects;
428
447
  }
@@ -432,6 +451,16 @@ var ApiServiceImpl = class {
432
451
  logger.info("api", `Fetched user: ${user.email}`);
433
452
  return user;
434
453
  }
454
+ async createProject(data) {
455
+ logger.info("api", `Creating project: ${data.name}`);
456
+ const response = await this.request("/api/agent/projects", {
457
+ method: "POST",
458
+ body: JSON.stringify(data)
459
+ });
460
+ const project = response.data;
461
+ logger.success("api", `Created project: ${project.name} (${project.id})`);
462
+ return project;
463
+ }
435
464
  };
436
465
  var apiService = new ApiServiceImpl();
437
466
 
@@ -2,7 +2,7 @@ import {
2
2
  apiService,
3
3
  config,
4
4
  logger
5
- } from "./chunk-CCM25AHM.js";
5
+ } from "./chunk-5XNQJJVE.js";
6
6
 
7
7
  // src/utils/process.ts
8
8
  import fs from "fs";
@@ -322,17 +322,11 @@ var CommitQueueProcessor = class {
322
322
  throw new Error(`Directory ${projectPath} is not a git repository`);
323
323
  }
324
324
  const status = await gitService.getStatus(projectPath);
325
- const hasChanges = status.staged.length > 0 || status.unstaged.length > 0 || status.untracked.length > 0;
326
- if (hasChanges) {
327
- throw new Error("Repository has uncommitted changes. Please commit or stash them first.");
328
- }
329
- if (commit.files && commit.files.length > 0) {
330
- logger.info("queue", `Staging specific files: ${commit.files.join(", ")}`);
331
- await gitService.stageFiles(projectPath, commit.files);
332
- } else {
333
- logger.info("queue", "Staging all changes");
334
- await gitService.stageAll(projectPath);
325
+ const hasStagedChanges = status.staged.length > 0;
326
+ if (!hasStagedChanges) {
327
+ throw new Error('No staged changes to commit. Please stage files using "git add" before committing.');
335
328
  }
329
+ logger.info("queue", `Committing ${status.staged.length} staged files.`);
336
330
  logger.info("queue", `Creating commit with message: "${commit.message}"`);
337
331
  const sha = await gitService.commit(projectPath, commit.message);
338
332
  logger.success("queue", `Commit created successfully: ${sha}`);
@@ -5,13 +5,13 @@ import {
5
5
  projectService,
6
6
  removePidFile,
7
7
  writePidFile
8
- } from "../chunk-TCWMYJPP.js";
8
+ } from "../chunk-WFWZCAJ6.js";
9
9
  import {
10
10
  apiService,
11
11
  authService,
12
12
  config,
13
13
  logger
14
- } from "../chunk-CCM25AHM.js";
14
+ } from "../chunk-5XNQJJVE.js";
15
15
 
16
16
  // src/daemon/runner.ts
17
17
  import "dotenv/config";
package/dist/index.js CHANGED
@@ -8,13 +8,13 @@ import {
8
8
  projectService,
9
9
  spawnDetached,
10
10
  validatePidFile
11
- } from "./chunk-TCWMYJPP.js";
11
+ } from "./chunk-WFWZCAJ6.js";
12
12
  import {
13
13
  apiService,
14
14
  authService,
15
15
  config,
16
16
  logger
17
- } from "./chunk-CCM25AHM.js";
17
+ } from "./chunk-5XNQJJVE.js";
18
18
 
19
19
  // src/index.ts
20
20
  import "dotenv/config";
@@ -44,82 +44,245 @@ function startCallbackServer(expectedState, timeoutMs = 5 * 60 * 1e3) {
44
44
  callbackReject = reject2;
45
45
  });
46
46
  server.on("request", (req, res) => {
47
+ res.setHeader("Access-Control-Allow-Origin", "*");
48
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
49
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-XSRF-TOKEN, X-Requested-With, X-Inertia, X-Inertia-Version");
50
+ res.setHeader("Access-Control-Expose-Headers", "X-Inertia-Location");
51
+ res.setHeader("Access-Control-Allow-Private-Network", "true");
52
+ if (req.method === "OPTIONS") {
53
+ res.setHeader("Access-Control-Allow-Private-Network", "true");
54
+ res.writeHead(200);
55
+ res.end();
56
+ return;
57
+ }
47
58
  if (req.url?.startsWith("/auth/callback")) {
48
59
  try {
49
60
  const url = new URL(req.url, `http://${req.headers.host}`);
50
61
  const token = url.searchParams.get("token");
51
62
  const state = url.searchParams.get("state");
52
63
  const error = url.searchParams.get("error");
64
+ const next = url.searchParams.get("next");
65
+ const accept = req.headers.accept || "";
66
+ const isInertia = req.headers["x-inertia"] === "true";
67
+ const isJsonRequest = accept.includes("application/json") || isInertia;
53
68
  if (error) {
54
69
  const errorDescription = url.searchParams.get("error_description") || error;
55
- const escapedError = escapeHtml(errorDescription);
56
- res.writeHead(400, { "Content-Type": "text/html" });
57
- res.end(`
58
- <html>
59
- <head><title>Authentication Failed</title></head>
60
- <body>
61
- <h1>Authentication Failed</h1>
62
- <p>${escapedError}</p>
63
- <p>You can close this window.</p>
64
- </body>
65
- </html>
66
- `);
70
+ if (isJsonRequest) {
71
+ res.writeHead(400, { "Content-Type": "application/json" });
72
+ res.end(JSON.stringify({
73
+ status: "error",
74
+ error,
75
+ message: errorDescription
76
+ }));
77
+ } else {
78
+ const escapedError = escapeHtml(errorDescription);
79
+ res.writeHead(400, { "Content-Type": "text/html" });
80
+ res.end(`
81
+ <html>
82
+ <head><title>Authentication Failed</title></head>
83
+ <body>
84
+ <h1>Authentication Failed</h1>
85
+ <p>${escapedError}</p>
86
+ <p>You can close this window.</p>
87
+ </body>
88
+ </html>
89
+ `);
90
+ }
67
91
  callbackReject(new Error(`OAuth error: ${errorDescription}`));
68
92
  return;
69
93
  }
70
94
  if (state !== expectedState) {
71
- res.writeHead(400, { "Content-Type": "text/html" });
72
- res.end(`
73
- <html>
74
- <head><title>Authentication Failed</title></head>
75
- <body>
76
- <h1>Authentication Failed</h1>
77
- <p>Invalid state parameter. Security validation failed.</p>
78
- <p>You can close this window.</p>
79
- </body>
80
- </html>
81
- `);
95
+ if (isJsonRequest) {
96
+ res.writeHead(400, { "Content-Type": "application/json" });
97
+ res.end(JSON.stringify({
98
+ status: "error",
99
+ error: "invalid_state",
100
+ message: "State parameter mismatch"
101
+ }));
102
+ } else {
103
+ res.writeHead(400, { "Content-Type": "text/html" });
104
+ res.end(`
105
+ <html>
106
+ <head><title>Authentication Failed</title></head>
107
+ <body>
108
+ <h1>Authentication Failed</h1>
109
+ <p>Invalid state parameter. Security validation failed.</p>
110
+ <p>You can close this window.</p>
111
+ </body>
112
+ </html>
113
+ `);
114
+ }
82
115
  callbackReject(new Error("State parameter mismatch"));
83
116
  return;
84
117
  }
85
118
  if (!token) {
86
- res.writeHead(400, { "Content-Type": "text/html" });
87
- res.end(`
88
- <html>
89
- <head><title>Authentication Failed</title></head>
90
- <body>
91
- <h1>Authentication Failed</h1>
92
- <p>No token provided in callback.</p>
93
- <p>You can close this window.</p>
94
- </body>
95
- </html>
96
- `);
119
+ if (isJsonRequest) {
120
+ res.writeHead(400, { "Content-Type": "application/json" });
121
+ res.end(JSON.stringify({
122
+ status: "error",
123
+ error: "missing_token",
124
+ message: "No token provided"
125
+ }));
126
+ } else {
127
+ res.writeHead(400, { "Content-Type": "text/html" });
128
+ res.end(`
129
+ <html>
130
+ <head><title>Authentication Failed</title></head>
131
+ <body>
132
+ <h1>Authentication Failed</h1>
133
+ <p>No token provided in callback.</p>
134
+ <p>You can close this window.</p>
135
+ </body>
136
+ </html>
137
+ `);
138
+ }
97
139
  callbackReject(new Error("No token in callback"));
98
140
  return;
99
141
  }
100
- res.writeHead(200, { "Content-Type": "text/html" });
101
- res.end(`
142
+ if (isInertia && next) {
143
+ res.writeHead(409, { "X-Inertia-Location": next });
144
+ res.end();
145
+ } else if (isJsonRequest) {
146
+ res.writeHead(200, { "Content-Type": "application/json" });
147
+ res.end(JSON.stringify({
148
+ status: "success",
149
+ message: "Authentication successful",
150
+ user: "Authenticated",
151
+ next: next || void 0
152
+ }));
153
+ } else if (next) {
154
+ res.writeHead(302, { "Location": next });
155
+ res.end();
156
+ } else {
157
+ res.writeHead(200, { "Content-Type": "text/html" });
158
+ res.end(`
159
+ <!DOCTYPE html>
102
160
  <html>
103
- <head><title>Authentication Successful</title></head>
161
+ <head>
162
+ <title>Stint - Authentication Successful</title>
163
+ <style>
164
+ body {
165
+ background-color: #0f172a;
166
+ color: #e2e8f0;
167
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
168
+ display: flex;
169
+ justify-content: center;
170
+ align-items: center;
171
+ height: 100vh;
172
+ margin: 0;
173
+ }
174
+ .container {
175
+ background-color: #1e293b;
176
+ padding: 2.5rem;
177
+ border-radius: 1rem;
178
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
179
+ text-align: center;
180
+ max-width: 28rem;
181
+ width: 100%;
182
+ border: 1px solid #334155;
183
+ }
184
+ h1 {
185
+ color: #38bdf8;
186
+ margin-top: 0;
187
+ font-size: 1.5rem;
188
+ margin-bottom: 1rem;
189
+ }
190
+ p {
191
+ color: #94a3b8;
192
+ line-height: 1.5;
193
+ margin-bottom: 1.5rem;
194
+ }
195
+ .icon {
196
+ color: #22c55e;
197
+ width: 3rem;
198
+ height: 3rem;
199
+ margin-bottom: 1.5rem;
200
+ }
201
+ .btn {
202
+ background-color: #38bdf8;
203
+ color: #0f172a;
204
+ padding: 0.75rem 1.5rem;
205
+ border-radius: 0.5rem;
206
+ text-decoration: none;
207
+ font-weight: 600;
208
+ display: inline-block;
209
+ transition: background-color 0.2s;
210
+ border: none;
211
+ cursor: pointer;
212
+ }
213
+ .btn:hover {
214
+ background-color: #0ea5e9;
215
+ }
216
+ </style>
217
+ </head>
104
218
  <body>
105
- <h1>Authentication Successful!</h1>
106
- <p>You have been successfully authenticated. You can close this window.</p>
219
+ <div class="container">
220
+ <svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
221
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
222
+ </svg>
223
+ <h1>Authentication Successful</h1>
224
+ <p>You have successfully logged in to Stint. You can now close this window and return to your terminal.</p>
225
+ <button onclick="window.close()" class="btn">Close Window</button>
226
+ </div>
227
+ <script>
228
+ // Attempt to close the window automatically after 3 seconds
229
+ setTimeout(function() {
230
+ window.close();
231
+ }, 3000);
232
+ </script>
107
233
  </body>
108
234
  </html>
109
- `);
235
+ `);
236
+ }
110
237
  callbackResolve(token);
111
238
  } catch (error) {
112
- res.writeHead(500, { "Content-Type": "text/html" });
113
- res.end(`
114
- <html>
115
- <head><title>Error</title></head>
116
- <body>
117
- <h1>Error</h1>
118
- <p>An error occurred processing the callback.</p>
119
- <p>You can close this window.</p>
120
- </body>
121
- </html>
122
- `);
239
+ const isJsonRequest = req.headers.accept?.includes("application/json");
240
+ if (isJsonRequest) {
241
+ res.writeHead(500, { "Content-Type": "application/json" });
242
+ res.end(JSON.stringify({
243
+ status: "error",
244
+ message: "Internal server error processing callback"
245
+ }));
246
+ } else {
247
+ res.writeHead(500, { "Content-Type": "text/html" });
248
+ res.end(`
249
+ <!DOCTYPE html>
250
+ <html>
251
+ <head>
252
+ <title>Stint - Error</title>
253
+ <style>
254
+ body {
255
+ background-color: #0f172a;
256
+ color: #e2e8f0;
257
+ font-family: sans-serif;
258
+ display: flex;
259
+ justify-content: center;
260
+ align-items: center;
261
+ height: 100vh;
262
+ margin: 0;
263
+ }
264
+ .container {
265
+ background-color: #1e293b;
266
+ padding: 2rem;
267
+ border-radius: 0.5rem;
268
+ text-align: center;
269
+ max-width: 24rem;
270
+ border: 1px solid #334155;
271
+ }
272
+ h1 { color: #f43f5e; margin-top: 0; }
273
+ p { color: #94a3b8; }
274
+ </style>
275
+ </head>
276
+ <body>
277
+ <div class="container">
278
+ <h1>Authentication Error</h1>
279
+ <p>An error occurred processing the callback.</p>
280
+ <p>Please try logging in again.</p>
281
+ </div>
282
+ </body>
283
+ </html>
284
+ `);
285
+ }
123
286
  callbackReject(error);
124
287
  }
125
288
  } else {
@@ -134,7 +297,7 @@ function startCallbackServer(expectedState, timeoutMs = 5 * 60 * 1e3) {
134
297
  reject(error);
135
298
  }
136
299
  });
137
- server.listen(0, "127.0.0.1", () => {
300
+ server.listen(0, "localhost", () => {
138
301
  const address = server.address();
139
302
  const port = address.port;
140
303
  timeout = setTimeout(() => {
@@ -144,7 +307,7 @@ function startCallbackServer(expectedState, timeoutMs = 5 * 60 * 1e3) {
144
307
  callbackPromise.then(() => clearTimeout(timeout)).catch(() => clearTimeout(timeout)).finally(() => {
145
308
  setTimeout(() => {
146
309
  server.close();
147
- }, 100);
310
+ }, 500);
148
311
  });
149
312
  resolve({
150
313
  server,
@@ -283,9 +446,10 @@ function registerWhoamiCommand(program2) {
283
446
  }
284
447
 
285
448
  // src/commands/link.ts
286
- import { select } from "@inquirer/prompts";
449
+ import { select, input } from "@inquirer/prompts";
287
450
  import ora4 from "ora";
288
451
  import chalk4 from "chalk";
452
+ import path from "path";
289
453
  import process2 from "process";
290
454
  function registerLinkCommand(program2) {
291
455
  program2.command("link").description("Link current directory to a Stint project").action(async () => {
@@ -317,17 +481,59 @@ function registerLinkCommand(program2) {
317
481
  return;
318
482
  }
319
483
  spinner.succeed("Ready to link");
320
- const selectedProjectId = await select({
484
+ const choices = projects.map((project) => ({
485
+ name: project.name,
486
+ value: project.id,
487
+ description: `ID: ${project.id}`
488
+ }));
489
+ const CREATE_NEW_PROJECT = "create-new-project";
490
+ choices.push({
491
+ name: "\u2795 Create new project",
492
+ value: CREATE_NEW_PROJECT,
493
+ description: "Create a new project on Stint"
494
+ });
495
+ const selectedAction = await select({
321
496
  message: "Select a project to link:",
322
- choices: projects.map((project) => ({
323
- name: `${project.name}${project.description ? ` - ${project.description}` : ""}`,
324
- value: project.id,
325
- description: `ID: ${project.id}`
326
- }))
497
+ choices
327
498
  });
499
+ let selectedProjectId = selectedAction;
500
+ let selectedProject = projects.find((p) => p.id === selectedProjectId);
501
+ if (selectedAction === CREATE_NEW_PROJECT) {
502
+ const name = await input({
503
+ message: "Project name:",
504
+ default: path.basename(cwd),
505
+ validate: (input2) => input2.trim().length > 0 || "Project name is required"
506
+ });
507
+ const description = await input({
508
+ message: "Description (optional):"
509
+ });
510
+ const createSpinner = ora4("Creating project...").start();
511
+ try {
512
+ let repoInfo = null;
513
+ if (isRepo) {
514
+ try {
515
+ repoInfo = await gitService.getRepoInfo(cwd);
516
+ } catch (e) {
517
+ logger.warn("link", "Failed to get repo info for creation metadata", e);
518
+ }
519
+ }
520
+ const newProject = await apiService.createProject({
521
+ name,
522
+ description: description || void 0,
523
+ repo_path: cwd,
524
+ remote_url: repoInfo?.remoteUrl || void 0,
525
+ default_branch: repoInfo?.currentBranch || void 0
526
+ });
527
+ createSpinner.succeed("Project created successfully!");
528
+ selectedProjectId = newProject.id;
529
+ selectedProject = newProject;
530
+ } catch (error) {
531
+ createSpinner.fail("Failed to create project");
532
+ throw error;
533
+ }
534
+ }
328
535
  const linkSpinner = ora4("Linking project...").start();
329
536
  await projectService.linkProject(cwd, selectedProjectId);
330
- const selectedProject = projects.find((p) => p.id === selectedProjectId);
331
537
  linkSpinner.succeed("Project linked successfully!");
332
538
  console.log(chalk4.green(`
333
539
  \u2713 Linked to ${chalk4.bold(selectedProject?.name || selectedProjectId)}`));
@@ -400,6 +606,8 @@ Directory ${cwd} is no longer linked.
400
606
  import ora6 from "ora";
401
607
  import chalk6 from "chalk";
402
608
  import process4 from "process";
609
+ import path2 from "path";
610
+ import os from "os";
403
611
  function registerStatusCommand(program2) {
404
612
  program2.command("status").description("Show linked project and connection status").action(async () => {
405
613
  const spinner = ora6("Gathering status...").start();
@@ -459,7 +667,15 @@ function registerStatusCommand(program2) {
459
667
  }
460
668
  console.log(chalk6.blue("\n\u2699\uFE0F Daemon:"));
461
669
  console.log(chalk6.gray("\u2500".repeat(50)));
462
- console.log(`${chalk6.bold("Status:")} ${chalk6.gray("Not running (Phase 3)")}`);
670
+ const { valid, pid } = validatePidFile();
671
+ if (valid && pid) {
672
+ console.log(`${chalk6.bold("Status:")} ${chalk6.green("\u2713 Running")}`);
673
+ console.log(`${chalk6.bold("PID:")} ${pid}`);
674
+ console.log(`${chalk6.bold("Logs:")} ${path2.join(os.homedir(), ".config", "stint", "logs", "daemon.log")}`);
675
+ } else {
676
+ console.log(`${chalk6.bold("Status:")} ${chalk6.yellow("Not running")}`);
677
+ console.log(chalk6.gray('Run "stint daemon start" to start the background agent.'));
678
+ }
463
679
  console.log();
464
680
  logger.info("status", "Status command executed");
465
681
  } catch (error) {
@@ -517,8 +733,8 @@ function registerSyncCommand(program2) {
517
733
  import ora8 from "ora";
518
734
  import chalk8 from "chalk";
519
735
  import fs from "fs";
520
- import path from "path";
521
- import os from "os";
736
+ import path3 from "path";
737
+ import os2 from "os";
522
738
  import { fileURLToPath } from "url";
523
739
  import { dirname } from "path";
524
740
  var __filename = fileURLToPath(import.meta.url);
@@ -544,7 +760,7 @@ function registerDaemonCommands(program2) {
544
760
  console.log(chalk8.gray('Run "stint login" first.\n'));
545
761
  process.exit(1);
546
762
  }
547
- const runnerPath = path.join(__dirname, "daemon", "runner.js");
763
+ const runnerPath = path3.join(__dirname, "daemon", "runner.js");
548
764
  if (!fs.existsSync(runnerPath)) {
549
765
  throw new Error(`Daemon runner not found at ${runnerPath}`);
550
766
  }
@@ -558,7 +774,7 @@ function registerDaemonCommands(program2) {
558
774
  console.log(chalk8.green(`
559
775
  \u2713 Daemon is running in the background`));
560
776
  console.log(chalk8.gray(`PID: ${daemonPid}`));
561
- console.log(chalk8.gray(`Logs: ${path.join(os.homedir(), ".config", "stint", "logs", "daemon.log")}
777
+ console.log(chalk8.gray(`Logs: ${path3.join(os2.homedir(), ".config", "stint", "logs", "daemon.log")}
562
778
  `));
563
779
  logger.success("daemon", `Daemon started with PID ${daemonPid}`);
564
780
  } catch (error) {
@@ -619,7 +835,7 @@ function registerDaemonCommands(program2) {
619
835
  console.log(`${chalk8.bold("Status:")} ${chalk8.green("\u2713 Running")}`);
620
836
  console.log(`${chalk8.bold("PID:")} ${pid}`);
621
837
  console.log(`${chalk8.bold("PID File:")} ${getPidFilePath()}`);
622
- console.log(`${chalk8.bold("Logs:")} ${path.join(os.homedir(), ".config", "stint", "logs", "daemon.log")}`);
838
+ console.log(`${chalk8.bold("Logs:")} ${path3.join(os2.homedir(), ".config", "stint", "logs", "daemon.log")}`);
623
839
  } else {
624
840
  console.log(`${chalk8.bold("Status:")} ${chalk8.yellow("Not running")}`);
625
841
  console.log(chalk8.gray('Run "stint daemon start" to start the daemon.'));
@@ -637,7 +853,7 @@ function registerDaemonCommands(program2) {
637
853
  });
638
854
  daemon.command("logs").description("Tail daemon logs").option("-n, --lines <number>", "Number of lines to show", "50").action(async (options) => {
639
855
  try {
640
- const logFile = path.join(os.homedir(), ".config", "stint", "logs", "daemon.log");
856
+ const logFile = path3.join(os2.homedir(), ".config", "stint", "logs", "daemon.log");
641
857
  if (!fs.existsSync(logFile)) {
642
858
  console.log(chalk8.yellow("\n\u26A0 No daemon logs found."));
643
859
  console.log(chalk8.gray("The daemon has not been started yet.\n"));
@@ -693,7 +909,7 @@ Log file: ${logFile}`));
693
909
  if (!user) {
694
910
  throw new Error("Not authenticated");
695
911
  }
696
- const runnerPath = path.join(__dirname, "daemon", "runner.js");
912
+ const runnerPath = path3.join(__dirname, "daemon", "runner.js");
697
913
  const daemonPid = spawnDetached("node", [runnerPath]);
698
914
  await new Promise((resolve) => setTimeout(resolve, 1e3));
699
915
  if (!isProcessRunning(daemonPid)) {
@@ -714,6 +930,7 @@ Log file: ${logFile}`));
714
930
  // src/commands/commit.ts
715
931
  import ora9 from "ora";
716
932
  import chalk9 from "chalk";
933
+ import { confirm as confirm2 } from "@inquirer/prompts";
717
934
  import process6 from "process";
718
935
  function registerCommitCommands(program2) {
719
936
  program2.command("commits").description("List pending commits for the current project").action(async () => {
@@ -759,7 +976,7 @@ function registerCommitCommands(program2) {
759
976
  }
760
977
  });
761
978
  program2.command("commit <id>").description("Execute a specific pending commit").action(async (id) => {
762
- const spinner = ora9("Executing commit...").start();
979
+ const spinner = ora9("Checking repository status...").start();
763
980
  try {
764
981
  const cwd = process6.cwd();
765
982
  const linkedProject = projectService.getLinkedProject(cwd);
@@ -769,6 +986,14 @@ function registerCommitCommands(program2) {
769
986
  console.log(chalk9.gray('Run "stint link" first to link this directory.\n'));
770
987
  process6.exit(1);
771
988
  }
989
+ const status = await gitService.getStatus(cwd);
990
+ if (status.staged.length === 0) {
991
+ spinner.fail("No staged changes");
992
+ console.log(chalk9.yellow("\n\u26A0 No staged changes detected."));
993
+ console.log(chalk9.gray("Please stage the files you want to commit first."));
994
+ console.log(chalk9.gray(" git add <files>\n"));
995
+ process6.exit(1);
996
+ }
772
997
  spinner.text = "Fetching commit details...";
773
998
  const commits = await apiService.getPendingCommits(linkedProject.projectId);
774
999
  const commit = commits.find((c) => c.id.startsWith(id));
@@ -780,7 +1005,24 @@ function registerCommitCommands(program2) {
780
1005
  console.log(chalk9.gray('Run "stint commits" to see available commits.\n'));
781
1006
  process6.exit(1);
782
1007
  }
783
- spinner.text = `Executing commit: ${commit.message}`;
1008
+ spinner.stop();
1009
+ console.log(chalk9.blue("\n\u{1F4CB} Staged changes to commit:"));
1010
+ console.log(chalk9.gray("\u2500".repeat(40)));
1011
+ status.staged.forEach((file) => {
1012
+ console.log(chalk9.green(` + ${file}`));
1013
+ });
1014
+ console.log();
1015
+ console.log(`${chalk9.bold("Message:")} ${commit.message}`);
1016
+ console.log();
1017
+ const confirmed = await confirm2({
1018
+ message: "Are you sure you want to commit these changes?",
1019
+ default: true
1020
+ });
1021
+ if (!confirmed) {
1022
+ console.log(chalk9.yellow("\nCommit cancelled.\n"));
1023
+ return;
1024
+ }
1025
+ const execSpinner = ora9("Executing commit...").start();
784
1026
  const project = {
785
1027
  id: linkedProject.projectId,
786
1028
  name: "Current Project",
@@ -789,7 +1031,7 @@ function registerCommitCommands(program2) {
789
1031
  updatedAt: ""
790
1032
  };
791
1033
  const sha = await commitQueue.executeCommit(commit, project);
792
- spinner.succeed("Commit executed successfully!");
1034
+ execSpinner.succeed("Commit executed successfully!");
793
1035
  console.log(chalk9.green("\n\u2713 Commit executed"));
794
1036
  console.log(chalk9.gray("\u2500".repeat(50)));
795
1037
  console.log(`${chalk9.bold("Commit ID:")} ${commit.id}`);
@@ -798,7 +1040,9 @@ function registerCommitCommands(program2) {
798
1040
  console.log();
799
1041
  logger.success("commit", `Executed commit ${commit.id} -> ${sha}`);
800
1042
  } catch (error) {
801
- spinner.fail("Commit execution failed");
1043
+ if (ora9().isSpinning) {
1044
+ ora9().fail("Commit execution failed");
1045
+ }
802
1046
  logger.error("commit", "Failed to execute commit", error);
803
1047
  console.error(chalk9.red(`
804
1048
  \u2716 Error: ${error.message}
@@ -818,8 +1062,9 @@ function getTimeAgo(date) {
818
1062
  }
819
1063
 
820
1064
  // src/index.ts
1065
+ var AGENT_VERSION = "1.0.2";
821
1066
  var program = new Command();
822
- program.name("stint").description("Stint Agent - Local daemon for Stint Project Assistant").version("1.0.0").addHelpText("after", `
1067
+ program.name("stint").description("Stint Agent - Local daemon for Stint Project Assistant").version(AGENT_VERSION, "-V, --version", "output the current version").addHelpText("after", `
823
1068
  ${chalk10.bold("Examples:")}
824
1069
  ${chalk10.cyan("$")} stint login ${chalk10.gray("# Authenticate with Stint")}
825
1070
  ${chalk10.cyan("$")} stint link ${chalk10.gray("# Link current directory to a project")}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gowelle/stint-agent",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Local agent for Stint - Project Assistant",
5
5
  "author": "Gowelle John <gowelle.john@icloud.com>",
6
6
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "prepublishOnly": "npm run build"
37
37
  },
38
38
  "dependencies": {
39
- "@inquirer/prompts": "^5.0.0",
39
+ "@inquirer/prompts": "^5.5.0",
40
40
  "chalk": "^5.3.0",
41
41
  "commander": "^12.0.0",
42
42
  "conf": "^12.0.0",
@@ -60,4 +60,4 @@
60
60
  "engines": {
61
61
  "node": ">=20.0.0"
62
62
  }
63
- }
63
+ }