@gowelle/stint-agent 1.0.1 → 1.0.3
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
|
@@ -34,7 +34,7 @@ stint login
|
|
|
34
34
|
# Check your authentication status
|
|
35
35
|
stint whoami
|
|
36
36
|
|
|
37
|
-
# Link a project
|
|
37
|
+
# Link a project (or create a new one)
|
|
38
38
|
cd /path/to/your/project
|
|
39
39
|
stint link
|
|
40
40
|
|
|
@@ -47,6 +47,13 @@ stint daemon status
|
|
|
47
47
|
|
|
48
48
|
## Commands
|
|
49
49
|
|
|
50
|
+
### General
|
|
51
|
+
|
|
52
|
+
| Command | Description |
|
|
53
|
+
|---------|-------------|
|
|
54
|
+
| `stint --version`, `stint -V` | Show current agent version |
|
|
55
|
+
| `stint --help`, `stint -h` | Show help information |
|
|
56
|
+
|
|
50
57
|
### Authentication
|
|
51
58
|
|
|
52
59
|
| Command | Description |
|
|
@@ -69,7 +76,7 @@ stint daemon status
|
|
|
69
76
|
|
|
70
77
|
| Command | Description |
|
|
71
78
|
|---------|-------------|
|
|
72
|
-
| `stint link` | Link current directory to a Stint project |
|
|
79
|
+
| `stint link` | Link current directory to a Stint project (or create a new one) |
|
|
73
80
|
| `stint unlink [--force]` | Remove project link |
|
|
74
81
|
| `stint status` | Show project, git, auth, and daemon status |
|
|
75
82
|
| `stint sync` | Manually sync repository information to server |
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
apiService,
|
|
3
3
|
config,
|
|
4
4
|
logger
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NMOUYAW2.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
|
|
326
|
-
if (
|
|
327
|
-
throw new Error(
|
|
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}`);
|
|
@@ -254,7 +254,7 @@ var AuthServiceImpl = class {
|
|
|
254
254
|
return null;
|
|
255
255
|
}
|
|
256
256
|
try {
|
|
257
|
-
const { apiService: apiService2 } = await import("./api-
|
|
257
|
+
const { apiService: apiService2 } = await import("./api-37DCZYLF.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.
|
|
277
|
+
var AGENT_VERSION = "1.0.3";
|
|
278
278
|
var ApiServiceImpl = class {
|
|
279
279
|
sessionId = null;
|
|
280
280
|
async getHeaders() {
|
|
@@ -351,7 +351,7 @@ var ApiServiceImpl = class {
|
|
|
351
351
|
return session;
|
|
352
352
|
}, "Connect");
|
|
353
353
|
}
|
|
354
|
-
async disconnect() {
|
|
354
|
+
async disconnect(reason) {
|
|
355
355
|
if (!this.sessionId) {
|
|
356
356
|
return;
|
|
357
357
|
}
|
|
@@ -359,7 +359,8 @@ var ApiServiceImpl = class {
|
|
|
359
359
|
await this.request("/api/agent/disconnect", {
|
|
360
360
|
method: "POST",
|
|
361
361
|
body: JSON.stringify({
|
|
362
|
-
session_id: this.sessionId
|
|
362
|
+
session_id: this.sessionId,
|
|
363
|
+
reason: reason || "Agent disconnected"
|
|
363
364
|
})
|
|
364
365
|
});
|
|
365
366
|
this.sessionId = null;
|
|
@@ -381,22 +382,40 @@ var ApiServiceImpl = class {
|
|
|
381
382
|
}
|
|
382
383
|
async getPendingCommits(projectId) {
|
|
383
384
|
logger.info("api", `Fetching pending commits for project ${projectId}`);
|
|
384
|
-
const
|
|
385
|
-
`/api/agent/
|
|
385
|
+
const response = await this.request(
|
|
386
|
+
`/api/agent/pending-commits?project_id=${projectId}`
|
|
386
387
|
);
|
|
388
|
+
const commits = response.data.map((item) => ({
|
|
389
|
+
id: item.id,
|
|
390
|
+
projectId: item.project_id || item.projectId,
|
|
391
|
+
message: item.message,
|
|
392
|
+
files: item.files,
|
|
393
|
+
createdAt: item.created_at || item.createdAt
|
|
394
|
+
}));
|
|
387
395
|
logger.info("api", `Found ${commits.length} pending commits`);
|
|
388
396
|
return commits;
|
|
389
397
|
}
|
|
390
398
|
async markCommitExecuted(commitId, sha) {
|
|
391
399
|
logger.info("api", `Marking commit ${commitId} as executed (SHA: ${sha})`);
|
|
392
400
|
return this.withRetry(async () => {
|
|
393
|
-
const
|
|
401
|
+
const response = await this.request(
|
|
394
402
|
`/api/agent/commits/${commitId}/executed`,
|
|
395
403
|
{
|
|
396
404
|
method: "POST",
|
|
397
405
|
body: JSON.stringify({ sha })
|
|
398
406
|
}
|
|
399
407
|
);
|
|
408
|
+
const data = response.data;
|
|
409
|
+
const commit = {
|
|
410
|
+
id: data.id,
|
|
411
|
+
projectId: data.project_id || data.projectId,
|
|
412
|
+
message: data.message,
|
|
413
|
+
sha: data.sha,
|
|
414
|
+
status: data.status,
|
|
415
|
+
createdAt: data.created_at || data.createdAt,
|
|
416
|
+
executedAt: data.executed_at || data.executedAt,
|
|
417
|
+
error: data.error
|
|
418
|
+
};
|
|
400
419
|
logger.success("api", `Commit ${commitId} marked as executed`);
|
|
401
420
|
return commit;
|
|
402
421
|
}, "Mark commit executed");
|
|
@@ -422,7 +441,8 @@ var ApiServiceImpl = class {
|
|
|
422
441
|
}
|
|
423
442
|
async getLinkedProjects() {
|
|
424
443
|
logger.info("api", "Fetching linked projects");
|
|
425
|
-
const
|
|
444
|
+
const response = await this.request("/api/agent/projects");
|
|
445
|
+
const projects = response.data;
|
|
426
446
|
logger.info("api", `Found ${projects.length} linked projects`);
|
|
427
447
|
return projects;
|
|
428
448
|
}
|
|
@@ -432,6 +452,16 @@ var ApiServiceImpl = class {
|
|
|
432
452
|
logger.info("api", `Fetched user: ${user.email}`);
|
|
433
453
|
return user;
|
|
434
454
|
}
|
|
455
|
+
async createProject(data) {
|
|
456
|
+
logger.info("api", `Creating project: ${data.name}`);
|
|
457
|
+
const response = await this.request("/api/agent/projects", {
|
|
458
|
+
method: "POST",
|
|
459
|
+
body: JSON.stringify(data)
|
|
460
|
+
});
|
|
461
|
+
const project = response.data;
|
|
462
|
+
logger.success("api", `Created project: ${project.name} (${project.id})`);
|
|
463
|
+
return project;
|
|
464
|
+
}
|
|
435
465
|
};
|
|
436
466
|
var apiService = new ApiServiceImpl();
|
|
437
467
|
|
package/dist/daemon/runner.js
CHANGED
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
projectService,
|
|
6
6
|
removePidFile,
|
|
7
7
|
writePidFile
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-EVWSJ3RU.js";
|
|
9
9
|
import {
|
|
10
10
|
apiService,
|
|
11
11
|
authService,
|
|
12
12
|
config,
|
|
13
13
|
logger
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-NMOUYAW2.js";
|
|
15
15
|
|
|
16
16
|
// src/daemon/runner.ts
|
|
17
17
|
import "dotenv/config";
|
|
@@ -27,8 +27,12 @@ var WebSocketServiceImpl = class {
|
|
|
27
27
|
isManualDisconnect = false;
|
|
28
28
|
// Event handlers
|
|
29
29
|
commitApprovedHandlers = [];
|
|
30
|
+
commitPendingHandlers = [];
|
|
31
|
+
suggestionCreatedHandlers = [];
|
|
30
32
|
projectUpdatedHandlers = [];
|
|
31
33
|
disconnectHandlers = [];
|
|
34
|
+
agentDisconnectedHandlers = [];
|
|
35
|
+
syncRequestedHandlers = [];
|
|
32
36
|
async connect() {
|
|
33
37
|
try {
|
|
34
38
|
const token = await authService.getToken();
|
|
@@ -108,12 +112,24 @@ var WebSocketServiceImpl = class {
|
|
|
108
112
|
onCommitApproved(handler) {
|
|
109
113
|
this.commitApprovedHandlers.push(handler);
|
|
110
114
|
}
|
|
115
|
+
onCommitPending(handler) {
|
|
116
|
+
this.commitPendingHandlers.push(handler);
|
|
117
|
+
}
|
|
118
|
+
onSuggestionCreated(handler) {
|
|
119
|
+
this.suggestionCreatedHandlers.push(handler);
|
|
120
|
+
}
|
|
111
121
|
onProjectUpdated(handler) {
|
|
112
122
|
this.projectUpdatedHandlers.push(handler);
|
|
113
123
|
}
|
|
114
124
|
onDisconnect(handler) {
|
|
115
125
|
this.disconnectHandlers.push(handler);
|
|
116
126
|
}
|
|
127
|
+
onAgentDisconnected(handler) {
|
|
128
|
+
this.agentDisconnectedHandlers.push(handler);
|
|
129
|
+
}
|
|
130
|
+
onSyncRequested(handler) {
|
|
131
|
+
this.syncRequestedHandlers.push(handler);
|
|
132
|
+
}
|
|
117
133
|
sendMessage(message) {
|
|
118
134
|
if (!this.isConnected()) {
|
|
119
135
|
logger.warn("websocket", "Cannot send message: not connected");
|
|
@@ -139,6 +155,18 @@ var WebSocketServiceImpl = class {
|
|
|
139
155
|
this.commitApprovedHandlers.forEach((handler) => handler(commit, project));
|
|
140
156
|
return;
|
|
141
157
|
}
|
|
158
|
+
if (message.event === "commit.pending") {
|
|
159
|
+
const { pendingCommit } = message.data;
|
|
160
|
+
logger.info("websocket", `Commit pending: ${pendingCommit.id}`);
|
|
161
|
+
this.commitPendingHandlers.forEach((handler) => handler(pendingCommit));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (message.event === "suggestion.created") {
|
|
165
|
+
const { suggestion } = message.data;
|
|
166
|
+
logger.info("websocket", `Suggestion created: ${suggestion.id}`);
|
|
167
|
+
this.suggestionCreatedHandlers.forEach((handler) => handler(suggestion));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
142
170
|
if (message.event === "project.updated") {
|
|
143
171
|
const { project } = message.data;
|
|
144
172
|
logger.info("websocket", `Project updated: ${project.id}`);
|
|
@@ -148,6 +176,13 @@ var WebSocketServiceImpl = class {
|
|
|
148
176
|
if (message.event === "sync.requested") {
|
|
149
177
|
const { projectId } = message.data;
|
|
150
178
|
logger.info("websocket", `Sync requested for project: ${projectId}`);
|
|
179
|
+
this.syncRequestedHandlers.forEach((handler) => handler(projectId));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (message.event === "agent.disconnected") {
|
|
183
|
+
const reason = message.data?.reason || "Server requested disconnect";
|
|
184
|
+
logger.warn("websocket", `Agent disconnected by server: ${reason}`);
|
|
185
|
+
this.agentDisconnectedHandlers.forEach((handler) => handler(reason));
|
|
151
186
|
return;
|
|
152
187
|
}
|
|
153
188
|
logger.debug("websocket", `Unhandled event: ${message.event}`);
|
|
@@ -358,6 +393,19 @@ var FileWatcher = class {
|
|
|
358
393
|
addProject(projectPath, projectId) {
|
|
359
394
|
this.watchProject(projectPath, projectId);
|
|
360
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Sync a project by ID (called when server requests a sync)
|
|
398
|
+
*/
|
|
399
|
+
async syncProjectById(projectId) {
|
|
400
|
+
const linkedProjects = projectService.getAllLinkedProjects();
|
|
401
|
+
for (const [projectPath, linkedProject] of Object.entries(linkedProjects)) {
|
|
402
|
+
if (linkedProject.projectId === projectId) {
|
|
403
|
+
await this.performSync(projectPath, projectId);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
logger.warn("watcher", `Cannot sync: project ${projectId} not found in linked projects`);
|
|
408
|
+
}
|
|
361
409
|
/**
|
|
362
410
|
* Remove a project from watching (called when a project is unlinked)
|
|
363
411
|
*/
|
|
@@ -377,9 +425,32 @@ var FileWatcher = class {
|
|
|
377
425
|
}
|
|
378
426
|
};
|
|
379
427
|
|
|
428
|
+
// src/utils/notify.ts
|
|
429
|
+
import notifier from "node-notifier";
|
|
430
|
+
function notify(options) {
|
|
431
|
+
try {
|
|
432
|
+
notifier.notify({
|
|
433
|
+
title: options.title,
|
|
434
|
+
message: options.message,
|
|
435
|
+
open: options.open,
|
|
436
|
+
sound: true,
|
|
437
|
+
wait: false,
|
|
438
|
+
appID: "Stint Agent"
|
|
439
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
440
|
+
}, (error) => {
|
|
441
|
+
if (error) {
|
|
442
|
+
logger.error("notify", "Failed to send notification", error);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
} catch (error) {
|
|
446
|
+
logger.error("notify", "Failed to send notification", error);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
380
450
|
// src/daemon/index.ts
|
|
381
451
|
var heartbeatInterval = null;
|
|
382
452
|
var isShuttingDown = false;
|
|
453
|
+
var shutdownReason;
|
|
383
454
|
var fileWatcher = new FileWatcher();
|
|
384
455
|
async function startDaemon() {
|
|
385
456
|
logger.info("daemon", "Starting daemon...");
|
|
@@ -406,6 +477,30 @@ async function startDaemon() {
|
|
|
406
477
|
websocketService.onDisconnect(() => {
|
|
407
478
|
logger.warn("daemon", "WebSocket disconnected, will attempt to reconnect");
|
|
408
479
|
});
|
|
480
|
+
websocketService.onAgentDisconnected(async (reason) => {
|
|
481
|
+
logger.warn("daemon", `Server disconnected agent: ${reason}`);
|
|
482
|
+
logger.info("daemon", "Initiating graceful shutdown...");
|
|
483
|
+
await shutdown(`Server: ${reason}`);
|
|
484
|
+
process.exit(0);
|
|
485
|
+
});
|
|
486
|
+
websocketService.onSuggestionCreated((suggestion) => {
|
|
487
|
+
logger.info("daemon", `Suggestion created: ${suggestion.title} (${suggestion.priority})`);
|
|
488
|
+
notify({
|
|
489
|
+
title: "New Suggestion",
|
|
490
|
+
message: `${suggestion.title}
|
|
491
|
+
Priority: ${suggestion.priority}`,
|
|
492
|
+
open: `https://stint.codes/projects/${suggestion.project_id}/suggestions/${suggestion.id}`
|
|
493
|
+
// Hypothetical URL structure
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
websocketService.onSyncRequested(async (projectId) => {
|
|
497
|
+
logger.info("daemon", `Server requested sync for project: ${projectId}`);
|
|
498
|
+
try {
|
|
499
|
+
await fileWatcher.syncProjectById(projectId);
|
|
500
|
+
} catch (error) {
|
|
501
|
+
logger.error("daemon", `Failed to sync project ${projectId}`, error);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
409
504
|
setupSignalHandlers();
|
|
410
505
|
startHeartbeat();
|
|
411
506
|
fileWatcher.start();
|
|
@@ -443,15 +538,16 @@ function setupSignalHandlers() {
|
|
|
443
538
|
signals.forEach((signal) => {
|
|
444
539
|
process.on(signal, async () => {
|
|
445
540
|
logger.info("daemon", `Received ${signal}, shutting down...`);
|
|
446
|
-
await shutdown();
|
|
541
|
+
await shutdown(`Signal: ${signal}`);
|
|
447
542
|
process.exit(0);
|
|
448
543
|
});
|
|
449
544
|
});
|
|
450
545
|
logger.info("daemon", "Signal handlers registered");
|
|
451
546
|
}
|
|
452
|
-
async function shutdown() {
|
|
547
|
+
async function shutdown(reason) {
|
|
453
548
|
if (isShuttingDown) return;
|
|
454
549
|
isShuttingDown = true;
|
|
550
|
+
shutdownReason = reason;
|
|
455
551
|
logger.info("daemon", "Shutting down daemon...");
|
|
456
552
|
stopHeartbeat();
|
|
457
553
|
try {
|
|
@@ -467,7 +563,7 @@ async function shutdown() {
|
|
|
467
563
|
logger.error("daemon", "Failed to disconnect from WebSocket", error);
|
|
468
564
|
}
|
|
469
565
|
try {
|
|
470
|
-
await apiService.disconnect();
|
|
566
|
+
await apiService.disconnect(shutdownReason);
|
|
471
567
|
logger.info("daemon", "Disconnected from API");
|
|
472
568
|
} catch (error) {
|
|
473
569
|
logger.error("daemon", "Failed to disconnect from API", error);
|
package/dist/index.js
CHANGED
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
projectService,
|
|
9
9
|
spawnDetached,
|
|
10
10
|
validatePidFile
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-EVWSJ3RU.js";
|
|
12
12
|
import {
|
|
13
13
|
apiService,
|
|
14
14
|
authService,
|
|
15
15
|
config,
|
|
16
16
|
logger
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-NMOUYAW2.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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
|
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
|
-
<
|
|
106
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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, "
|
|
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
|
-
},
|
|
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
|
|
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
|
|
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.message}`);
|
|
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
|
-
|
|
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
|
|
521
|
-
import
|
|
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 =
|
|
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: ${
|
|
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:")} ${
|
|
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 =
|
|
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 =
|
|
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("
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.3";
|
|
821
1066
|
var program = new Command();
|
|
822
|
-
program.name("stint").description("Stint Agent - Local daemon for Stint Project Assistant").version("
|
|
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.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Local agent for Stint - Project Assistant",
|
|
5
5
|
"author": "Gowelle John <gowelle.john@icloud.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,12 +36,13 @@
|
|
|
36
36
|
"prepublishOnly": "npm run build"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@inquirer/prompts": "^5.
|
|
39
|
+
"@inquirer/prompts": "^5.5.0",
|
|
40
40
|
"chalk": "^5.3.0",
|
|
41
41
|
"commander": "^12.0.0",
|
|
42
42
|
"conf": "^12.0.0",
|
|
43
43
|
"dotenv": "^17.2.3",
|
|
44
44
|
"node-fetch": "^3.3.2",
|
|
45
|
+
"node-notifier": "^10.0.1",
|
|
45
46
|
"open": "^10.0.0",
|
|
46
47
|
"ora": "^8.0.1",
|
|
47
48
|
"simple-git": "^3.22.0",
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@types/node": "^20.11.0",
|
|
53
|
+
"@types/node-notifier": "^8.0.5",
|
|
52
54
|
"@types/ws": "^8.5.10",
|
|
53
55
|
"@typescript-eslint/eslint-plugin": "^8.50.0",
|
|
54
56
|
"@typescript-eslint/parser": "^8.50.0",
|
|
@@ -60,4 +62,4 @@
|
|
|
60
62
|
"engines": {
|
|
61
63
|
"node": ">=20.0.0"
|
|
62
64
|
}
|
|
63
|
-
}
|
|
65
|
+
}
|