@keepgoingdev/cli 0.1.1 → 0.2.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 +363 -33
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -52,17 +52,46 @@ function formatRelativeTime(timestamp) {
|
|
|
52
52
|
|
|
53
53
|
// ../../packages/shared/src/gitUtils.ts
|
|
54
54
|
import { execFileSync, execFile } from "child_process";
|
|
55
|
+
import path from "path";
|
|
55
56
|
import { promisify } from "util";
|
|
56
57
|
var execFileAsync = promisify(execFile);
|
|
57
58
|
function findGitRoot(startPath) {
|
|
58
59
|
try {
|
|
59
|
-
const
|
|
60
|
+
const gitCommonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
60
61
|
cwd: startPath,
|
|
61
62
|
encoding: "utf-8",
|
|
62
63
|
timeout: 5e3
|
|
63
|
-
});
|
|
64
|
-
|
|
64
|
+
}).trim();
|
|
65
|
+
if (!gitCommonDir) return startPath;
|
|
66
|
+
const absoluteGitDir = path.isAbsolute(gitCommonDir) ? gitCommonDir : path.resolve(startPath, gitCommonDir);
|
|
67
|
+
return path.dirname(absoluteGitDir);
|
|
68
|
+
} catch {
|
|
69
|
+
return startPath;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
var storageRootCache = /* @__PURE__ */ new Map();
|
|
73
|
+
function resolveStorageRoot(startPath) {
|
|
74
|
+
const cached = storageRootCache.get(startPath);
|
|
75
|
+
if (cached !== void 0) {
|
|
76
|
+
return cached;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const toplevel = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
80
|
+
cwd: startPath,
|
|
81
|
+
encoding: "utf-8",
|
|
82
|
+
timeout: 5e3
|
|
83
|
+
}).trim();
|
|
84
|
+
const commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
85
|
+
cwd: startPath,
|
|
86
|
+
encoding: "utf-8",
|
|
87
|
+
timeout: 5e3
|
|
88
|
+
}).trim();
|
|
89
|
+
const absoluteCommonDir = path.resolve(toplevel, commonDir);
|
|
90
|
+
const mainRoot = path.dirname(absoluteCommonDir);
|
|
91
|
+
storageRootCache.set(startPath, mainRoot);
|
|
92
|
+
return mainRoot;
|
|
65
93
|
} catch {
|
|
94
|
+
storageRootCache.set(startPath, startPath);
|
|
66
95
|
return startPath;
|
|
67
96
|
}
|
|
68
97
|
}
|
|
@@ -102,7 +131,7 @@ function getRecentSessions(allSessions, count = RECENT_SESSION_COUNT) {
|
|
|
102
131
|
|
|
103
132
|
// ../../packages/shared/src/storage.ts
|
|
104
133
|
import fs from "fs";
|
|
105
|
-
import
|
|
134
|
+
import path2 from "path";
|
|
106
135
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
107
136
|
var STORAGE_DIR = ".keepgoing";
|
|
108
137
|
var META_FILE = "meta.json";
|
|
@@ -114,10 +143,11 @@ var KeepGoingWriter = class {
|
|
|
114
143
|
stateFilePath;
|
|
115
144
|
metaFilePath;
|
|
116
145
|
constructor(workspacePath) {
|
|
117
|
-
|
|
118
|
-
this.
|
|
119
|
-
this.
|
|
120
|
-
this.
|
|
146
|
+
const mainRoot = resolveStorageRoot(workspacePath);
|
|
147
|
+
this.storagePath = path2.join(mainRoot, STORAGE_DIR);
|
|
148
|
+
this.sessionsFilePath = path2.join(this.storagePath, SESSIONS_FILE);
|
|
149
|
+
this.stateFilePath = path2.join(this.storagePath, STATE_FILE);
|
|
150
|
+
this.metaFilePath = path2.join(this.storagePath, META_FILE);
|
|
121
151
|
}
|
|
122
152
|
ensureDir() {
|
|
123
153
|
if (!fs.existsSync(this.storagePath)) {
|
|
@@ -182,7 +212,7 @@ var KeepGoingWriter = class {
|
|
|
182
212
|
|
|
183
213
|
// ../../packages/shared/src/decisionStorage.ts
|
|
184
214
|
import fs2 from "fs";
|
|
185
|
-
import
|
|
215
|
+
import path3 from "path";
|
|
186
216
|
|
|
187
217
|
// ../../packages/shared/src/featureGate.ts
|
|
188
218
|
var DefaultFeatureGate = class {
|
|
@@ -192,9 +222,152 @@ var DefaultFeatureGate = class {
|
|
|
192
222
|
};
|
|
193
223
|
var currentGate = new DefaultFeatureGate();
|
|
194
224
|
|
|
195
|
-
// src/
|
|
225
|
+
// ../../packages/shared/src/license.ts
|
|
226
|
+
import crypto from "crypto";
|
|
196
227
|
import fs3 from "fs";
|
|
197
|
-
import
|
|
228
|
+
import os from "os";
|
|
229
|
+
import path4 from "path";
|
|
230
|
+
var LICENSE_FILE = "license.json";
|
|
231
|
+
var DEVICE_ID_FILE = "device-id";
|
|
232
|
+
function getGlobalLicenseDir() {
|
|
233
|
+
return path4.join(os.homedir(), ".keepgoing");
|
|
234
|
+
}
|
|
235
|
+
function getGlobalLicensePath() {
|
|
236
|
+
return path4.join(getGlobalLicenseDir(), LICENSE_FILE);
|
|
237
|
+
}
|
|
238
|
+
function getDeviceId() {
|
|
239
|
+
const dir = getGlobalLicenseDir();
|
|
240
|
+
const filePath = path4.join(dir, DEVICE_ID_FILE);
|
|
241
|
+
try {
|
|
242
|
+
const existing = fs3.readFileSync(filePath, "utf-8").trim();
|
|
243
|
+
if (existing) return existing;
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
const id = crypto.randomUUID();
|
|
247
|
+
if (!fs3.existsSync(dir)) {
|
|
248
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
249
|
+
}
|
|
250
|
+
fs3.writeFileSync(filePath, id, "utf-8");
|
|
251
|
+
return id;
|
|
252
|
+
}
|
|
253
|
+
function readLicenseCache() {
|
|
254
|
+
const licensePath = getGlobalLicensePath();
|
|
255
|
+
try {
|
|
256
|
+
if (!fs3.existsSync(licensePath)) {
|
|
257
|
+
return void 0;
|
|
258
|
+
}
|
|
259
|
+
const raw = fs3.readFileSync(licensePath, "utf-8");
|
|
260
|
+
return JSON.parse(raw);
|
|
261
|
+
} catch {
|
|
262
|
+
return void 0;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function writeLicenseCache(cache) {
|
|
266
|
+
const dirPath = getGlobalLicenseDir();
|
|
267
|
+
if (!fs3.existsSync(dirPath)) {
|
|
268
|
+
fs3.mkdirSync(dirPath, { recursive: true });
|
|
269
|
+
}
|
|
270
|
+
const licensePath = path4.join(dirPath, LICENSE_FILE);
|
|
271
|
+
fs3.writeFileSync(licensePath, JSON.stringify(cache, null, 2), "utf-8");
|
|
272
|
+
}
|
|
273
|
+
function deleteLicenseCache() {
|
|
274
|
+
const licensePath = getGlobalLicensePath();
|
|
275
|
+
try {
|
|
276
|
+
if (fs3.existsSync(licensePath)) {
|
|
277
|
+
fs3.unlinkSync(licensePath);
|
|
278
|
+
}
|
|
279
|
+
} catch {
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function isCachedLicenseValid(cache) {
|
|
283
|
+
return cache?.status === "active";
|
|
284
|
+
}
|
|
285
|
+
var REVALIDATION_THRESHOLD_MS = 24 * 60 * 60 * 1e3;
|
|
286
|
+
|
|
287
|
+
// ../../packages/shared/src/licenseClient.ts
|
|
288
|
+
var BASE_URL = "https://api.lemonsqueezy.com/v1/licenses";
|
|
289
|
+
var REQUEST_TIMEOUT_MS = 15e3;
|
|
290
|
+
var EXPECTED_STORE_ID = 301555;
|
|
291
|
+
var EXPECTED_PRODUCT_ID = 864311;
|
|
292
|
+
function fetchWithTimeout(url, init) {
|
|
293
|
+
const controller = new AbortController();
|
|
294
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
295
|
+
return fetch(url, { ...init, signal: controller.signal }).finally(() => clearTimeout(timer));
|
|
296
|
+
}
|
|
297
|
+
function validateProductIdentity(meta) {
|
|
298
|
+
if (!meta) return "License response missing product metadata.";
|
|
299
|
+
if (meta.store_id !== EXPECTED_STORE_ID || meta.product_id !== EXPECTED_PRODUCT_ID) {
|
|
300
|
+
return "This license key does not belong to KeepGoing.";
|
|
301
|
+
}
|
|
302
|
+
return void 0;
|
|
303
|
+
}
|
|
304
|
+
async function safeJson(res) {
|
|
305
|
+
try {
|
|
306
|
+
const text = await res.text();
|
|
307
|
+
return JSON.parse(text);
|
|
308
|
+
} catch {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async function activateLicense(licenseKey, instanceName, options) {
|
|
313
|
+
try {
|
|
314
|
+
const res = await fetchWithTimeout(`${BASE_URL}/activate`, {
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
317
|
+
body: new URLSearchParams({ license_key: licenseKey, instance_name: instanceName })
|
|
318
|
+
});
|
|
319
|
+
const data = await safeJson(res);
|
|
320
|
+
if (!res.ok || !data?.activated) {
|
|
321
|
+
return { valid: false, error: data?.error || `Activation failed (${res.status})` };
|
|
322
|
+
}
|
|
323
|
+
if (!options?.allowTestMode && data.license_key?.test_mode) {
|
|
324
|
+
if (data.license_key?.key && data.instance?.id) {
|
|
325
|
+
await deactivateLicense(data.license_key.key, data.instance.id);
|
|
326
|
+
}
|
|
327
|
+
return { valid: false, error: "This is a test license key. Please use a production license key from your purchase confirmation." };
|
|
328
|
+
}
|
|
329
|
+
if (!options?.allowTestMode) {
|
|
330
|
+
const productError = validateProductIdentity(data.meta);
|
|
331
|
+
if (productError) {
|
|
332
|
+
if (data.license_key?.key && data.instance?.id) {
|
|
333
|
+
await deactivateLicense(data.license_key.key, data.instance.id);
|
|
334
|
+
}
|
|
335
|
+
return { valid: false, error: productError };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
valid: true,
|
|
340
|
+
licenseKey: data.license_key?.key,
|
|
341
|
+
instanceId: data.instance?.id,
|
|
342
|
+
customerName: data.meta?.customer_name,
|
|
343
|
+
productName: data.meta?.product_name
|
|
344
|
+
};
|
|
345
|
+
} catch (err) {
|
|
346
|
+
const message = err instanceof Error && err.name === "AbortError" ? "Request timed out. Please check your network connection and try again." : err instanceof Error ? err.message : "Network error";
|
|
347
|
+
return { valid: false, error: message };
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async function deactivateLicense(licenseKey, instanceId) {
|
|
351
|
+
try {
|
|
352
|
+
const res = await fetchWithTimeout(`${BASE_URL}/deactivate`, {
|
|
353
|
+
method: "POST",
|
|
354
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
355
|
+
body: new URLSearchParams({ license_key: licenseKey, instance_id: instanceId })
|
|
356
|
+
});
|
|
357
|
+
const data = await safeJson(res);
|
|
358
|
+
if (!res.ok || !data?.deactivated) {
|
|
359
|
+
return { deactivated: false, error: data?.error || `Deactivation failed (${res.status})` };
|
|
360
|
+
}
|
|
361
|
+
return { deactivated: true };
|
|
362
|
+
} catch (err) {
|
|
363
|
+
const message = err instanceof Error && err.name === "AbortError" ? "Request timed out. Please check your network connection and try again." : err instanceof Error ? err.message : "Network error";
|
|
364
|
+
return { deactivated: false, error: message };
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/storage.ts
|
|
369
|
+
import fs4 from "fs";
|
|
370
|
+
import path5 from "path";
|
|
198
371
|
var STORAGE_DIR2 = ".keepgoing";
|
|
199
372
|
var META_FILE2 = "meta.json";
|
|
200
373
|
var SESSIONS_FILE2 = "sessions.json";
|
|
@@ -205,13 +378,13 @@ var KeepGoingReader = class {
|
|
|
205
378
|
sessionsFilePath;
|
|
206
379
|
stateFilePath;
|
|
207
380
|
constructor(workspacePath) {
|
|
208
|
-
this.storagePath =
|
|
209
|
-
this.metaFilePath =
|
|
210
|
-
this.sessionsFilePath =
|
|
211
|
-
this.stateFilePath =
|
|
381
|
+
this.storagePath = path5.join(workspacePath, STORAGE_DIR2);
|
|
382
|
+
this.metaFilePath = path5.join(this.storagePath, META_FILE2);
|
|
383
|
+
this.sessionsFilePath = path5.join(this.storagePath, SESSIONS_FILE2);
|
|
384
|
+
this.stateFilePath = path5.join(this.storagePath, STATE_FILE2);
|
|
212
385
|
}
|
|
213
386
|
exists() {
|
|
214
|
-
return
|
|
387
|
+
return fs4.existsSync(this.storagePath);
|
|
215
388
|
}
|
|
216
389
|
getState() {
|
|
217
390
|
return this.readJsonFile(this.stateFilePath);
|
|
@@ -249,8 +422,8 @@ var KeepGoingReader = class {
|
|
|
249
422
|
}
|
|
250
423
|
readJsonFile(filePath) {
|
|
251
424
|
try {
|
|
252
|
-
if (!
|
|
253
|
-
const raw =
|
|
425
|
+
if (!fs4.existsSync(filePath)) return void 0;
|
|
426
|
+
const raw = fs4.readFileSync(filePath, "utf-8");
|
|
254
427
|
return JSON.parse(raw);
|
|
255
428
|
} catch {
|
|
256
429
|
return void 0;
|
|
@@ -305,7 +478,96 @@ function renderNoData() {
|
|
|
305
478
|
);
|
|
306
479
|
}
|
|
307
480
|
|
|
481
|
+
// src/updateCheck.ts
|
|
482
|
+
import { spawn } from "child_process";
|
|
483
|
+
import { readFileSync, existsSync } from "fs";
|
|
484
|
+
import path6 from "path";
|
|
485
|
+
import os2 from "os";
|
|
486
|
+
var CLI_VERSION = "0.2.1";
|
|
487
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@keepgoingdev/cli/latest";
|
|
488
|
+
var FETCH_TIMEOUT_MS = 5e3;
|
|
489
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
490
|
+
var CACHE_DIR = path6.join(os2.homedir(), ".keepgoing");
|
|
491
|
+
var CACHE_PATH = path6.join(CACHE_DIR, "update-check.json");
|
|
492
|
+
function isNewerVersion(current, latest) {
|
|
493
|
+
const cur = current.split(".").map(Number);
|
|
494
|
+
const lat = latest.split(".").map(Number);
|
|
495
|
+
for (let i = 0; i < 3; i++) {
|
|
496
|
+
if ((lat[i] ?? 0) > (cur[i] ?? 0)) return true;
|
|
497
|
+
if ((lat[i] ?? 0) < (cur[i] ?? 0)) return false;
|
|
498
|
+
}
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
function getCachedUpdateInfo() {
|
|
502
|
+
try {
|
|
503
|
+
if (!existsSync(CACHE_PATH)) return null;
|
|
504
|
+
const raw = readFileSync(CACHE_PATH, "utf-8");
|
|
505
|
+
const cache = JSON.parse(raw);
|
|
506
|
+
if (!cache.latest || !cache.checkedAt) return null;
|
|
507
|
+
const age = Date.now() - new Date(cache.checkedAt).getTime();
|
|
508
|
+
if (age > CHECK_INTERVAL_MS) return null;
|
|
509
|
+
return {
|
|
510
|
+
current: CLI_VERSION,
|
|
511
|
+
latest: cache.latest,
|
|
512
|
+
updateAvailable: isNewerVersion(CLI_VERSION, cache.latest)
|
|
513
|
+
};
|
|
514
|
+
} catch {
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function spawnBackgroundCheck() {
|
|
519
|
+
try {
|
|
520
|
+
if (existsSync(CACHE_PATH)) {
|
|
521
|
+
const raw = readFileSync(CACHE_PATH, "utf-8");
|
|
522
|
+
const cache = JSON.parse(raw);
|
|
523
|
+
const age = Date.now() - new Date(cache.checkedAt).getTime();
|
|
524
|
+
if (age < CHECK_INTERVAL_MS) return;
|
|
525
|
+
}
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
const script = `
|
|
529
|
+
const https = require('https');
|
|
530
|
+
const fs = require('fs');
|
|
531
|
+
const path = require('path');
|
|
532
|
+
const os = require('os');
|
|
533
|
+
|
|
534
|
+
const url = ${JSON.stringify(NPM_REGISTRY_URL)};
|
|
535
|
+
const cacheDir = path.join(os.homedir(), '.keepgoing');
|
|
536
|
+
const cachePath = path.join(cacheDir, 'update-check.json');
|
|
537
|
+
const currentVersion = ${JSON.stringify(CLI_VERSION)};
|
|
538
|
+
|
|
539
|
+
const req = https.get(url, { timeout: ${FETCH_TIMEOUT_MS} }, (res) => {
|
|
540
|
+
let data = '';
|
|
541
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
542
|
+
res.on('end', () => {
|
|
543
|
+
try {
|
|
544
|
+
const latest = JSON.parse(data).version;
|
|
545
|
+
if (!latest) process.exit(0);
|
|
546
|
+
if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
|
|
547
|
+
fs.writeFileSync(cachePath, JSON.stringify({
|
|
548
|
+
latest,
|
|
549
|
+
current: currentVersion,
|
|
550
|
+
checkedAt: new Date().toISOString(),
|
|
551
|
+
}));
|
|
552
|
+
} catch {}
|
|
553
|
+
process.exit(0);
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
req.on('error', () => process.exit(0));
|
|
557
|
+
req.on('timeout', () => { req.destroy(); process.exit(0); });
|
|
558
|
+
`;
|
|
559
|
+
const child = spawn(process.execPath, ["-e", script], {
|
|
560
|
+
detached: true,
|
|
561
|
+
stdio: "ignore",
|
|
562
|
+
env: { ...process.env }
|
|
563
|
+
});
|
|
564
|
+
child.unref();
|
|
565
|
+
}
|
|
566
|
+
|
|
308
567
|
// src/commands/status.ts
|
|
568
|
+
var RESET2 = "\x1B[0m";
|
|
569
|
+
var BOLD2 = "\x1B[1m";
|
|
570
|
+
var DIM2 = "\x1B[2m";
|
|
309
571
|
async function statusCommand(opts) {
|
|
310
572
|
const reader = new KeepGoingReader(opts.cwd);
|
|
311
573
|
if (!reader.exists()) {
|
|
@@ -333,11 +595,16 @@ async function statusCommand(opts) {
|
|
|
333
595
|
(Date.now() - new Date(lastSession.timestamp).getTime()) / (1e3 * 60 * 60 * 24)
|
|
334
596
|
);
|
|
335
597
|
renderCheckpoint(lastSession, daysSince);
|
|
598
|
+
const cached = getCachedUpdateInfo();
|
|
599
|
+
if (cached?.updateAvailable) {
|
|
600
|
+
console.log(`${DIM2}Update available: ${cached.current} \u2192 ${cached.latest}. Run: ${RESET2}${BOLD2}npm install -g @keepgoingdev/cli@latest${RESET2}`);
|
|
601
|
+
}
|
|
602
|
+
spawnBackgroundCheck();
|
|
336
603
|
}
|
|
337
604
|
|
|
338
605
|
// src/commands/save.ts
|
|
339
606
|
import readline from "readline";
|
|
340
|
-
import
|
|
607
|
+
import path7 from "path";
|
|
341
608
|
function prompt(rl, question) {
|
|
342
609
|
return new Promise((resolve) => {
|
|
343
610
|
rl.question(question, (answer) => {
|
|
@@ -381,16 +648,16 @@ async function saveCommand(opts) {
|
|
|
381
648
|
workspaceRoot: opts.cwd,
|
|
382
649
|
source: "manual"
|
|
383
650
|
});
|
|
384
|
-
const projectName =
|
|
651
|
+
const projectName = path7.basename(opts.cwd);
|
|
385
652
|
const writer = new KeepGoingWriter(opts.cwd);
|
|
386
653
|
writer.saveCheckpoint(checkpoint, projectName);
|
|
387
654
|
console.log("Checkpoint saved.");
|
|
388
655
|
}
|
|
389
656
|
|
|
390
657
|
// src/commands/hook.ts
|
|
391
|
-
import
|
|
392
|
-
import
|
|
393
|
-
import
|
|
658
|
+
import fs5 from "fs";
|
|
659
|
+
import path8 from "path";
|
|
660
|
+
import os3 from "os";
|
|
394
661
|
var HOOK_MARKER_START = "# keepgoing-hook-start";
|
|
395
662
|
var HOOK_MARKER_END = "# keepgoing-hook-end";
|
|
396
663
|
var ZSH_HOOK = `${HOOK_MARKER_START}
|
|
@@ -416,12 +683,12 @@ fi
|
|
|
416
683
|
${HOOK_MARKER_END}`;
|
|
417
684
|
function detectShellRcFile() {
|
|
418
685
|
const shellEnv = process.env["SHELL"] ?? "";
|
|
419
|
-
const home =
|
|
686
|
+
const home = os3.homedir();
|
|
420
687
|
if (shellEnv.endsWith("zsh")) {
|
|
421
|
-
return { shell: "zsh", rcFile:
|
|
688
|
+
return { shell: "zsh", rcFile: path8.join(home, ".zshrc") };
|
|
422
689
|
}
|
|
423
690
|
if (shellEnv.endsWith("bash")) {
|
|
424
|
-
return { shell: "bash", rcFile:
|
|
691
|
+
return { shell: "bash", rcFile: path8.join(home, ".bashrc") };
|
|
425
692
|
}
|
|
426
693
|
return void 0;
|
|
427
694
|
}
|
|
@@ -437,14 +704,14 @@ function hookInstallCommand() {
|
|
|
437
704
|
const hookBlock = shell === "zsh" ? ZSH_HOOK : BASH_HOOK;
|
|
438
705
|
let existing = "";
|
|
439
706
|
try {
|
|
440
|
-
existing =
|
|
707
|
+
existing = fs5.readFileSync(rcFile, "utf-8");
|
|
441
708
|
} catch {
|
|
442
709
|
}
|
|
443
710
|
if (existing.includes(HOOK_MARKER_START)) {
|
|
444
711
|
console.log(`KeepGoing hook is already installed in ${rcFile}.`);
|
|
445
712
|
return;
|
|
446
713
|
}
|
|
447
|
-
|
|
714
|
+
fs5.appendFileSync(rcFile, `
|
|
448
715
|
${hookBlock}
|
|
449
716
|
`, "utf-8");
|
|
450
717
|
console.log(`KeepGoing hook installed in ${rcFile}.`);
|
|
@@ -463,7 +730,7 @@ function hookUninstallCommand() {
|
|
|
463
730
|
const { rcFile } = detected;
|
|
464
731
|
let existing = "";
|
|
465
732
|
try {
|
|
466
|
-
existing =
|
|
733
|
+
existing = fs5.readFileSync(rcFile, "utf-8");
|
|
467
734
|
} catch {
|
|
468
735
|
console.log(`${rcFile} not found \u2014 nothing to remove.`);
|
|
469
736
|
return;
|
|
@@ -479,26 +746,78 @@ function hookUninstallCommand() {
|
|
|
479
746
|
"g"
|
|
480
747
|
);
|
|
481
748
|
const updated = existing.replace(pattern, "").replace(/\n{3,}/g, "\n\n");
|
|
482
|
-
|
|
749
|
+
fs5.writeFileSync(rcFile, updated, "utf-8");
|
|
483
750
|
console.log(`KeepGoing hook removed from ${rcFile}.`);
|
|
484
751
|
console.log(`Reload your shell config to deactivate it:
|
|
485
752
|
`);
|
|
486
753
|
console.log(` source ${rcFile}`);
|
|
487
754
|
}
|
|
488
755
|
|
|
756
|
+
// src/commands/activate.ts
|
|
757
|
+
async function activateCommand({ licenseKey }) {
|
|
758
|
+
if (!licenseKey) {
|
|
759
|
+
console.error("Usage: keepgoing activate <license-key>");
|
|
760
|
+
process.exit(1);
|
|
761
|
+
}
|
|
762
|
+
const existing = readLicenseCache();
|
|
763
|
+
if (isCachedLicenseValid(existing)) {
|
|
764
|
+
const who2 = existing.customerName ? ` (${existing.customerName})` : "";
|
|
765
|
+
console.log(`Pro license is already active${who2}.`);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
console.log("Activating license...");
|
|
769
|
+
const result = await activateLicense(licenseKey, getDeviceId());
|
|
770
|
+
if (!result.valid) {
|
|
771
|
+
console.error(`Activation failed: ${result.error ?? "unknown error"}`);
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
775
|
+
writeLicenseCache({
|
|
776
|
+
licenseKey: result.licenseKey || licenseKey,
|
|
777
|
+
instanceId: result.instanceId || getDeviceId(),
|
|
778
|
+
status: "active",
|
|
779
|
+
lastValidatedAt: now,
|
|
780
|
+
activatedAt: now,
|
|
781
|
+
customerName: result.customerName,
|
|
782
|
+
productName: result.productName
|
|
783
|
+
});
|
|
784
|
+
const who = result.customerName ? ` Welcome, ${result.customerName}!` : "";
|
|
785
|
+
console.log(`Pro license activated successfully.${who}`);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/commands/deactivate.ts
|
|
789
|
+
async function deactivateCommand() {
|
|
790
|
+
const cache = readLicenseCache();
|
|
791
|
+
if (!cache) {
|
|
792
|
+
console.log("No active license found on this device.");
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
console.log("Deactivating license...");
|
|
796
|
+
const result = await deactivateLicense(cache.licenseKey, cache.instanceId);
|
|
797
|
+
deleteLicenseCache();
|
|
798
|
+
if (!result.deactivated) {
|
|
799
|
+
console.error(`License cleared locally, but remote deactivation failed: ${result.error ?? "unknown error"}`);
|
|
800
|
+
process.exit(1);
|
|
801
|
+
}
|
|
802
|
+
console.log("Pro license deactivated successfully. The activation slot has been freed.");
|
|
803
|
+
}
|
|
804
|
+
|
|
489
805
|
// src/index.ts
|
|
490
806
|
var HELP_TEXT = `
|
|
491
807
|
keepgoing: resume side projects without the mental friction
|
|
492
808
|
|
|
493
809
|
Usage:
|
|
494
|
-
keepgoing status
|
|
495
|
-
keepgoing save
|
|
496
|
-
keepgoing hook
|
|
810
|
+
keepgoing status Show the last checkpoint for this project
|
|
811
|
+
keepgoing save Save a new checkpoint interactively
|
|
812
|
+
keepgoing hook Manage the shell hook
|
|
813
|
+
keepgoing activate <key> Activate a Pro license on this device
|
|
814
|
+
keepgoing deactivate Deactivate the Pro license from this device
|
|
497
815
|
|
|
498
816
|
Options:
|
|
499
817
|
--cwd <path> Override the working directory (default: current directory)
|
|
500
818
|
--json Output raw JSON (status only)
|
|
501
819
|
--quiet Output a single summary line (status only)
|
|
820
|
+
-v, --version Show the CLI version
|
|
502
821
|
-h, --help Show this help text
|
|
503
822
|
|
|
504
823
|
Hook subcommands:
|
|
@@ -520,6 +839,8 @@ function parseArgs(argv) {
|
|
|
520
839
|
json = true;
|
|
521
840
|
} else if (arg === "--quiet") {
|
|
522
841
|
quiet = true;
|
|
842
|
+
} else if (arg === "-v" || arg === "--version") {
|
|
843
|
+
command = "version";
|
|
523
844
|
} else if (arg === "-h" || arg === "--help") {
|
|
524
845
|
command = "help";
|
|
525
846
|
} else if (!command) {
|
|
@@ -552,6 +873,15 @@ async function main() {
|
|
|
552
873
|
process.exit(1);
|
|
553
874
|
}
|
|
554
875
|
break;
|
|
876
|
+
case "version":
|
|
877
|
+
console.log(`keepgoing v${"0.2.1"}`);
|
|
878
|
+
break;
|
|
879
|
+
case "activate":
|
|
880
|
+
await activateCommand({ licenseKey: subcommand });
|
|
881
|
+
break;
|
|
882
|
+
case "deactivate":
|
|
883
|
+
await deactivateCommand();
|
|
884
|
+
break;
|
|
555
885
|
case "help":
|
|
556
886
|
case "":
|
|
557
887
|
console.log(HELP_TEXT);
|