@keepgoingdev/cli 0.2.1 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +5 -3
  2. package/dist/index.js +272 -68
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -50,17 +50,19 @@ Git branch and touched files are auto-detected from the workspace.
50
50
 
51
51
  Install a shell hook that runs `keepgoing status --quiet` automatically whenever you `cd` into a directory that contains `.keepgoing/`.
52
52
 
53
- Supports **zsh** and **bash**. Detected from `$SHELL`.
53
+ Supports **zsh**, **bash**, and **fish**. Detected from `$SHELL`.
54
54
 
55
55
  ```bash
56
56
  keepgoing hook install
57
57
  # → Reload your shell:
58
- source ~/.zshrc # or ~/.bashrc
58
+ source ~/.zshrc # zsh
59
+ source ~/.bashrc # bash
60
+ source ~/.config/fish/config.fish # fish
59
61
  ```
60
62
 
61
63
  ### `keepgoing hook uninstall`
62
64
 
63
- Remove the shell hook from your `~/.zshrc` or `~/.bashrc`.
65
+ Remove the shell hook from your shell config file.
64
66
 
65
67
  ```bash
66
68
  keepgoing hook uninstall
package/dist/index.js CHANGED
@@ -57,14 +57,12 @@ import { promisify } from "util";
57
57
  var execFileAsync = promisify(execFile);
58
58
  function findGitRoot(startPath) {
59
59
  try {
60
- const gitCommonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
60
+ const toplevel = execFileSync("git", ["rev-parse", "--show-toplevel"], {
61
61
  cwd: startPath,
62
62
  encoding: "utf-8",
63
63
  timeout: 5e3
64
64
  }).trim();
65
- if (!gitCommonDir) return startPath;
66
- const absoluteGitDir = path.isAbsolute(gitCommonDir) ? gitCommonDir : path.resolve(startPath, gitCommonDir);
67
- return path.dirname(absoluteGitDir);
65
+ return toplevel || startPath;
68
66
  } catch {
69
67
  return startPath;
70
68
  }
@@ -132,22 +130,33 @@ function getRecentSessions(allSessions, count = RECENT_SESSION_COUNT) {
132
130
  // ../../packages/shared/src/storage.ts
133
131
  import fs from "fs";
134
132
  import path2 from "path";
135
- import { randomUUID as randomUUID2 } from "crypto";
133
+ import { randomUUID as randomUUID2, createHash } from "crypto";
136
134
  var STORAGE_DIR = ".keepgoing";
137
135
  var META_FILE = "meta.json";
138
136
  var SESSIONS_FILE = "sessions.json";
139
137
  var STATE_FILE = "state.json";
138
+ var CURRENT_TASKS_FILE = "current-tasks.json";
139
+ var STALE_SESSION_MS = 2 * 60 * 60 * 1e3;
140
+ function pruneStaleTasks(tasks) {
141
+ const now = Date.now();
142
+ return tasks.filter((t) => {
143
+ const updatedAt = new Date(t.updatedAt).getTime();
144
+ return !isNaN(updatedAt) && now - updatedAt < STALE_SESSION_MS;
145
+ });
146
+ }
140
147
  var KeepGoingWriter = class {
141
148
  storagePath;
142
149
  sessionsFilePath;
143
150
  stateFilePath;
144
151
  metaFilePath;
152
+ currentTasksFilePath;
145
153
  constructor(workspacePath) {
146
154
  const mainRoot = resolveStorageRoot(workspacePath);
147
155
  this.storagePath = path2.join(mainRoot, STORAGE_DIR);
148
156
  this.sessionsFilePath = path2.join(this.storagePath, SESSIONS_FILE);
149
157
  this.stateFilePath = path2.join(this.storagePath, STATE_FILE);
150
158
  this.metaFilePath = path2.join(this.storagePath, META_FILE);
159
+ this.currentTasksFilePath = path2.join(this.storagePath, CURRENT_TASKS_FILE);
151
160
  }
152
161
  ensureDir() {
153
162
  if (!fs.existsSync(this.storagePath)) {
@@ -208,82 +217,214 @@ var KeepGoingWriter = class {
208
217
  }
209
218
  fs.writeFileSync(this.metaFilePath, JSON.stringify(meta, null, 2), "utf-8");
210
219
  }
220
+ // ---------------------------------------------------------------------------
221
+ // Multi-session API
222
+ // ---------------------------------------------------------------------------
223
+ /** Read all current tasks from current-tasks.json. Auto-prunes stale sessions. */
224
+ readCurrentTasks() {
225
+ try {
226
+ if (fs.existsSync(this.currentTasksFilePath)) {
227
+ const raw = JSON.parse(fs.readFileSync(this.currentTasksFilePath, "utf-8"));
228
+ const tasks = Array.isArray(raw) ? raw : raw.tasks ?? [];
229
+ return this.pruneStale(tasks);
230
+ }
231
+ } catch {
232
+ }
233
+ return [];
234
+ }
235
+ /**
236
+ * Upsert a session task by sessionId into current-tasks.json.
237
+ * If no sessionId is present on the task, generates one.
238
+ */
239
+ upsertSession(update) {
240
+ this.ensureDir();
241
+ this.upsertSessionCore(update);
242
+ }
243
+ /** Core upsert logic: merges the update into current-tasks.json and returns the pruned task list. */
244
+ upsertSessionCore(update) {
245
+ this.ensureDir();
246
+ const sessionId = update.sessionId || generateSessionId(update);
247
+ const tasks = this.readAllTasksRaw();
248
+ const existingIdx = tasks.findIndex((t) => t.sessionId === sessionId);
249
+ let merged;
250
+ if (existingIdx >= 0) {
251
+ const existing = tasks[existingIdx];
252
+ merged = { ...existing, ...update, sessionId };
253
+ tasks[existingIdx] = merged;
254
+ } else {
255
+ merged = { ...update, sessionId };
256
+ tasks.push(merged);
257
+ }
258
+ const pruned = this.pruneStale(tasks);
259
+ this.writeTasksFile(pruned);
260
+ return pruned;
261
+ }
262
+ /** Remove a specific session by ID. */
263
+ removeSession(sessionId) {
264
+ const tasks = this.readAllTasksRaw().filter((t) => t.sessionId !== sessionId);
265
+ this.writeTasksFile(tasks);
266
+ }
267
+ /** Get all active sessions (sessionActive=true and within stale threshold). */
268
+ getActiveSessions() {
269
+ return this.readCurrentTasks().filter((t) => t.sessionActive);
270
+ }
271
+ /** Get a specific session by ID. */
272
+ getSession(sessionId) {
273
+ return this.readCurrentTasks().find((t) => t.sessionId === sessionId);
274
+ }
275
+ // ---------------------------------------------------------------------------
276
+ // Private helpers
277
+ // ---------------------------------------------------------------------------
278
+ readAllTasksRaw() {
279
+ try {
280
+ if (fs.existsSync(this.currentTasksFilePath)) {
281
+ const raw = JSON.parse(fs.readFileSync(this.currentTasksFilePath, "utf-8"));
282
+ return Array.isArray(raw) ? [...raw] : [...raw.tasks ?? []];
283
+ }
284
+ } catch {
285
+ }
286
+ return [];
287
+ }
288
+ pruneStale(tasks) {
289
+ return pruneStaleTasks(tasks);
290
+ }
291
+ writeTasksFile(tasks) {
292
+ const data = { version: 1, tasks };
293
+ fs.writeFileSync(this.currentTasksFilePath, JSON.stringify(data, null, 2), "utf-8");
294
+ }
211
295
  };
296
+ function generateSessionId(context) {
297
+ const parts = [
298
+ context.worktreePath || context.workspaceRoot || "",
299
+ context.agentLabel || "",
300
+ context.branch || ""
301
+ ].filter(Boolean);
302
+ if (parts.length === 0) {
303
+ return randomUUID2();
304
+ }
305
+ const hash = createHash("sha256").update(parts.join("|")).digest("hex").slice(0, 12);
306
+ return `ses_${hash}`;
307
+ }
212
308
 
213
309
  // ../../packages/shared/src/decisionStorage.ts
214
- import fs2 from "fs";
215
- import path3 from "path";
216
-
217
- // ../../packages/shared/src/featureGate.ts
218
- var DefaultFeatureGate = class {
219
- isEnabled(_feature) {
220
- return true;
221
- }
222
- };
223
- var currentGate = new DefaultFeatureGate();
310
+ import fs3 from "fs";
311
+ import path4 from "path";
224
312
 
225
313
  // ../../packages/shared/src/license.ts
226
314
  import crypto from "crypto";
227
- import fs3 from "fs";
315
+ import fs2 from "fs";
228
316
  import os from "os";
229
- import path4 from "path";
317
+ import path3 from "path";
230
318
  var LICENSE_FILE = "license.json";
231
319
  var DEVICE_ID_FILE = "device-id";
232
320
  function getGlobalLicenseDir() {
233
- return path4.join(os.homedir(), ".keepgoing");
321
+ return path3.join(os.homedir(), ".keepgoing");
234
322
  }
235
323
  function getGlobalLicensePath() {
236
- return path4.join(getGlobalLicenseDir(), LICENSE_FILE);
324
+ return path3.join(getGlobalLicenseDir(), LICENSE_FILE);
237
325
  }
238
326
  function getDeviceId() {
239
327
  const dir = getGlobalLicenseDir();
240
- const filePath = path4.join(dir, DEVICE_ID_FILE);
328
+ const filePath = path3.join(dir, DEVICE_ID_FILE);
241
329
  try {
242
- const existing = fs3.readFileSync(filePath, "utf-8").trim();
330
+ const existing = fs2.readFileSync(filePath, "utf-8").trim();
243
331
  if (existing) return existing;
244
332
  } catch {
245
333
  }
246
334
  const id = crypto.randomUUID();
247
- if (!fs3.existsSync(dir)) {
248
- fs3.mkdirSync(dir, { recursive: true });
335
+ if (!fs2.existsSync(dir)) {
336
+ fs2.mkdirSync(dir, { recursive: true });
249
337
  }
250
- fs3.writeFileSync(filePath, id, "utf-8");
338
+ fs2.writeFileSync(filePath, id, "utf-8");
251
339
  return id;
252
340
  }
253
- function readLicenseCache() {
341
+ var DECISION_DETECTION_VARIANT_ID = 1361527;
342
+ var SESSION_AWARENESS_VARIANT_ID = 1366510;
343
+ var TEST_DECISION_DETECTION_VARIANT_ID = 1345647;
344
+ var TEST_SESSION_AWARENESS_VARIANT_ID = 1365992;
345
+ var VARIANT_FEATURE_MAP = {
346
+ [DECISION_DETECTION_VARIANT_ID]: ["decisions"],
347
+ [SESSION_AWARENESS_VARIANT_ID]: ["session-awareness"],
348
+ [TEST_DECISION_DETECTION_VARIANT_ID]: ["decisions"],
349
+ [TEST_SESSION_AWARENESS_VARIANT_ID]: ["session-awareness"]
350
+ // Future bundle: [BUNDLE_VARIANT_ID]: ['decisions', 'session-awareness'],
351
+ };
352
+ var KNOWN_VARIANT_IDS = new Set(Object.keys(VARIANT_FEATURE_MAP).map(Number));
353
+ function getVariantLabel(variantId) {
354
+ const features = VARIANT_FEATURE_MAP[variantId];
355
+ if (!features) return "Unknown Add-on";
356
+ if (features.includes("decisions") && features.includes("session-awareness")) return "Pro Bundle";
357
+ if (features.includes("decisions")) return "Decision Detection";
358
+ if (features.includes("session-awareness")) return "Session Awareness";
359
+ return "Pro Add-on";
360
+ }
361
+ var _cachedStore;
362
+ var _cacheTimestamp = 0;
363
+ var LICENSE_CACHE_TTL_MS = 2e3;
364
+ function readLicenseStore() {
365
+ const now = Date.now();
366
+ if (_cachedStore && now - _cacheTimestamp < LICENSE_CACHE_TTL_MS) {
367
+ return _cachedStore;
368
+ }
254
369
  const licensePath = getGlobalLicensePath();
370
+ let store;
255
371
  try {
256
- if (!fs3.existsSync(licensePath)) {
257
- return void 0;
372
+ if (!fs2.existsSync(licensePath)) {
373
+ store = { version: 2, licenses: [] };
374
+ } else {
375
+ const raw = fs2.readFileSync(licensePath, "utf-8");
376
+ const data = JSON.parse(raw);
377
+ if (data?.version === 2 && Array.isArray(data.licenses)) {
378
+ store = data;
379
+ } else {
380
+ store = { version: 2, licenses: [] };
381
+ }
258
382
  }
259
- const raw = fs3.readFileSync(licensePath, "utf-8");
260
- return JSON.parse(raw);
261
383
  } catch {
262
- return void 0;
384
+ store = { version: 2, licenses: [] };
263
385
  }
386
+ _cachedStore = store;
387
+ _cacheTimestamp = now;
388
+ return store;
264
389
  }
265
- function writeLicenseCache(cache) {
390
+ function writeLicenseStore(store) {
266
391
  const dirPath = getGlobalLicenseDir();
267
- if (!fs3.existsSync(dirPath)) {
268
- fs3.mkdirSync(dirPath, { recursive: true });
392
+ if (!fs2.existsSync(dirPath)) {
393
+ fs2.mkdirSync(dirPath, { recursive: true });
269
394
  }
270
- const licensePath = path4.join(dirPath, LICENSE_FILE);
271
- fs3.writeFileSync(licensePath, JSON.stringify(cache, null, 2), "utf-8");
395
+ const licensePath = path3.join(dirPath, LICENSE_FILE);
396
+ fs2.writeFileSync(licensePath, JSON.stringify(store, null, 2), "utf-8");
397
+ _cachedStore = store;
398
+ _cacheTimestamp = Date.now();
272
399
  }
273
- function deleteLicenseCache() {
274
- const licensePath = getGlobalLicensePath();
275
- try {
276
- if (fs3.existsSync(licensePath)) {
277
- fs3.unlinkSync(licensePath);
278
- }
279
- } catch {
400
+ function addLicenseEntry(entry) {
401
+ const store = readLicenseStore();
402
+ const idx = store.licenses.findIndex((l) => l.licenseKey === entry.licenseKey);
403
+ if (idx >= 0) {
404
+ store.licenses[idx] = entry;
405
+ } else {
406
+ store.licenses.push(entry);
280
407
  }
408
+ writeLicenseStore(store);
281
409
  }
282
- function isCachedLicenseValid(cache) {
283
- return cache?.status === "active";
410
+ function removeLicenseEntry(licenseKey) {
411
+ const store = readLicenseStore();
412
+ store.licenses = store.licenses.filter((l) => l.licenseKey !== licenseKey);
413
+ writeLicenseStore(store);
414
+ }
415
+ function getActiveLicenses() {
416
+ return readLicenseStore().licenses.filter((l) => l.status === "active");
284
417
  }
285
418
  var REVALIDATION_THRESHOLD_MS = 24 * 60 * 60 * 1e3;
286
419
 
420
+ // ../../packages/shared/src/featureGate.ts
421
+ var DefaultFeatureGate = class {
422
+ isEnabled(_feature) {
423
+ return true;
424
+ }
425
+ };
426
+ var currentGate = new DefaultFeatureGate();
427
+
287
428
  // ../../packages/shared/src/licenseClient.ts
288
429
  var BASE_URL = "https://api.lemonsqueezy.com/v1/licenses";
289
430
  var REQUEST_TIMEOUT_MS = 15e3;
@@ -334,13 +475,21 @@ async function activateLicense(licenseKey, instanceName, options) {
334
475
  }
335
476
  return { valid: false, error: productError };
336
477
  }
478
+ if (data.meta?.variant_id && !KNOWN_VARIANT_IDS.has(data.meta.variant_id)) {
479
+ if (data.license_key?.key && data.instance?.id) {
480
+ await deactivateLicense(data.license_key.key, data.instance.id);
481
+ }
482
+ return { valid: false, error: "This license key is for an unrecognized add-on variant. Please update KeepGoing or contact support." };
483
+ }
337
484
  }
338
485
  return {
339
486
  valid: true,
340
487
  licenseKey: data.license_key?.key,
341
488
  instanceId: data.instance?.id,
342
489
  customerName: data.meta?.customer_name,
343
- productName: data.meta?.product_name
490
+ productName: data.meta?.product_name,
491
+ variantId: data.meta?.variant_id,
492
+ variantName: data.meta?.variant_name
344
493
  };
345
494
  } catch (err) {
346
495
  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";
@@ -483,7 +632,7 @@ import { spawn } from "child_process";
483
632
  import { readFileSync, existsSync } from "fs";
484
633
  import path6 from "path";
485
634
  import os2 from "os";
486
- var CLI_VERSION = "0.2.1";
635
+ var CLI_VERSION = "0.3.1";
487
636
  var NPM_REGISTRY_URL = "https://registry.npmjs.org/@keepgoingdev/cli/latest";
488
637
  var FETCH_TIMEOUT_MS = 5e3;
489
638
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
@@ -681,6 +830,16 @@ if command -v keepgoing >/dev/null 2>&1; then
681
830
  }
682
831
  fi
683
832
  ${HOOK_MARKER_END}`;
