@gowelle/stint-agent 1.0.0 → 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,3 @@
|
|
|
1
|
-
// src/services/api.ts
|
|
2
|
-
import { createRequire } from "module";
|
|
3
|
-
|
|
4
1
|
// src/utils/config.ts
|
|
5
2
|
import Conf from "conf";
|
|
6
3
|
import { randomUUID } from "crypto";
|
|
@@ -257,7 +254,7 @@ var AuthServiceImpl = class {
|
|
|
257
254
|
return null;
|
|
258
255
|
}
|
|
259
256
|
try {
|
|
260
|
-
const { apiService: apiService2 } = await import("./api-
|
|
257
|
+
const { apiService: apiService2 } = await import("./api-ZMYJOXXU.js");
|
|
261
258
|
const user = await apiService2.getCurrentUser();
|
|
262
259
|
logger.info("auth", `Token validated for user: ${user.email}`);
|
|
263
260
|
return user;
|
|
@@ -277,9 +274,7 @@ var AuthServiceImpl = class {
|
|
|
277
274
|
var authService = new AuthServiceImpl();
|
|
278
275
|
|
|
279
276
|
// src/services/api.ts
|
|
280
|
-
var
|
|
281
|
-
var packageJson = require2("../../package.json");
|
|
282
|
-
var AGENT_VERSION = packageJson.version;
|
|
277
|
+
var AGENT_VERSION = "1.0.2";
|
|
283
278
|
var ApiServiceImpl = class {
|
|
284
279
|
sessionId = null;
|
|
285
280
|
async getHeaders() {
|
|
@@ -386,22 +381,40 @@ var ApiServiceImpl = class {
|
|
|
386
381
|
}
|
|
387
382
|
async getPendingCommits(projectId) {
|
|
388
383
|
logger.info("api", `Fetching pending commits for project ${projectId}`);
|
|
389
|
-
const
|
|
390
|
-
`/api/agent/
|
|
384
|
+
const response = await this.request(
|
|
385
|
+
`/api/agent/pending-commits?project_id=${projectId}`
|
|
391
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
|
+
}));
|
|
392
394
|
logger.info("api", `Found ${commits.length} pending commits`);
|
|
393
395
|
return commits;
|
|
394
396
|
}
|
|
395
397
|
async markCommitExecuted(commitId, sha) {
|
|
396
398
|
logger.info("api", `Marking commit ${commitId} as executed (SHA: ${sha})`);
|
|
397
399
|
return this.withRetry(async () => {
|
|
398
|
-
const
|
|
400
|
+
const response = await this.request(
|
|
399
401
|
`/api/agent/commits/${commitId}/executed`,
|
|
400
402
|
{
|
|
401
403
|
method: "POST",
|
|
402
404
|
body: JSON.stringify({ sha })
|
|
403
405
|
}
|
|
404
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
|
+
};
|
|
405
418
|
logger.success("api", `Commit ${commitId} marked as executed`);
|
|
406
419
|
return commit;
|
|
407
420
|
}, "Mark commit executed");
|
|
@@ -427,7 +440,8 @@ var ApiServiceImpl = class {
|
|
|
427
440
|
}
|
|
428
441
|
async getLinkedProjects() {
|
|
429
442
|
logger.info("api", "Fetching linked projects");
|
|
430
|
-
const
|
|
443
|
+
const response = await this.request("/api/agent/projects");
|
|
444
|
+
const projects = response.data;
|
|
431
445
|
logger.info("api", `Found ${projects.length} linked projects`);
|
|
432
446
|
return projects;
|
|
433
447
|
}
|
|
@@ -437,6 +451,16 @@ var ApiServiceImpl = class {
|
|
|
437
451
|
logger.info("api", `Fetched user: ${user.email}`);
|
|
438
452
|
return user;
|
|
439
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
|
+
}
|
|
440
464
|
};
|
|
441
465
|
var apiService = new ApiServiceImpl();
|
|
442
466
|
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
apiService,
|
|
3
3
|
config,
|
|
4
4
|
logger
|
|
5
|
-
} from "./chunk-
|
|
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
|
|
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}`);
|
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-WFWZCAJ6.js";
|
|
9
9
|
import {
|
|
10
10
|
apiService,
|
|
11
11
|
authService,
|
|
12
12
|
config,
|
|
13
13
|
logger
|
|
14
|
-
} from "../chunk-
|
|
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-
|
|
11
|
+
} from "./chunk-WFWZCAJ6.js";
|
|
12
12
|
import {
|
|
13
13
|
apiService,
|
|
14
14
|
authService,
|
|
15
15
|
config,
|
|
16
16
|
logger
|
|
17
|
-
} from "./chunk-
|
|
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
|
-
|
|
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);
|
|
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.2";
|
|
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.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.
|
|
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
|
+
}
|