@keepgoingdev/cli 0.1.0

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 +93 -0
  2. package/dist/index.js +529 -0
  3. package/package.json +24 -0
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # @keepgoing/cli
2
+
3
+ Terminal CLI for [KeepGoing](https://keepgoing.dev) — resume side projects without the mental friction.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ # Run without installing
9
+ npx @keepgoing/cli status
10
+
11
+ # Install globally
12
+ npm install -g @keepgoing/cli
13
+
14
+ # Or add to a project
15
+ npm install --save-dev @keepgoing/cli
16
+ ```
17
+
18
+ ## Commands
19
+
20
+ ### `keepgoing status`
21
+
22
+ Show the last checkpoint for the current project.
23
+
24
+ ```
25
+ KeepGoing · 12 days ago
26
+
27
+ Summary: Refactored auth middleware to support JWT rotation
28
+ Next step: Implement verifyRefreshToken helper in auth.ts
29
+ Branch: feature/auth-refactor
30
+ Files: auth.ts, middleware.ts, routes/token.ts (+2 more)
31
+ ```
32
+
33
+ Flags:
34
+
35
+ - `--cwd <path>` — override the working directory
36
+ - `--json` — output raw JSON of the last checkpoint (for scripting)
37
+ - `--quiet` — output a single summary line (used by the shell hook)
38
+
39
+ ### `keepgoing save`
40
+
41
+ Save a new checkpoint interactively. Prompts for:
42
+
43
+ 1. What did you work on? (required)
44
+ 2. What's your next step? (required)
45
+ 3. Any blockers? (optional)
46
+
47
+ Git branch and touched files are auto-detected from the workspace.
48
+
49
+ ### `keepgoing hook install`
50
+
51
+ Install a shell hook that runs `keepgoing status --quiet` automatically whenever you `cd` into a directory that contains `.keepgoing/`.
52
+
53
+ Supports **zsh** and **bash**. Detected from `$SHELL`.
54
+
55
+ ```bash
56
+ keepgoing hook install
57
+ # → Reload your shell:
58
+ source ~/.zshrc # or ~/.bashrc
59
+ ```
60
+
61
+ ### `keepgoing hook uninstall`
62
+
63
+ Remove the shell hook from your `~/.zshrc` or `~/.bashrc`.
64
+
65
+ ```bash
66
+ keepgoing hook uninstall
67
+ ```
68
+
69
+ ## Shell Hook Setup
70
+
71
+ The shell hook gives you zero-friction project context when you enter a project directory.
72
+
73
+ After running `keepgoing hook install` and reloading your shell, entering any directory with a `.keepgoing/` folder will automatically print:
74
+
75
+ ```
76
+ KeepGoing · 3 days ago · Refactored auth middleware to support JWT rotation
77
+ ```
78
+
79
+ ## Data Format
80
+
81
+ Reads and writes the same `.keepgoing/` schema used by the VS Code extension:
82
+
83
+ - `.keepgoing/sessions.json` — all checkpoints
84
+ - `.keepgoing/state.json` — last session state
85
+ - `.keepgoing/meta.json` — project metadata
86
+
87
+ ## Development
88
+
89
+ From the monorepo root:
90
+
91
+ ```bash
92
+ npm run build --workspace=apps/cli
93
+ ```
package/dist/index.js ADDED
@@ -0,0 +1,529 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/storage.ts
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import { randomUUID as randomUUID2 } from "crypto";
7
+
8
+ // ../../packages/shared/src/session.ts
9
+ import { randomUUID } from "crypto";
10
+ function generateCheckpointId() {
11
+ return randomUUID();
12
+ }
13
+ function createCheckpoint(fields) {
14
+ return {
15
+ id: generateCheckpointId(),
16
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
17
+ ...fields
18
+ };
19
+ }
20
+
21
+ // ../../packages/shared/src/timeUtils.ts
22
+ function formatRelativeTime(timestamp) {
23
+ const now = Date.now();
24
+ const then = new Date(timestamp).getTime();
25
+ const diffMs = now - then;
26
+ if (isNaN(diffMs)) {
27
+ return "unknown time";
28
+ }
29
+ if (diffMs < 0) {
30
+ return "in the future";
31
+ }
32
+ const seconds = Math.floor(diffMs / 1e3);
33
+ const minutes = Math.floor(seconds / 60);
34
+ const hours = Math.floor(minutes / 60);
35
+ const days = Math.floor(hours / 24);
36
+ const weeks = Math.floor(days / 7);
37
+ const months = Math.floor(days / 30);
38
+ const years = Math.floor(days / 365);
39
+ if (seconds < 10) {
40
+ return "just now";
41
+ } else if (seconds < 60) {
42
+ return `${seconds} seconds ago`;
43
+ } else if (minutes < 60) {
44
+ return minutes === 1 ? "1 minute ago" : `${minutes} minutes ago`;
45
+ } else if (hours < 24) {
46
+ return hours === 1 ? "1 hour ago" : `${hours} hours ago`;
47
+ } else if (days < 7) {
48
+ return days === 1 ? "1 day ago" : `${days} days ago`;
49
+ } else if (weeks < 4) {
50
+ return weeks === 1 ? "1 week ago" : `${weeks} weeks ago`;
51
+ } else if (months < 12) {
52
+ return months === 1 ? "1 month ago" : `${months} months ago`;
53
+ } else {
54
+ return years === 1 ? "1 year ago" : `${years} years ago`;
55
+ }
56
+ }
57
+
58
+ // ../../packages/shared/src/gitUtils.ts
59
+ import { execFileSync, execFile } from "child_process";
60
+ import { promisify } from "util";
61
+ var execFileAsync = promisify(execFile);
62
+ function getCurrentBranch(workspacePath) {
63
+ try {
64
+ const result = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
65
+ cwd: workspacePath,
66
+ encoding: "utf-8",
67
+ timeout: 5e3
68
+ });
69
+ return result.trim() || void 0;
70
+ } catch {
71
+ return void 0;
72
+ }
73
+ }
74
+ function getTouchedFiles(workspacePath) {
75
+ try {
76
+ const result = execFileSync("git", ["status", "--porcelain"], {
77
+ cwd: workspacePath,
78
+ encoding: "utf-8",
79
+ timeout: 5e3
80
+ });
81
+ if (!result.trim()) {
82
+ return [];
83
+ }
84
+ return result.trim().split("\n").map((line) => line.substring(3).trim()).filter((file) => file.length > 0 && !file.endsWith("/"));
85
+ } catch {
86
+ return [];
87
+ }
88
+ }
89
+
90
+ // ../../packages/shared/src/reentry.ts
91
+ var RECENT_SESSION_COUNT = 5;
92
+ function getRecentSessions(allSessions, count = RECENT_SESSION_COUNT) {
93
+ return allSessions.slice(-count).reverse();
94
+ }
95
+
96
+ // src/storage.ts
97
+ var STORAGE_DIR = ".keepgoing";
98
+ var META_FILE = "meta.json";
99
+ var SESSIONS_FILE = "sessions.json";
100
+ 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
+ var KeepGoingWriter = class {
160
+ storagePath;
161
+ sessionsFilePath;
162
+ stateFilePath;
163
+ metaFilePath;
164
+ constructor(workspacePath) {
165
+ this.storagePath = path.join(workspacePath, STORAGE_DIR);
166
+ this.sessionsFilePath = path.join(this.storagePath, SESSIONS_FILE);
167
+ this.stateFilePath = path.join(this.storagePath, STATE_FILE);
168
+ this.metaFilePath = path.join(this.storagePath, META_FILE);
169
+ }
170
+ ensureDir() {
171
+ if (!fs.existsSync(this.storagePath)) {
172
+ fs.mkdirSync(this.storagePath, { recursive: true });
173
+ }
174
+ }
175
+ saveCheckpoint(checkpoint, projectName) {
176
+ this.ensureDir();
177
+ let sessionsData;
178
+ try {
179
+ if (fs.existsSync(this.sessionsFilePath)) {
180
+ const raw = JSON.parse(fs.readFileSync(this.sessionsFilePath, "utf-8"));
181
+ if (Array.isArray(raw)) {
182
+ sessionsData = { version: 1, project: projectName, sessions: raw };
183
+ } else {
184
+ sessionsData = raw;
185
+ }
186
+ } else {
187
+ sessionsData = { version: 1, project: projectName, sessions: [] };
188
+ }
189
+ } catch {
190
+ sessionsData = { version: 1, project: projectName, sessions: [] };
191
+ }
192
+ sessionsData.sessions.push(checkpoint);
193
+ sessionsData.lastSessionId = checkpoint.id;
194
+ fs.writeFileSync(this.sessionsFilePath, JSON.stringify(sessionsData, null, 2), "utf-8");
195
+ const state = {
196
+ lastSessionId: checkpoint.id,
197
+ lastKnownBranch: checkpoint.gitBranch,
198
+ lastActivityAt: checkpoint.timestamp
199
+ };
200
+ fs.writeFileSync(this.stateFilePath, JSON.stringify(state, null, 2), "utf-8");
201
+ let meta;
202
+ try {
203
+ if (fs.existsSync(this.metaFilePath)) {
204
+ meta = JSON.parse(fs.readFileSync(this.metaFilePath, "utf-8"));
205
+ meta.lastUpdated = checkpoint.timestamp;
206
+ } else {
207
+ meta = {
208
+ projectId: randomUUID2(),
209
+ createdAt: checkpoint.timestamp,
210
+ lastUpdated: checkpoint.timestamp
211
+ };
212
+ }
213
+ } catch {
214
+ meta = {
215
+ projectId: randomUUID2(),
216
+ createdAt: checkpoint.timestamp,
217
+ lastUpdated: checkpoint.timestamp
218
+ };
219
+ }
220
+ fs.writeFileSync(this.metaFilePath, JSON.stringify(meta, null, 2), "utf-8");
221
+ }
222
+ };
223
+
224
+ // src/render.ts
225
+ var RESET = "\x1B[0m";
226
+ var BOLD = "\x1B[1m";
227
+ var DIM = "\x1B[2m";
228
+ var YELLOW = "\x1B[33m";
229
+ var CYAN = "\x1B[36m";
230
+ function renderCheckpoint(checkpoint, daysSince) {
231
+ const relTime = formatRelativeTime(checkpoint.timestamp);
232
+ if (daysSince !== void 0 && daysSince >= 7) {
233
+ console.log(`${YELLOW}\u26A0 This project has been idle for ${daysSince} days.${RESET}`);
234
+ }
235
+ console.log(`
236
+ ${BOLD}KeepGoing${RESET} \xB7 ${DIM}${relTime}${RESET}
237
+ `);
238
+ const label = (s) => `${CYAN}${s}${RESET}`;
239
+ if (checkpoint.summary) {
240
+ console.log(` ${label("Summary:")} ${checkpoint.summary}`);
241
+ }
242
+ if (checkpoint.nextStep) {
243
+ console.log(` ${label("Next step:")} ${checkpoint.nextStep}`);
244
+ }
245
+ if (checkpoint.blocker) {
246
+ console.log(` ${label("Blocker:")} ${checkpoint.blocker}`);
247
+ }
248
+ if (checkpoint.gitBranch) {
249
+ console.log(` ${label("Branch:")} ${checkpoint.gitBranch}`);
250
+ }
251
+ if (checkpoint.touchedFiles && checkpoint.touchedFiles.length > 0) {
252
+ const MAX_FILES = 3;
253
+ const shown = checkpoint.touchedFiles.slice(0, MAX_FILES).join(", ");
254
+ const extra = checkpoint.touchedFiles.length - MAX_FILES;
255
+ const filesStr = extra > 0 ? `${shown} (+${extra} more)` : shown;
256
+ console.log(` ${label("Files:")} ${filesStr}`);
257
+ }
258
+ console.log("");
259
+ }
260
+ function renderQuiet(checkpoint) {
261
+ const relTime = formatRelativeTime(checkpoint.timestamp);
262
+ const summary = checkpoint.summary || checkpoint.nextStep || "checkpoint saved";
263
+ console.log(`KeepGoing \xB7 ${relTime} \xB7 ${summary}`);
264
+ }
265
+ function renderNoData() {
266
+ console.log(
267
+ `No KeepGoing data found. Run ${BOLD}keepgoing save${RESET} to save your first checkpoint.`
268
+ );
269
+ }
270
+
271
+ // src/commands/status.ts
272
+ async function statusCommand(opts) {
273
+ const reader = new KeepGoingReader(opts.cwd);
274
+ if (!reader.exists()) {
275
+ if (!opts.quiet) {
276
+ renderNoData();
277
+ }
278
+ process.exit(0);
279
+ }
280
+ const lastSession = reader.getLastSession();
281
+ if (!lastSession) {
282
+ if (!opts.quiet) {
283
+ renderNoData();
284
+ }
285
+ process.exit(0);
286
+ }
287
+ if (opts.json) {
288
+ console.log(JSON.stringify(lastSession, null, 2));
289
+ return;
290
+ }
291
+ if (opts.quiet) {
292
+ renderQuiet(lastSession);
293
+ return;
294
+ }
295
+ const daysSince = Math.floor(
296
+ (Date.now() - new Date(lastSession.timestamp).getTime()) / (1e3 * 60 * 60 * 24)
297
+ );
298
+ renderCheckpoint(lastSession, daysSince);
299
+ }
300
+
301
+ // src/commands/save.ts
302
+ import readline from "readline";
303
+ import path2 from "path";
304
+ function prompt(rl, question) {
305
+ return new Promise((resolve) => {
306
+ rl.question(question, (answer) => {
307
+ resolve(answer.trim());
308
+ });
309
+ });
310
+ }
311
+ async function saveCommand(opts) {
312
+ const rl = readline.createInterface({
313
+ input: process.stdin,
314
+ output: process.stdout
315
+ });
316
+ let summary = "";
317
+ let nextStep = "";
318
+ let blocker = "";
319
+ try {
320
+ while (!summary) {
321
+ summary = await prompt(rl, "What did you work on? ");
322
+ if (!summary) {
323
+ console.log(" (This field is required)");
324
+ }
325
+ }
326
+ while (!nextStep) {
327
+ nextStep = await prompt(rl, "What's your next step? ");
328
+ if (!nextStep) {
329
+ console.log(" (This field is required)");
330
+ }
331
+ }
332
+ blocker = await prompt(rl, "Any blockers? (leave empty to skip) ");
333
+ } finally {
334
+ rl.close();
335
+ }
336
+ const gitBranch = getCurrentBranch(opts.cwd);
337
+ const touchedFiles = getTouchedFiles(opts.cwd);
338
+ const checkpoint = createCheckpoint({
339
+ summary,
340
+ nextStep,
341
+ blocker: blocker || void 0,
342
+ gitBranch,
343
+ touchedFiles,
344
+ workspaceRoot: opts.cwd,
345
+ source: "manual"
346
+ });
347
+ const projectName = path2.basename(opts.cwd);
348
+ const writer = new KeepGoingWriter(opts.cwd);
349
+ writer.saveCheckpoint(checkpoint, projectName);
350
+ console.log("Checkpoint saved.");
351
+ }
352
+
353
+ // src/commands/hook.ts
354
+ import fs2 from "fs";
355
+ import path3 from "path";
356
+ import os from "os";
357
+ var HOOK_MARKER_START = "# keepgoing-hook-start";
358
+ var HOOK_MARKER_END = "# keepgoing-hook-end";
359
+ var ZSH_HOOK = `${HOOK_MARKER_START}
360
+ # KeepGoing shell hook \u2014 auto-injected by 'keepgoing hook install'
361
+ if command -v keepgoing >/dev/null 2>&1; then
362
+ function chpwd() {
363
+ if [ -d ".keepgoing" ]; then
364
+ keepgoing status --quiet
365
+ fi
366
+ }
367
+ fi
368
+ ${HOOK_MARKER_END}`;
369
+ var BASH_HOOK = `${HOOK_MARKER_START}
370
+ # KeepGoing shell hook \u2014 auto-injected by 'keepgoing hook install'
371
+ if command -v keepgoing >/dev/null 2>&1; then
372
+ function cd() {
373
+ builtin cd "$@" || return
374
+ if [ -d ".keepgoing" ]; then
375
+ keepgoing status --quiet
376
+ fi
377
+ }
378
+ fi
379
+ ${HOOK_MARKER_END}`;
380
+ function detectShellRcFile() {
381
+ const shellEnv = process.env["SHELL"] ?? "";
382
+ const home = os.homedir();
383
+ if (shellEnv.endsWith("zsh")) {
384
+ return { shell: "zsh", rcFile: path3.join(home, ".zshrc") };
385
+ }
386
+ if (shellEnv.endsWith("bash")) {
387
+ return { shell: "bash", rcFile: path3.join(home, ".bashrc") };
388
+ }
389
+ return void 0;
390
+ }
391
+ function hookInstallCommand() {
392
+ const detected = detectShellRcFile();
393
+ if (!detected) {
394
+ console.error(
395
+ 'Could not detect your shell. Set $SHELL to "zsh" or "bash" and try again.'
396
+ );
397
+ process.exit(1);
398
+ }
399
+ const { shell, rcFile } = detected;
400
+ const hookBlock = shell === "zsh" ? ZSH_HOOK : BASH_HOOK;
401
+ let existing = "";
402
+ try {
403
+ existing = fs2.readFileSync(rcFile, "utf-8");
404
+ } catch {
405
+ }
406
+ if (existing.includes(HOOK_MARKER_START)) {
407
+ console.log(`KeepGoing hook is already installed in ${rcFile}.`);
408
+ return;
409
+ }
410
+ fs2.appendFileSync(rcFile, `
411
+ ${hookBlock}
412
+ `, "utf-8");
413
+ console.log(`KeepGoing hook installed in ${rcFile}.`);
414
+ console.log(`Reload your shell config to activate it:
415
+ `);
416
+ console.log(` source ${rcFile}`);
417
+ }
418
+ function hookUninstallCommand() {
419
+ const detected = detectShellRcFile();
420
+ if (!detected) {
421
+ console.error(
422
+ 'Could not detect your shell. Set $SHELL to "zsh" or "bash" and try again.'
423
+ );
424
+ process.exit(1);
425
+ }
426
+ const { rcFile } = detected;
427
+ let existing = "";
428
+ try {
429
+ existing = fs2.readFileSync(rcFile, "utf-8");
430
+ } catch {
431
+ console.log(`${rcFile} not found \u2014 nothing to remove.`);
432
+ return;
433
+ }
434
+ if (!existing.includes(HOOK_MARKER_START)) {
435
+ console.log("KeepGoing hook is not installed \u2014 nothing to remove.");
436
+ return;
437
+ }
438
+ const pattern = new RegExp(
439
+ `
440
+ ?${HOOK_MARKER_START}[\\s\\S]*?${HOOK_MARKER_END}
441
+ ?`,
442
+ "g"
443
+ );
444
+ const updated = existing.replace(pattern, "").replace(/\n{3,}/g, "\n\n");
445
+ fs2.writeFileSync(rcFile, updated, "utf-8");
446
+ console.log(`KeepGoing hook removed from ${rcFile}.`);
447
+ console.log(`Reload your shell config to deactivate it:
448
+ `);
449
+ console.log(` source ${rcFile}`);
450
+ }
451
+
452
+ // src/index.ts
453
+ var HELP_TEXT = `
454
+ keepgoing: resume side projects without the mental friction
455
+
456
+ Usage:
457
+ keepgoing status Show the last checkpoint for this project
458
+ keepgoing save Save a new checkpoint interactively
459
+ keepgoing hook Manage the shell hook
460
+
461
+ Options:
462
+ --cwd <path> Override the working directory (default: current directory)
463
+ --json Output raw JSON (status only)
464
+ --quiet Output a single summary line (status only)
465
+ -h, --help Show this help text
466
+
467
+ Hook subcommands:
468
+ keepgoing hook install Install the shell hook into ~/.zshrc or ~/.bashrc
469
+ keepgoing hook uninstall Remove the shell hook
470
+ `;
471
+ function parseArgs(argv) {
472
+ const args = argv.slice(2);
473
+ let command = "";
474
+ let subcommand = "";
475
+ let cwd = process.cwd();
476
+ let json = false;
477
+ let quiet = false;
478
+ for (let i = 0; i < args.length; i++) {
479
+ const arg = args[i];
480
+ if (arg === "--cwd" && i + 1 < args.length) {
481
+ cwd = args[++i];
482
+ } else if (arg === "--json") {
483
+ json = true;
484
+ } else if (arg === "--quiet") {
485
+ quiet = true;
486
+ } else if (arg === "-h" || arg === "--help") {
487
+ command = "help";
488
+ } else if (!command) {
489
+ command = arg;
490
+ } else if (!subcommand) {
491
+ subcommand = arg;
492
+ }
493
+ }
494
+ return { command, subcommand, cwd, json, quiet };
495
+ }
496
+ async function main() {
497
+ const { command, subcommand, cwd, json, quiet } = parseArgs(process.argv);
498
+ switch (command) {
499
+ case "status":
500
+ await statusCommand({ cwd, json, quiet });
501
+ break;
502
+ case "save":
503
+ await saveCommand({ cwd });
504
+ break;
505
+ case "hook":
506
+ if (subcommand === "install") {
507
+ hookInstallCommand();
508
+ } else if (subcommand === "uninstall") {
509
+ hookUninstallCommand();
510
+ } else {
511
+ console.error(
512
+ `Unknown hook subcommand: "${subcommand}". Use "install" or "uninstall".`
513
+ );
514
+ process.exit(1);
515
+ }
516
+ break;
517
+ case "help":
518
+ case "":
519
+ console.log(HELP_TEXT);
520
+ break;
521
+ default:
522
+ console.error(`Unknown command: "${command}". Run "keepgoing --help" for usage.`);
523
+ process.exit(1);
524
+ }
525
+ }
526
+ main().catch((err) => {
527
+ console.error(err instanceof Error ? err.message : String(err));
528
+ process.exit(1);
529
+ });
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@keepgoingdev/cli",
3
+ "version": "0.1.0",
4
+ "description": "Terminal CLI for KeepGoing. Resume side projects without the mental friction.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "bin": {
8
+ "keepgoing": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist/"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsup",
15
+ "watch": "tsup --watch",
16
+ "prepublishOnly": "tsup"
17
+ },
18
+ "devDependencies": {
19
+ "@keepgoingdev/shared": "*",
20
+ "@types/node": "^20.11.0",
21
+ "tsup": "^8.0.0",
22
+ "typescript": "^5.3.0"
23
+ }
24
+ }