833
+ var FISH_HOOK = `${HOOK_MARKER_START}
834
+ # KeepGoing shell hook, auto-injected by 'keepgoing hook install'
835
+ if command -v keepgoing >/dev/null 2>&1
836
+ function __keepgoing_on_pwd_change --on-variable PWD
837
+ if test -d .keepgoing
838
+ keepgoing status --quiet
839
+ end
840
+ end
841
+ end
842
+ ${HOOK_MARKER_END}`;
684
843
  function detectShellRcFile() {
685
844
  const shellEnv = process.env["SHELL"] ?? "";
686
845
  const home = os3.homedir();
@@ -690,18 +849,22 @@ function detectShellRcFile() {
690
849
  if (shellEnv.endsWith("bash")) {
691
850
  return { shell: "bash", rcFile: path8.join(home, ".bashrc") };
692
851
  }
852
+ if (shellEnv.endsWith("fish")) {
853
+ const xdgConfig = process.env["XDG_CONFIG_HOME"] || path8.join(home, ".config");
854
+ return { shell: "fish", rcFile: path8.join(xdgConfig, "fish", "config.fish") };
855
+ }
693
856
  return void 0;
694
857
  }
695
858
  function hookInstallCommand() {
696
859
  const detected = detectShellRcFile();
697
860
  if (!detected) {
698
861
  console.error(
699
- 'Could not detect your shell. Set $SHELL to "zsh" or "bash" and try again.'
862
+ 'Could not detect your shell. Set $SHELL to "zsh", "bash", or "fish" and try again.'
700
863
  );
701
864
  process.exit(1);
702
865
  }
703
866
  const { shell, rcFile } = detected;
704
- const hookBlock = shell === "zsh" ? ZSH_HOOK : BASH_HOOK;
867
+ const hookBlock = shell === "zsh" ? ZSH_HOOK : shell === "fish" ? FISH_HOOK : BASH_HOOK;
705
868
  let existing = "";
706
869
  try {
707
870
  existing = fs5.readFileSync(rcFile, "utf-8");
@@ -723,7 +886,7 @@ function hookUninstallCommand() {
723
886
  const detected = detectShellRcFile();
724
887
  if (!detected) {
725
888
  console.error(
726
- 'Could not detect your shell. Set $SHELL to "zsh" or "bash" and try again.'
889
+ 'Could not detect your shell. Set $SHELL to "zsh", "bash", or "fish" and try again.'
727
890
  );
728
891
  process.exit(1);
729
892
  }
@@ -759,10 +922,14 @@ async function activateCommand({ licenseKey }) {
759
922
  console.error("Usage: keepgoing activate <license-key>");
760
923
  process.exit(1);
761
924
  }
762
- const existing = readLicenseCache();
763
- if (isCachedLicenseValid(existing)) {
764
- const who2 = existing.customerName ? ` (${existing.customerName})` : "";
765
- console.log(`Pro license is already active${who2}.`);
925
+ const store = readLicenseStore();
926
+ const existingForKey = store.licenses.find(
927
+ (l) => l.status === "active" && l.licenseKey === licenseKey
928
+ );
929
+ if (existingForKey) {
930
+ const label2 = getVariantLabel(existingForKey.variantId);
931
+ const who2 = existingForKey.customerName ? ` (${existingForKey.customerName})` : "";
932
+ console.log(`${label2} is already active${who2}.`);
766
933
  return;
767
934
  }
768
935
  console.log("Activating license...");
@@ -771,35 +938,72 @@ async function activateCommand({ licenseKey }) {
771
938
  console.error(`Activation failed: ${result.error ?? "unknown error"}`);
772
939
  process.exit(1);
773
940
  }
941
+ const variantId = result.variantId;
942
+ const existingForVariant = store.licenses.find(
943
+ (l) => l.status === "active" && l.variantId === variantId
944
+ );
945
+ if (existingForVariant) {
946
+ const label2 = getVariantLabel(variantId);
947
+ const who2 = existingForVariant.customerName ? ` (${existingForVariant.customerName})` : "";
948
+ console.log(`${label2} is already active${who2}.`);
949
+ return;
950
+ }
774
951
  const now = (/* @__PURE__ */ new Date()).toISOString();
775
- writeLicenseCache({
952
+ addLicenseEntry({
776
953
  licenseKey: result.licenseKey || licenseKey,
777
954
  instanceId: result.instanceId || getDeviceId(),
778
955
  status: "active",
779
956
  lastValidatedAt: now,
780
957
  activatedAt: now,
958
+ variantId,
781
959
  customerName: result.customerName,
782
- productName: result.productName
960
+ productName: result.productName,
961
+ variantName: result.variantName
783
962
  });
963
+ const label = getVariantLabel(variantId);
784
964
  const who = result.customerName ? ` Welcome, ${result.customerName}!` : "";
785
- console.log(`Pro license activated successfully.${who}`);
965
+ console.log(`${label} activated successfully.${who}`);
786
966
  }
787
967
 
788
968
  // src/commands/deactivate.ts
789
- async function deactivateCommand() {
790
- const cache = readLicenseCache();
791
- if (!cache) {
969
+ async function deactivateCommand(opts) {
970
+ const active = getActiveLicenses();
971
+ if (active.length === 0) {
792
972
  console.log("No active license found on this device.");
793
973
  return;
794
974
  }
975
+ let targets;
976
+ if (opts?.licenseKey) {
977
+ const match = active.find((l) => l.licenseKey === opts.licenseKey);
978
+ if (!match) {
979
+ console.error(`No active license found with key "${opts.licenseKey}".`);
980
+ process.exit(1);
981
+ }
982
+ targets = [match];
983
+ } else if (active.length === 1) {
984
+ targets = active;
985
+ } else {
986
+ console.log("Multiple active licenses found. Specify which to deactivate:");
987
+ console.log(" keepgoing deactivate <license-key>");
988
+ console.log("");
989
+ for (const l of active) {
990
+ const label = getVariantLabel(l.variantId);
991
+ const who = l.customerName ? ` (${l.customerName})` : "";
992
+ console.log(` ${label}${who}: ${l.licenseKey}`);
993
+ }
994
+ return;
995
+ }
795
996
  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);
997
+ for (const entry of targets) {
998
+ const result = await deactivateLicense(entry.licenseKey, entry.instanceId);
999
+ removeLicenseEntry(entry.licenseKey);
1000
+ const label = getVariantLabel(entry.variantId);
1001
+ if (!result.deactivated) {
1002
+ console.error(`${label} license cleared locally, but remote deactivation failed: ${result.error ?? "unknown error"}`);
1003
+ } else {
1004
+ console.log(`${label} license deactivated successfully. The activation slot has been freed.`);
1005
+ }
801
1006
  }
802
- console.log("Pro license deactivated successfully. The activation slot has been freed.");
803
1007
  }
804
1008
 
805
1009
  // src/index.ts
@@ -821,7 +1025,7 @@ Options:
821
1025
  -h, --help Show this help text
822
1026
 
823
1027
  Hook subcommands:
824
- keepgoing hook install Install the shell hook into ~/.zshrc or ~/.bashrc
1028
+ keepgoing hook install Install the shell hook (zsh, bash, fish)
825
1029
  keepgoing hook uninstall Remove the shell hook
826
1030
  `;
827
1031
  function parseArgs(argv) {
@@ -874,13 +1078,13 @@ async function main() {
874
1078
  }
875
1079
  break;
876
1080
  case "version":
877
- console.log(`keepgoing v${"0.2.1"}`);
1081
+ console.log(`keepgoing v${"0.3.1"}`);
878
1082
  break;
879
1083
  case "activate":
880
1084
  await activateCommand({ licenseKey: subcommand });
881
1085
  break;
882
1086
  case "deactivate":
883
- await deactivateCommand();
1087
+ await deactivateCommand({ licenseKey: subcommand || void 0 });
884
1088
  break;
885
1089
  case "help":
886
1090
  case "":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keepgoingdev/cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Terminal CLI for KeepGoing. Resume side projects without the mental friction.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",