@keepgoingdev/cli 0.1.0 → 0.1.1
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/dist/index.js +117 -79
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/storage.ts
|
|
4
|
-
import fs from "fs";
|
|
5
|
-
import path from "path";
|
|
6
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
7
|
-
|
|
8
3
|
// ../../packages/shared/src/session.ts
|
|
9
4
|
import { randomUUID } from "crypto";
|
|
10
5
|
function generateCheckpointId() {
|
|
@@ -59,6 +54,18 @@ function formatRelativeTime(timestamp) {
|
|
|
59
54
|
import { execFileSync, execFile } from "child_process";
|
|
60
55
|
import { promisify } from "util";
|
|
61
56
|
var execFileAsync = promisify(execFile);
|
|
57
|
+
function findGitRoot(startPath) {
|
|
58
|
+
try {
|
|
59
|
+
const result = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
60
|
+
cwd: startPath,
|
|
61
|
+
encoding: "utf-8",
|
|
62
|
+
timeout: 5e3
|
|
63
|
+
});
|
|
64
|
+
return result.trim() || startPath;
|
|
65
|
+
} catch {
|
|
66
|
+
return startPath;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
62
69
|
function getCurrentBranch(workspacePath) {
|
|
63
70
|
try {
|
|
64
71
|
const result = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
@@ -93,69 +100,14 @@ function getRecentSessions(allSessions, count = RECENT_SESSION_COUNT) {
|
|
|
93
100
|
return allSessions.slice(-count).reverse();
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
// src/storage.ts
|
|
103
|
+
// ../../packages/shared/src/storage.ts
|
|
104
|
+
import fs from "fs";
|
|
105
|
+
import path from "path";
|
|
106
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
97
107
|
var STORAGE_DIR = ".keepgoing";
|
|
98
108
|
var META_FILE = "meta.json";
|
|
99
109
|
var SESSIONS_FILE = "sessions.json";
|
|
100
110
|
var STATE_FILE = "state.json";
|
|
101
|
-
var KeepGoingReader = class {
|
|
102
|
-
storagePath;
|
|
103
|
-
metaFilePath;
|
|
104
|
-
sessionsFilePath;
|
|
105
|
-
stateFilePath;
|
|
106
|
-
constructor(workspacePath) {
|
|
107
|
-
this.storagePath = path.join(workspacePath, STORAGE_DIR);
|
|
108
|
-
this.metaFilePath = path.join(this.storagePath, META_FILE);
|
|
109
|
-
this.sessionsFilePath = path.join(this.storagePath, SESSIONS_FILE);
|
|
110
|
-
this.stateFilePath = path.join(this.storagePath, STATE_FILE);
|
|
111
|
-
}
|
|
112
|
-
exists() {
|
|
113
|
-
return fs.existsSync(this.storagePath);
|
|
114
|
-
}
|
|
115
|
-
getState() {
|
|
116
|
-
return this.readJsonFile(this.stateFilePath);
|
|
117
|
-
}
|
|
118
|
-
getMeta() {
|
|
119
|
-
return this.readJsonFile(this.metaFilePath);
|
|
120
|
-
}
|
|
121
|
-
getSessions() {
|
|
122
|
-
return this.parseSessions().sessions;
|
|
123
|
-
}
|
|
124
|
-
getLastSession() {
|
|
125
|
-
const { sessions, wrapperLastSessionId } = this.parseSessions();
|
|
126
|
-
if (sessions.length === 0) {
|
|
127
|
-
return void 0;
|
|
128
|
-
}
|
|
129
|
-
const state = this.getState();
|
|
130
|
-
if (state?.lastSessionId) {
|
|
131
|
-
const found = sessions.find((s) => s.id === state.lastSessionId);
|
|
132
|
-
if (found) return found;
|
|
133
|
-
}
|
|
134
|
-
if (wrapperLastSessionId) {
|
|
135
|
-
const found = sessions.find((s) => s.id === wrapperLastSessionId);
|
|
136
|
-
if (found) return found;
|
|
137
|
-
}
|
|
138
|
-
return sessions[sessions.length - 1];
|
|
139
|
-
}
|
|
140
|
-
getRecentSessions(count) {
|
|
141
|
-
return getRecentSessions(this.getSessions(), count);
|
|
142
|
-
}
|
|
143
|
-
parseSessions() {
|
|
144
|
-
const raw = this.readJsonFile(this.sessionsFilePath);
|
|
145
|
-
if (!raw) return { sessions: [] };
|
|
146
|
-
if (Array.isArray(raw)) return { sessions: raw };
|
|
147
|
-
return { sessions: raw.sessions ?? [], wrapperLastSessionId: raw.lastSessionId };
|
|
148
|
-
}
|
|
149
|
-
readJsonFile(filePath) {
|
|
150
|
-
try {
|
|
151
|
-
if (!fs.existsSync(filePath)) return void 0;
|
|
152
|
-
const raw = fs.readFileSync(filePath, "utf-8");
|
|
153
|
-
return JSON.parse(raw);
|
|
154
|
-
} catch {
|
|
155
|
-
return void 0;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
111
|
var KeepGoingWriter = class {
|
|
160
112
|
storagePath;
|
|
161
113
|
sessionsFilePath;
|
|
@@ -191,6 +143,10 @@ var KeepGoingWriter = class {
|
|
|
191
143
|
}
|
|
192
144
|
sessionsData.sessions.push(checkpoint);
|
|
193
145
|
sessionsData.lastSessionId = checkpoint.id;
|
|
146
|
+
const MAX_SESSIONS = 200;
|
|
147
|
+
if (sessionsData.sessions.length > MAX_SESSIONS) {
|
|
148
|
+
sessionsData.sessions = sessionsData.sessions.slice(-MAX_SESSIONS);
|
|
149
|
+
}
|
|
194
150
|
fs.writeFileSync(this.sessionsFilePath, JSON.stringify(sessionsData, null, 2), "utf-8");
|
|
195
151
|
const state = {
|
|
196
152
|
lastSessionId: checkpoint.id,
|
|
@@ -198,29 +154,110 @@ var KeepGoingWriter = class {
|
|
|
198
154
|
lastActivityAt: checkpoint.timestamp
|
|
199
155
|
};
|
|
200
156
|
fs.writeFileSync(this.stateFilePath, JSON.stringify(state, null, 2), "utf-8");
|
|
157
|
+
this.updateMeta(checkpoint.timestamp);
|
|
158
|
+
}
|
|
159
|
+
updateMeta(timestamp) {
|
|
201
160
|
let meta;
|
|
202
161
|
try {
|
|
203
162
|
if (fs.existsSync(this.metaFilePath)) {
|
|
204
163
|
meta = JSON.parse(fs.readFileSync(this.metaFilePath, "utf-8"));
|
|
205
|
-
meta.lastUpdated =
|
|
164
|
+
meta.lastUpdated = timestamp;
|
|
206
165
|
} else {
|
|
207
166
|
meta = {
|
|
208
167
|
projectId: randomUUID2(),
|
|
209
|
-
createdAt:
|
|
210
|
-
lastUpdated:
|
|
168
|
+
createdAt: timestamp,
|
|
169
|
+
lastUpdated: timestamp
|
|
211
170
|
};
|
|
212
171
|
}
|
|
213
172
|
} catch {
|
|
214
173
|
meta = {
|
|
215
174
|
projectId: randomUUID2(),
|
|
216
|
-
createdAt:
|
|
217
|
-
lastUpdated:
|
|
175
|
+
createdAt: timestamp,
|
|
176
|
+
lastUpdated: timestamp
|
|
218
177
|
};
|
|
219
178
|
}
|
|
220
179
|
fs.writeFileSync(this.metaFilePath, JSON.stringify(meta, null, 2), "utf-8");
|
|
221
180
|
}
|
|
222
181
|
};
|
|
223
182
|
|
|
183
|
+
// ../../packages/shared/src/decisionStorage.ts
|
|
184
|
+
import fs2 from "fs";
|
|
185
|
+
import path2 from "path";
|
|
186
|
+
|
|
187
|
+
// ../../packages/shared/src/featureGate.ts
|
|
188
|
+
var DefaultFeatureGate = class {
|
|
189
|
+
isEnabled(_feature) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
var currentGate = new DefaultFeatureGate();
|
|
194
|
+
|
|
195
|
+
// src/storage.ts
|
|
196
|
+
import fs3 from "fs";
|
|
197
|
+
import path3 from "path";
|
|
198
|
+
var STORAGE_DIR2 = ".keepgoing";
|
|
199
|
+
var META_FILE2 = "meta.json";
|
|
200
|
+
var SESSIONS_FILE2 = "sessions.json";
|
|
201
|
+
var STATE_FILE2 = "state.json";
|
|
202
|
+
var KeepGoingReader = class {
|
|
203
|
+
storagePath;
|
|
204
|
+
metaFilePath;
|
|
205
|
+
sessionsFilePath;
|
|
206
|
+
stateFilePath;
|
|
207
|
+
constructor(workspacePath) {
|
|
208
|
+
this.storagePath = path3.join(workspacePath, STORAGE_DIR2);
|
|
209
|
+
this.metaFilePath = path3.join(this.storagePath, META_FILE2);
|
|
210
|
+
this.sessionsFilePath = path3.join(this.storagePath, SESSIONS_FILE2);
|
|
211
|
+
this.stateFilePath = path3.join(this.storagePath, STATE_FILE2);
|
|
212
|
+
}
|
|
213
|
+
exists() {
|
|
214
|
+
return fs3.existsSync(this.storagePath);
|
|
215
|
+
}
|
|
216
|
+
getState() {
|
|
217
|
+
return this.readJsonFile(this.stateFilePath);
|
|
218
|
+
}
|
|
219
|
+
getMeta() {
|
|
220
|
+
return this.readJsonFile(this.metaFilePath);
|
|
221
|
+
}
|
|
222
|
+
getSessions() {
|
|
223
|
+
return this.parseSessions().sessions;
|
|
224
|
+
}
|
|
225
|
+
getLastSession() {
|
|
226
|
+
const { sessions, wrapperLastSessionId } = this.parseSessions();
|
|
227
|
+
if (sessions.length === 0) {
|
|
228
|
+
return void 0;
|
|
229
|
+
}
|
|
230
|
+
const state = this.getState();
|
|
231
|
+
if (state?.lastSessionId) {
|
|
232
|
+
const found = sessions.find((s) => s.id === state.lastSessionId);
|
|
233
|
+
if (found) return found;
|
|
234
|
+
}
|
|
235
|
+
if (wrapperLastSessionId) {
|
|
236
|
+
const found = sessions.find((s) => s.id === wrapperLastSessionId);
|
|
237
|
+
if (found) return found;
|
|
238
|
+
}
|
|
239
|
+
return sessions[sessions.length - 1];
|
|
240
|
+
}
|
|
241
|
+
getRecentSessions(count) {
|
|
242
|
+
return getRecentSessions(this.getSessions(), count);
|
|
243
|
+
}
|
|
244
|
+
parseSessions() {
|
|
245
|
+
const raw = this.readJsonFile(this.sessionsFilePath);
|
|
246
|
+
if (!raw) return { sessions: [] };
|
|
247
|
+
if (Array.isArray(raw)) return { sessions: raw };
|
|
248
|
+
return { sessions: raw.sessions ?? [], wrapperLastSessionId: raw.lastSessionId };
|
|
249
|
+
}
|
|
250
|
+
readJsonFile(filePath) {
|
|
251
|
+
try {
|
|
252
|
+
if (!fs3.existsSync(filePath)) return void 0;
|
|
253
|
+
const raw = fs3.readFileSync(filePath, "utf-8");
|
|
254
|
+
return JSON.parse(raw);
|
|
255
|
+
} catch {
|
|
256
|
+
return void 0;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
224
261
|
// src/render.ts
|
|
225
262
|
var RESET = "\x1B[0m";
|
|
226
263
|
var BOLD = "\x1B[1m";
|
|
@@ -300,7 +337,7 @@ async function statusCommand(opts) {
|
|
|
300
337
|
|
|
301
338
|
// src/commands/save.ts
|
|
302
339
|
import readline from "readline";
|
|
303
|
-
import
|
|
340
|
+
import path4 from "path";
|
|
304
341
|
function prompt(rl, question) {
|
|
305
342
|
return new Promise((resolve) => {
|
|
306
343
|
rl.question(question, (answer) => {
|
|
@@ -344,15 +381,15 @@ async function saveCommand(opts) {
|
|
|
344
381
|
workspaceRoot: opts.cwd,
|
|
345
382
|
source: "manual"
|
|
346
383
|
});
|
|
347
|
-
const projectName =
|
|
384
|
+
const projectName = path4.basename(opts.cwd);
|
|
348
385
|
const writer = new KeepGoingWriter(opts.cwd);
|
|
349
386
|
writer.saveCheckpoint(checkpoint, projectName);
|
|
350
387
|
console.log("Checkpoint saved.");
|
|
351
388
|
}
|
|
352
389
|
|
|
353
390
|
// src/commands/hook.ts
|
|
354
|
-
import
|
|
355
|
-
import
|
|
391
|
+
import fs4 from "fs";
|
|
392
|
+
import path5 from "path";
|
|
356
393
|
import os from "os";
|
|
357
394
|
var HOOK_MARKER_START = "# keepgoing-hook-start";
|
|
358
395
|
var HOOK_MARKER_END = "# keepgoing-hook-end";
|
|
@@ -381,10 +418,10 @@ function detectShellRcFile() {
|
|
|
381
418
|
const shellEnv = process.env["SHELL"] ?? "";
|
|
382
419
|
const home = os.homedir();
|
|
383
420
|
if (shellEnv.endsWith("zsh")) {
|
|
384
|
-
return { shell: "zsh", rcFile:
|
|
421
|
+
return { shell: "zsh", rcFile: path5.join(home, ".zshrc") };
|
|
385
422
|
}
|
|
386
423
|
if (shellEnv.endsWith("bash")) {
|
|
387
|
-
return { shell: "bash", rcFile:
|
|
424
|
+
return { shell: "bash", rcFile: path5.join(home, ".bashrc") };
|
|
388
425
|
}
|
|
389
426
|
return void 0;
|
|
390
427
|
}
|
|
@@ -400,14 +437,14 @@ function hookInstallCommand() {
|
|
|
400
437
|
const hookBlock = shell === "zsh" ? ZSH_HOOK : BASH_HOOK;
|
|
401
438
|
let existing = "";
|
|
402
439
|
try {
|
|
403
|
-
existing =
|
|
440
|
+
existing = fs4.readFileSync(rcFile, "utf-8");
|
|
404
441
|
} catch {
|
|
405
442
|
}
|
|
406
443
|
if (existing.includes(HOOK_MARKER_START)) {
|
|
407
444
|
console.log(`KeepGoing hook is already installed in ${rcFile}.`);
|
|
408
445
|
return;
|
|
409
446
|
}
|
|
410
|
-
|
|
447
|
+
fs4.appendFileSync(rcFile, `
|
|
411
448
|
${hookBlock}
|
|
412
449
|
`, "utf-8");
|
|
413
450
|
console.log(`KeepGoing hook installed in ${rcFile}.`);
|
|
@@ -426,7 +463,7 @@ function hookUninstallCommand() {
|
|
|
426
463
|
const { rcFile } = detected;
|
|
427
464
|
let existing = "";
|
|
428
465
|
try {
|
|
429
|
-
existing =
|
|
466
|
+
existing = fs4.readFileSync(rcFile, "utf-8");
|
|
430
467
|
} catch {
|
|
431
468
|
console.log(`${rcFile} not found \u2014 nothing to remove.`);
|
|
432
469
|
return;
|
|
@@ -442,7 +479,7 @@ function hookUninstallCommand() {
|
|
|
442
479
|
"g"
|
|
443
480
|
);
|
|
444
481
|
const updated = existing.replace(pattern, "").replace(/\n{3,}/g, "\n\n");
|
|
445
|
-
|
|
482
|
+
fs4.writeFileSync(rcFile, updated, "utf-8");
|
|
446
483
|
console.log(`KeepGoing hook removed from ${rcFile}.`);
|
|
447
484
|
console.log(`Reload your shell config to deactivate it:
|
|
448
485
|
`);
|
|
@@ -491,6 +528,7 @@ function parseArgs(argv) {
|
|
|
491
528
|
subcommand = arg;
|
|
492
529
|
}
|
|
493
530
|
}
|
|
531
|
+
cwd = findGitRoot(cwd);
|
|
494
532
|
return { command, subcommand, cwd, json, quiet };
|
|
495
533
|
}
|
|
496
534
|
async function main() {
|