@keepgoingdev/cli 1.0.0 → 1.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 (2) hide show
  1. package/dist/index.js +524 -75
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -159,6 +159,62 @@ function getTouchedFiles(workspacePath) {
159
159
  }
160
160
  }
161
161
 
162
+ // ../../packages/shared/src/briefingTier.ts
163
+ var MODEL_TIERS = {
164
+ // Standard: small/fast models (compact is only reachable via explicit tier or context window)
165
+ "claude-3-haiku": "standard",
166
+ "claude-3.5-haiku": "standard",
167
+ "claude-haiku-4-5": "standard",
168
+ "gpt-4o-mini": "standard",
169
+ "gemini-flash": "standard",
170
+ // Detailed: mid-tier models
171
+ "claude-3.5-sonnet": "detailed",
172
+ "claude-sonnet-4": "detailed",
173
+ "claude-sonnet-4-5": "detailed",
174
+ "claude-sonnet-4-6": "detailed",
175
+ "gpt-4o": "detailed",
176
+ "gemini-pro": "detailed",
177
+ // Full: large context models
178
+ "claude-opus-4": "full",
179
+ "claude-opus-4-5": "full",
180
+ "claude-opus-4-6": "full",
181
+ "o1": "full",
182
+ "o3": "full",
183
+ "gemini-ultra": "full"
184
+ };
185
+ function resolveTier(opts) {
186
+ if (opts?.tier) {
187
+ return opts.tier;
188
+ }
189
+ if (opts?.model) {
190
+ const fromModel = tierFromModelName(opts.model);
191
+ if (fromModel) return fromModel;
192
+ }
193
+ if (opts?.contextWindow !== void 0) {
194
+ return tierFromContextWindow(opts.contextWindow);
195
+ }
196
+ return "standard";
197
+ }
198
+ function tierFromModelName(model) {
199
+ const normalized = model.toLowerCase().trim();
200
+ if (MODEL_TIERS[normalized]) {
201
+ return MODEL_TIERS[normalized];
202
+ }
203
+ const entries = Object.entries(MODEL_TIERS).sort((a, b) => b[0].length - a[0].length);
204
+ for (const [key, tier] of entries) {
205
+ if (normalized.startsWith(key)) {
206
+ return tier;
207
+ }
208
+ }
209
+ return void 0;
210
+ }
211
+ function tierFromContextWindow(tokens) {
212
+ if (tokens < 16e3) return "compact";
213
+ if (tokens < 64e3) return "standard";
214
+ if (tokens < 2e5) return "detailed";
215
+ return "full";
216
+ }
217
+
162
218
  // ../../packages/shared/src/reentry.ts
163
219
  var RECENT_SESSION_COUNT = 5;
164
220
  function generateBriefing(lastSession, recentSessions, projectState, gitBranch, recentCommitMessages) {
@@ -184,6 +240,61 @@ function generateBriefing(lastSession, recentSessions, projectState, gitBranch,
184
240
  function getRecentSessions(allSessions, count = RECENT_SESSION_COUNT) {
185
241
  return allSessions.slice(-count).reverse();
186
242
  }
243
+ function generateEnrichedBriefing(opts) {
244
+ const tier = resolveTier({
245
+ tier: opts.tier,
246
+ model: opts.model,
247
+ contextWindow: opts.contextWindow
248
+ });
249
+ const core = generateBriefing(
250
+ opts.lastSession,
251
+ opts.recentSessions,
252
+ opts.projectState,
253
+ opts.gitBranch,
254
+ opts.recentCommits
255
+ );
256
+ if (!core) return void 0;
257
+ const enriched = { tier, core };
258
+ if (tier === "compact") {
259
+ return enriched;
260
+ }
261
+ enriched.blocker = opts.lastSession?.blocker;
262
+ enriched.gitBranch = opts.gitBranch;
263
+ enriched.isWorktree = opts.isWorktree;
264
+ if (tier === "standard") {
265
+ return enriched;
266
+ }
267
+ if (opts.decisions && opts.decisions.length > 0) {
268
+ enriched.decisions = opts.decisions.slice(0, tier === "detailed" ? 3 : 10);
269
+ }
270
+ if (opts.allTouchedFiles && opts.allTouchedFiles.length > 0) {
271
+ enriched.touchedFiles = tier === "detailed" ? opts.allTouchedFiles.slice(0, 10) : opts.allTouchedFiles;
272
+ } else if (opts.lastSession?.touchedFiles && opts.lastSession.touchedFiles.length > 0) {
273
+ enriched.touchedFiles = tier === "detailed" ? opts.lastSession.touchedFiles.slice(0, 10) : opts.lastSession.touchedFiles;
274
+ }
275
+ if (tier === "detailed") {
276
+ return enriched;
277
+ }
278
+ if (opts.allSessions && opts.allSessions.length > 0) {
279
+ const recent = opts.allSessions.slice(-5).reverse();
280
+ enriched.sessionHistory = recent.map((s) => ({
281
+ timestamp: s.timestamp,
282
+ summary: s.summary || "",
283
+ nextStep: s.nextStep || "",
284
+ branch: s.gitBranch
285
+ }));
286
+ }
287
+ if (opts.recentCommits && opts.recentCommits.length > 0) {
288
+ enriched.recentCommits = opts.recentCommits;
289
+ }
290
+ if (opts.fileConflicts && opts.fileConflicts.length > 0) {
291
+ enriched.fileConflicts = opts.fileConflicts;
292
+ }
293
+ if (opts.branchOverlaps && opts.branchOverlaps.length > 0) {
294
+ enriched.branchOverlaps = opts.branchOverlaps;
295
+ }
296
+ return enriched;
297
+ }
187
298
  function buildCurrentFocus(lastSession, projectState, gitBranch) {
188
299
  if (projectState.derivedCurrentFocus) {
189
300
  return projectState.derivedCurrentFocus;
@@ -359,9 +470,123 @@ function inferFocusFromFiles(files) {
359
470
  return names.join(", ");
360
471
  }
361
472
 
473
+ // ../../packages/shared/src/continueOn.ts
474
+ import path2 from "path";
475
+ function gatherContinueOnContext(reader, workspacePath) {
476
+ const projectName = path2.basename(workspacePath);
477
+ const gitBranch = reader.getCurrentBranch();
478
+ if (!reader.exists()) {
479
+ return {
480
+ projectName,
481
+ gitBranch,
482
+ recentCheckpoints: [],
483
+ recentDecisions: [],
484
+ currentTasks: [],
485
+ recentCommitMessages: []
486
+ };
487
+ }
488
+ const { session: lastCheckpoint } = reader.getScopedLastSession();
489
+ const recentCheckpoints = reader.getScopedRecentSessions(5);
490
+ const recentDecisions = reader.getScopedRecentDecisions(3);
491
+ const currentTasks = reader.getCurrentTasks();
492
+ const state = reader.getState() ?? {};
493
+ const sinceTimestamp = lastCheckpoint?.timestamp;
494
+ const recentCommitMessages = sinceTimestamp ? getCommitMessagesSince(workspacePath, sinceTimestamp) : [];
495
+ const briefing = generateBriefing(
496
+ lastCheckpoint,
497
+ recentCheckpoints,
498
+ state,
499
+ gitBranch,
500
+ recentCommitMessages
501
+ );
502
+ return {
503
+ projectName,
504
+ gitBranch,
505
+ briefing,
506
+ lastCheckpoint,
507
+ recentCheckpoints,
508
+ recentDecisions,
509
+ currentTasks,
510
+ recentCommitMessages
511
+ };
512
+ }
513
+ function formatContinueOnPrompt(context, options) {
514
+ const opts = { includeCommits: true, includeFiles: true, ...options };
515
+ const lines = [];
516
+ lines.push(`# Project Context: ${context.projectName}`);
517
+ lines.push("");
518
+ lines.push("I'm continuing work on a project. Here is my development context from KeepGoing.");
519
+ lines.push("");
520
+ lines.push("## Current Status");
521
+ if (context.gitBranch) {
522
+ lines.push(`- **Branch:** ${context.gitBranch}`);
523
+ }
524
+ if (context.briefing) {
525
+ lines.push(`- **Last worked:** ${context.briefing.lastWorked}`);
526
+ lines.push(`- **Focus:** ${context.briefing.currentFocus}`);
527
+ } else if (context.lastCheckpoint) {
528
+ lines.push(`- **Last worked:** ${formatRelativeTime(context.lastCheckpoint.timestamp)}`);
529
+ if (context.lastCheckpoint.summary) {
530
+ lines.push(`- **Focus:** ${context.lastCheckpoint.summary}`);
531
+ }
532
+ }
533
+ if (context.lastCheckpoint) {
534
+ lines.push("");
535
+ lines.push("## Last Session");
536
+ if (context.lastCheckpoint.summary) {
537
+ lines.push(`- **Summary:** ${context.lastCheckpoint.summary}`);
538
+ }
539
+ if (context.lastCheckpoint.nextStep) {
540
+ lines.push(`- **Next step:** ${context.lastCheckpoint.nextStep}`);
541
+ }
542
+ if (context.lastCheckpoint.blocker) {
543
+ lines.push(`- **Blocker:** ${context.lastCheckpoint.blocker}`);
544
+ }
545
+ if (opts.includeFiles && context.lastCheckpoint.touchedFiles.length > 0) {
546
+ const files = context.lastCheckpoint.touchedFiles.slice(0, 10).join(", ");
547
+ const extra = context.lastCheckpoint.touchedFiles.length > 10 ? ` (+${context.lastCheckpoint.touchedFiles.length - 10} more)` : "";
548
+ lines.push(`- **Files touched:** ${files}${extra}`);
549
+ }
550
+ }
551
+ const activeTasks = context.currentTasks.filter((t) => t.sessionActive);
552
+ if (activeTasks.length > 0) {
553
+ lines.push("");
554
+ lines.push("## Active Sessions");
555
+ for (const task of activeTasks) {
556
+ const label = task.agentLabel || "Unknown agent";
557
+ const branch = task.branch ? ` on \`${task.branch}\`` : "";
558
+ lines.push(`- **${label}**${branch}: ${task.taskSummary || "Working"}`);
559
+ }
560
+ }
561
+ if (context.recentDecisions.length > 0) {
562
+ lines.push("");
563
+ lines.push("## Recent Decisions");
564
+ for (const d of context.recentDecisions) {
565
+ lines.push(`- ${d.classification.category}: ${d.commitMessage}`);
566
+ }
567
+ }
568
+ if (opts.includeCommits && context.recentCommitMessages.length > 0) {
569
+ lines.push("");
570
+ lines.push("## Recent Commits");
571
+ const commits = context.recentCommitMessages.slice(0, 10);
572
+ for (const msg of commits) {
573
+ lines.push(`- ${msg}`);
574
+ }
575
+ }
576
+ lines.push("");
577
+ lines.push("---");
578
+ const nextStep = context.lastCheckpoint?.nextStep || context.briefing?.suggestedNext || "continue where I left off";
579
+ lines.push(`Please help me continue this work. My next step is: ${nextStep}.`);
580
+ let result = lines.join("\n");
581
+ if (opts.maxLength && result.length > opts.maxLength) {
582
+ result = result.slice(0, opts.maxLength - 3) + "...";
583
+ }
584
+ return result;
585
+ }
586
+
362
587
  // ../../packages/shared/src/storage.ts
363
588
  import fs from "fs";
364
- import path2 from "path";
589
+ import path3 from "path";
365
590
  import { randomUUID as randomUUID2, createHash } from "crypto";
366
591
  var STORAGE_DIR = ".keepgoing";
367
592
  var META_FILE = "meta.json";
@@ -384,11 +609,11 @@ var KeepGoingWriter = class {
384
609
  currentTasksFilePath;
385
610
  constructor(workspacePath) {
386
611
  const mainRoot = resolveStorageRoot(workspacePath);
387
- this.storagePath = path2.join(mainRoot, STORAGE_DIR);
388
- this.sessionsFilePath = path2.join(this.storagePath, SESSIONS_FILE);
389
- this.stateFilePath = path2.join(this.storagePath, STATE_FILE);
390
- this.metaFilePath = path2.join(this.storagePath, META_FILE);
391
- this.currentTasksFilePath = path2.join(this.storagePath, CURRENT_TASKS_FILE);
612
+ this.storagePath = path3.join(mainRoot, STORAGE_DIR);
613
+ this.sessionsFilePath = path3.join(this.storagePath, SESSIONS_FILE);
614
+ this.stateFilePath = path3.join(this.storagePath, STATE_FILE);
615
+ this.metaFilePath = path3.join(this.storagePath, META_FILE);
616
+ this.currentTasksFilePath = path3.join(this.storagePath, CURRENT_TASKS_FILE);
392
617
  }
393
618
  ensureDir() {
394
619
  if (!fs.existsSync(this.storagePath)) {
@@ -702,24 +927,24 @@ function capitalize(s) {
702
927
 
703
928
  // ../../packages/shared/src/decisionStorage.ts
704
929
  import fs3 from "fs";
705
- import path4 from "path";
930
+ import path5 from "path";
706
931
 
707
932
  // ../../packages/shared/src/license.ts
708
933
  import crypto from "crypto";
709
934
  import fs2 from "fs";
710
935
  import os from "os";
711
- import path3 from "path";
936
+ import path4 from "path";
712
937
  var LICENSE_FILE = "license.json";
713
938
  var DEVICE_ID_FILE = "device-id";
714
939
  function getGlobalLicenseDir() {
715
- return path3.join(os.homedir(), ".keepgoing");
940
+ return path4.join(os.homedir(), ".keepgoing");
716
941
  }
717
942
  function getGlobalLicensePath() {
718
- return path3.join(getGlobalLicenseDir(), LICENSE_FILE);
943
+ return path4.join(getGlobalLicenseDir(), LICENSE_FILE);
719
944
  }
720
945
  function getDeviceId() {
721
946
  const dir = getGlobalLicenseDir();
722
- const filePath = path3.join(dir, DEVICE_ID_FILE);
947
+ const filePath = path4.join(dir, DEVICE_ID_FILE);
723
948
  try {
724
949
  const existing = fs2.readFileSync(filePath, "utf-8").trim();
725
950
  if (existing) return existing;
@@ -786,7 +1011,7 @@ function writeLicenseStore(store) {
786
1011
  if (!fs2.existsSync(dirPath)) {
787
1012
  fs2.mkdirSync(dirPath, { recursive: true });
788
1013
  }
789
- const licensePath = path3.join(dirPath, LICENSE_FILE);
1014
+ const licensePath = path4.join(dirPath, LICENSE_FILE);
790
1015
  fs2.writeFileSync(licensePath, JSON.stringify(store, null, 2), "utf-8");
791
1016
  _cachedStore = store;
792
1017
  _cacheTimestamp = Date.now();
@@ -828,7 +1053,7 @@ var currentGate = new DefaultFeatureGate();
828
1053
 
829
1054
  // ../../packages/shared/src/reader.ts
830
1055
  import fs4 from "fs";
831
- import path5 from "path";
1056
+ import path6 from "path";
832
1057
  var STORAGE_DIR2 = ".keepgoing";
833
1058
  var META_FILE2 = "meta.json";
834
1059
  var SESSIONS_FILE2 = "sessions.json";
@@ -850,12 +1075,12 @@ var KeepGoingReader = class {
850
1075
  this.workspacePath = workspacePath;
851
1076
  const mainRoot = resolveStorageRoot(workspacePath);
852
1077
  this._isWorktree = mainRoot !== workspacePath;
853
- this.storagePath = path5.join(mainRoot, STORAGE_DIR2);
854
- this.metaFilePath = path5.join(this.storagePath, META_FILE2);
855
- this.sessionsFilePath = path5.join(this.storagePath, SESSIONS_FILE2);
856
- this.decisionsFilePath = path5.join(this.storagePath, DECISIONS_FILE);
857
- this.stateFilePath = path5.join(this.storagePath, STATE_FILE2);
858
- this.currentTasksFilePath = path5.join(this.storagePath, CURRENT_TASKS_FILE2);
1078
+ this.storagePath = path6.join(mainRoot, STORAGE_DIR2);
1079
+ this.metaFilePath = path6.join(this.storagePath, META_FILE2);
1080
+ this.sessionsFilePath = path6.join(this.storagePath, SESSIONS_FILE2);
1081
+ this.decisionsFilePath = path6.join(this.storagePath, DECISIONS_FILE);
1082
+ this.stateFilePath = path6.join(this.storagePath, STATE_FILE2);
1083
+ this.currentTasksFilePath = path6.join(this.storagePath, CURRENT_TASKS_FILE2);
859
1084
  }
860
1085
  /** Check if .keepgoing/ directory exists. */
861
1086
  exists() {
@@ -1111,7 +1336,7 @@ var KeepGoingReader = class {
1111
1336
  // ../../packages/shared/src/setup.ts
1112
1337
  import fs5 from "fs";
1113
1338
  import os2 from "os";
1114
- import path6 from "path";
1339
+ import path7 from "path";
1115
1340
  var KEEPGOING_MARKER = "@keepgoingdev/mcp-server";
1116
1341
  var SESSION_START_HOOK = {
1117
1342
  matcher: "",
@@ -1156,19 +1381,19 @@ function hasKeepGoingHook(hookEntries) {
1156
1381
  }
1157
1382
  function resolveScopePaths(scope, workspacePath) {
1158
1383
  if (scope === "user") {
1159
- const claudeDir2 = path6.join(os2.homedir(), ".claude");
1384
+ const claudeDir2 = path7.join(os2.homedir(), ".claude");
1160
1385
  return {
1161
1386
  claudeDir: claudeDir2,
1162
- settingsPath: path6.join(claudeDir2, "settings.json"),
1163
- claudeMdPath: path6.join(claudeDir2, "CLAUDE.md")
1387
+ settingsPath: path7.join(claudeDir2, "settings.json"),
1388
+ claudeMdPath: path7.join(claudeDir2, "CLAUDE.md")
1164
1389
  };
1165
1390
  }
1166
- const claudeDir = path6.join(workspacePath, ".claude");
1167
- const dotClaudeMdPath = path6.join(workspacePath, ".claude", "CLAUDE.md");
1168
- const rootClaudeMdPath = path6.join(workspacePath, "CLAUDE.md");
1391
+ const claudeDir = path7.join(workspacePath, ".claude");
1392
+ const dotClaudeMdPath = path7.join(workspacePath, ".claude", "CLAUDE.md");
1393
+ const rootClaudeMdPath = path7.join(workspacePath, "CLAUDE.md");
1169
1394
  return {
1170
1395
  claudeDir,
1171
- settingsPath: path6.join(claudeDir, "settings.json"),
1396
+ settingsPath: path7.join(claudeDir, "settings.json"),
1172
1397
  claudeMdPath: fs5.existsSync(dotClaudeMdPath) ? dotClaudeMdPath : rootClaudeMdPath
1173
1398
  };
1174
1399
  }
@@ -1281,7 +1506,7 @@ function setupProject(options) {
1281
1506
  messages.push(`CLAUDE.md: KeepGoing section already present in ${mdLabel}, skipped`);
1282
1507
  } else {
1283
1508
  const updated = existing + CLAUDE_MD_SECTION;
1284
- const mdDir = path6.dirname(claudeMdPath);
1509
+ const mdDir = path7.dirname(claudeMdPath);
1285
1510
  if (!fs5.existsSync(mdDir)) {
1286
1511
  fs5.mkdirSync(mdDir, { recursive: true });
1287
1512
  }
@@ -1430,29 +1655,6 @@ function renderNoData() {
1430
1655
  `No KeepGoing data found. Run ${BOLD}keepgoing save${RESET} to save your first checkpoint.`
1431
1656
  );
1432
1657
  }
1433
- function renderBriefing(briefing, decisions) {
1434
- const label = (s) => `${CYAN}${s}${RESET}`;
1435
- console.log(`
1436
- ${BOLD}KeepGoing Re-entry Briefing${RESET}
1437
- `);
1438
- console.log(` ${label("Last worked:")} ${briefing.lastWorked}`);
1439
- console.log(` ${label("Focus:")} ${briefing.currentFocus}`);
1440
- console.log(` ${label("Activity:")} ${briefing.recentActivity}`);
1441
- console.log(` ${label("Next:")} ${briefing.suggestedNext}`);
1442
- console.log(` ${label("Quick start:")} ${briefing.smallNextStep}`);
1443
- if (decisions && decisions.length > 0) {
1444
- console.log(`
1445
- ${label("Recent decisions:")}`);
1446
- for (const d of decisions) {
1447
- const relTime = formatRelativeTime(d.timestamp);
1448
- console.log(` ${d.classification.category}: ${d.commitMessage} ${DIM}(${relTime})${RESET}`);
1449
- }
1450
- }
1451
- console.log("");
1452
- }
1453
- function renderBriefingQuiet(briefing) {
1454
- console.log(`KeepGoing \xB7 ${briefing.lastWorked} \xB7 Focus: ${briefing.currentFocus} \xB7 Next: ${briefing.suggestedNext}`);
1455
- }
1456
1658
  function renderMomentum(checkpoint, ctx) {
1457
1659
  const relTime = formatRelativeTime(checkpoint.timestamp);
1458
1660
  const label = (s) => `${CYAN}${s}${RESET}`;
@@ -1602,18 +1804,130 @@ function renderSessionGroupHeader(sessionId, count) {
1602
1804
  const shortId = sessionId.slice(0, 8);
1603
1805
  console.log(`${BOLD}Session ${shortId}${RESET} ${DIM}(${count} checkpoint${count !== 1 ? "s" : ""})${RESET}`);
1604
1806
  }
1807
+ function renderContinueOn(prompt, copied, target) {
1808
+ console.log(`
1809
+ ${BOLD}KeepGoing Continue On${RESET}
1810
+ `);
1811
+ if (copied) {
1812
+ console.log(`${GREEN}\u2714 Context copied to clipboard${RESET}`);
1813
+ } else {
1814
+ console.log(`${YELLOW}\u26A0 Could not copy to clipboard. Prompt printed below.${RESET}`);
1815
+ }
1816
+ if (target) {
1817
+ console.log(`${DIM}Target: ${target}${RESET}`);
1818
+ }
1819
+ console.log("");
1820
+ console.log(`${DIM}--- prompt start ---${RESET}`);
1821
+ console.log(prompt);
1822
+ console.log(`${DIM}--- prompt end ---${RESET}`);
1823
+ console.log("");
1824
+ }
1825
+ function renderContinueOnQuiet(copied, target) {
1826
+ const targetStr = target ? ` (${target})` : "";
1827
+ if (copied) {
1828
+ console.log(`KeepGoing \xB7 Context copied to clipboard${targetStr}`);
1829
+ } else {
1830
+ console.log(`KeepGoing \xB7 Failed to copy context to clipboard${targetStr}`);
1831
+ }
1832
+ }
1833
+ function renderEnrichedBriefing(briefing) {
1834
+ const { tier, core } = briefing;
1835
+ const label = (s) => `${CYAN}${s}${RESET}`;
1836
+ console.log(`
1837
+ ${BOLD}KeepGoing Re-entry Briefing${RESET} ${DIM}(${tier})${RESET}
1838
+ `);
1839
+ if (briefing.isWorktree && briefing.gitBranch) {
1840
+ console.log(` ${DIM}Worktree: scoped to ${briefing.gitBranch}${RESET}
1841
+ `);
1842
+ }
1843
+ if (tier === "compact") {
1844
+ console.log(` ${label("Last worked:")} ${core.lastWorked}`);
1845
+ console.log(` ${label("Focus:")} ${core.currentFocus}`);
1846
+ console.log(` ${label("Quick start:")} ${core.smallNextStep}`);
1847
+ console.log("");
1848
+ return;
1849
+ }
1850
+ console.log(` ${label("Last worked:")} ${core.lastWorked}`);
1851
+ console.log(` ${label("Focus:")} ${core.currentFocus}`);
1852
+ console.log(` ${label("Activity:")} ${core.recentActivity}`);
1853
+ console.log(` ${label("Next:")} ${core.suggestedNext}`);
1854
+ console.log(` ${label("Quick start:")} ${core.smallNextStep}`);
1855
+ if (briefing.blocker) {
1856
+ console.log(` ${label("Blocker:")} ${YELLOW}${briefing.blocker}${RESET}`);
1857
+ }
1858
+ if (briefing.decisions && briefing.decisions.length > 0) {
1859
+ console.log(`
1860
+ ${label("Recent decisions:")}`);
1861
+ for (const d of briefing.decisions) {
1862
+ const relTime = formatRelativeTime(d.timestamp);
1863
+ console.log(` ${d.classification.category}: ${d.commitMessage} ${DIM}(${relTime})${RESET}`);
1864
+ }
1865
+ }
1866
+ if (briefing.touchedFiles && briefing.touchedFiles.length > 0) {
1867
+ console.log(`
1868
+ ${label(`Files touched (${briefing.touchedFiles.length}):`)}`);
1869
+ const MAX_FILES = tier === "full" ? 20 : 10;
1870
+ const shown = briefing.touchedFiles.slice(0, MAX_FILES);
1871
+ for (const f of shown) {
1872
+ console.log(` ${DIM}${f}${RESET}`);
1873
+ }
1874
+ if (briefing.touchedFiles.length > MAX_FILES) {
1875
+ console.log(` ${DIM}...and ${briefing.touchedFiles.length - MAX_FILES} more${RESET}`);
1876
+ }
1877
+ }
1878
+ if (briefing.sessionHistory && briefing.sessionHistory.length > 0) {
1879
+ console.log(`
1880
+ ${label("Session history:")}`);
1881
+ for (const s of briefing.sessionHistory) {
1882
+ const relTime = formatRelativeTime(s.timestamp);
1883
+ const branch = s.branch ? ` ${GREEN}(${s.branch})${RESET}` : "";
1884
+ console.log(` ${DIM}${relTime}${branch}${RESET} ${s.summary || "No summary"}`);
1885
+ if (s.nextStep) {
1886
+ console.log(` ${CYAN}\u2192${RESET} ${s.nextStep}`);
1887
+ }
1888
+ }
1889
+ }
1890
+ if (briefing.recentCommits && briefing.recentCommits.length > 0) {
1891
+ console.log(`
1892
+ ${label("Recent commits:")}`);
1893
+ for (const msg of briefing.recentCommits.slice(0, 10)) {
1894
+ console.log(` ${DIM}${msg}${RESET}`);
1895
+ }
1896
+ }
1897
+ if (briefing.fileConflicts && briefing.fileConflicts.length > 0) {
1898
+ console.log(`
1899
+ ${YELLOW}File conflicts:${RESET}`);
1900
+ for (const c of briefing.fileConflicts) {
1901
+ const labels = c.sessions.map((s) => s.agentLabel || s.sessionId).join(", ");
1902
+ console.log(` ${c.file}: ${labels}`);
1903
+ }
1904
+ }
1905
+ if (briefing.branchOverlaps && briefing.branchOverlaps.length > 0) {
1906
+ console.log(`
1907
+ ${YELLOW}Branch overlaps:${RESET}`);
1908
+ for (const o of briefing.branchOverlaps) {
1909
+ const labels = o.sessions.map((s) => s.agentLabel || s.sessionId).join(", ");
1910
+ console.log(` ${o.branch}: ${labels}`);
1911
+ }
1912
+ }
1913
+ console.log("");
1914
+ }
1915
+ function renderEnrichedBriefingQuiet(briefing) {
1916
+ const { core } = briefing;
1917
+ console.log(`KeepGoing \xB7 ${core.lastWorked} \xB7 Focus: ${core.currentFocus} \xB7 Next: ${core.smallNextStep}`);
1918
+ }
1605
1919
 
1606
1920
  // src/updateCheck.ts
1607
1921
  import { spawn } from "child_process";
1608
1922
  import { readFileSync, existsSync } from "fs";
1609
- import path7 from "path";
1923
+ import path8 from "path";
1610
1924
  import os3 from "os";
1611
- var CLI_VERSION = "1.0.0";
1925
+ var CLI_VERSION = "1.1.0";
1612
1926
  var NPM_REGISTRY_URL = "https://registry.npmjs.org/@keepgoingdev/cli/latest";
1613
1927
  var FETCH_TIMEOUT_MS = 5e3;
1614
1928
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1615
- var CACHE_DIR = path7.join(os3.homedir(), ".keepgoing");
1616
- var CACHE_PATH = path7.join(CACHE_DIR, "update-check.json");
1929
+ var CACHE_DIR = path8.join(os3.homedir(), ".keepgoing");
1930
+ var CACHE_PATH = path8.join(CACHE_DIR, "update-check.json");
1617
1931
  function isNewerVersion(current, latest) {
1618
1932
  const cur = current.split(".").map(Number);
1619
1933
  const lat = latest.split(".").map(Number);
@@ -1728,7 +2042,7 @@ async function statusCommand(opts) {
1728
2042
  }
1729
2043
 
1730
2044
  // src/commands/save.ts
1731
- import path8 from "path";
2045
+ import path9 from "path";
1732
2046
  async function saveCommand(opts) {
1733
2047
  const { cwd, message, nextStepOverride, json, quiet, force } = opts;
1734
2048
  const isManual = !!message;
@@ -1757,9 +2071,9 @@ async function saveCommand(opts) {
1757
2071
  sessionStartTime: lastSession?.timestamp ?? now,
1758
2072
  lastActivityTime: now
1759
2073
  });
1760
- const summary = message ?? buildSmartSummary(events) ?? `Worked on ${touchedFiles.slice(0, 5).map((f) => path8.basename(f)).join(", ")}`;
2074
+ const summary = message ?? buildSmartSummary(events) ?? `Worked on ${touchedFiles.slice(0, 5).map((f) => path9.basename(f)).join(", ")}`;
1761
2075
  const nextStep = nextStepOverride ?? buildSmartNextStep(events);
1762
- const projectName = path8.basename(resolveStorageRoot(cwd));
2076
+ const projectName = path9.basename(resolveStorageRoot(cwd));
1763
2077
  const sessionId = generateSessionId({
1764
2078
  workspaceRoot: cwd,
1765
2079
  branch: gitBranch ?? void 0,
@@ -1793,7 +2107,7 @@ async function saveCommand(opts) {
1793
2107
 
1794
2108
  // src/commands/hook.ts
1795
2109
  import fs6 from "fs";
1796
- import path9 from "path";
2110
+ import path10 from "path";
1797
2111
  import os4 from "os";
1798
2112
  import { execSync } from "child_process";
1799
2113
  var HOOK_MARKER_START = "# keepgoing-hook-start";
@@ -1853,14 +2167,14 @@ function detectShellRcFile(shellOverride) {
1853
2167
  }
1854
2168
  }
1855
2169
  if (shell === "zsh") {
1856
- return { shell: "zsh", rcFile: path9.join(home, ".zshrc") };
2170
+ return { shell: "zsh", rcFile: path10.join(home, ".zshrc") };
1857
2171
  }
1858
2172
  if (shell === "bash") {
1859
- return { shell: "bash", rcFile: path9.join(home, ".bashrc") };
2173
+ return { shell: "bash", rcFile: path10.join(home, ".bashrc") };
1860
2174
  }
1861
2175
  if (shell === "fish") {
1862
- const xdgConfig = process.env["XDG_CONFIG_HOME"] || path9.join(home, ".config");
1863
- return { shell: "fish", rcFile: path9.join(xdgConfig, "fish", "config.fish") };
2176
+ const xdgConfig = process.env["XDG_CONFIG_HOME"] || path10.join(home, ".config");
2177
+ return { shell: "fish", rcFile: path10.join(xdgConfig, "fish", "config.fish") };
1864
2178
  }
1865
2179
  return void 0;
1866
2180
  }
@@ -2030,30 +2344,43 @@ async function briefingCommand(opts) {
2030
2344
  const state = reader.getState() ?? {};
2031
2345
  const sinceTimestamp = lastSession?.timestamp;
2032
2346
  const recentCommits = sinceTimestamp ? getCommitMessagesSince(opts.cwd, sinceTimestamp) : [];
2033
- const briefing = generateBriefing(
2347
+ const decisions = reader.getScopedRecentDecisions(10);
2348
+ const allSessions = reader.getSessions();
2349
+ const fileConflicts = reader.detectFileConflicts();
2350
+ const branchOverlaps = reader.detectBranchOverlap();
2351
+ const effectiveTier = opts.quiet ? "compact" : opts.tier;
2352
+ const validTiers = ["compact", "standard", "detailed", "full"];
2353
+ const tier = effectiveTier && validTiers.includes(effectiveTier) ? effectiveTier : void 0;
2354
+ const briefing = generateEnrichedBriefing({
2355
+ tier,
2356
+ model: opts.model,
2034
2357
  lastSession,
2035
2358
  recentSessions,
2036
- state,
2359
+ projectState: state,
2037
2360
  gitBranch,
2038
- recentCommits
2039
- );
2361
+ recentCommits,
2362
+ decisions,
2363
+ allTouchedFiles: lastSession?.touchedFiles,
2364
+ allSessions,
2365
+ fileConflicts,
2366
+ branchOverlaps,
2367
+ isWorktree: reader.isWorktree
2368
+ });
2040
2369
  if (!briefing) {
2041
2370
  if (!opts.quiet) {
2042
2371
  console.log("No session data available to generate a briefing.");
2043
2372
  }
2044
2373
  return;
2045
2374
  }
2046
- const decisions = reader.getScopedRecentDecisions(3);
2047
2375
  if (opts.json) {
2048
- const output = decisions.length > 0 ? { ...briefing, decisions } : briefing;
2049
- console.log(JSON.stringify(output, null, 2));
2376
+ console.log(JSON.stringify(briefing, null, 2));
2050
2377
  return;
2051
2378
  }
2052
2379
  if (opts.quiet) {
2053
- renderBriefingQuiet(briefing);
2380
+ renderEnrichedBriefingQuiet(briefing);
2054
2381
  return;
2055
2382
  }
2056
- renderBriefing(briefing, decisions);
2383
+ renderEnrichedBriefing(briefing);
2057
2384
  }
2058
2385
 
2059
2386
  // src/commands/init.ts
@@ -2415,6 +2742,83 @@ async function logCommand(opts) {
2415
2742
  }
2416
2743
  }
2417
2744
 
2745
+ // src/platform.ts
2746
+ import { execSync as execSync2, spawnSync } from "child_process";
2747
+ function copyToClipboard(text) {
2748
+ try {
2749
+ const platform = process.platform;
2750
+ if (platform === "darwin") {
2751
+ execSync2("pbcopy", { input: text, stdio: ["pipe", "ignore", "ignore"] });
2752
+ } else if (platform === "win32") {
2753
+ execSync2("clip.exe", { input: text, stdio: ["pipe", "ignore", "ignore"] });
2754
+ } else {
2755
+ try {
2756
+ execSync2("xclip -selection clipboard", { input: text, stdio: ["pipe", "ignore", "ignore"] });
2757
+ } catch {
2758
+ execSync2("xsel --clipboard --input", { input: text, stdio: ["pipe", "ignore", "ignore"] });
2759
+ }
2760
+ }
2761
+ return true;
2762
+ } catch {
2763
+ return false;
2764
+ }
2765
+ }
2766
+ function openUrl(url) {
2767
+ try {
2768
+ const platform = process.platform;
2769
+ if (platform === "darwin") {
2770
+ spawnSync("open", [url], { stdio: "ignore" });
2771
+ } else if (platform === "win32") {
2772
+ spawnSync("cmd", ["/c", "start", "", url], { stdio: "ignore" });
2773
+ } else {
2774
+ spawnSync("xdg-open", [url], { stdio: "ignore" });
2775
+ }
2776
+ } catch {
2777
+ }
2778
+ }
2779
+
2780
+ // src/commands/continue.ts
2781
+ var TARGET_URLS = {
2782
+ chatgpt: "https://chat.openai.com",
2783
+ gemini: "https://gemini.google.com/app",
2784
+ copilot: "https://github.com/copilot",
2785
+ claude: "https://claude.ai/new"
2786
+ };
2787
+ async function continueCommand(opts) {
2788
+ const reader = new KeepGoingReader(opts.cwd);
2789
+ if (!reader.exists()) {
2790
+ if (!opts.quiet) {
2791
+ renderNoData();
2792
+ }
2793
+ return;
2794
+ }
2795
+ const context = gatherContinueOnContext(reader, opts.cwd);
2796
+ if (!context.lastCheckpoint && !context.briefing) {
2797
+ if (!opts.quiet) {
2798
+ console.log("No session data available. Save a checkpoint first.");
2799
+ }
2800
+ return;
2801
+ }
2802
+ const formatOpts = {};
2803
+ if (opts.target && opts.target in TARGET_URLS) {
2804
+ formatOpts.target = opts.target;
2805
+ }
2806
+ const prompt = formatContinueOnPrompt(context, formatOpts);
2807
+ if (opts.json) {
2808
+ console.log(JSON.stringify({ prompt, context }, null, 2));
2809
+ return;
2810
+ }
2811
+ const copied = copyToClipboard(prompt);
2812
+ if (opts.quiet) {
2813
+ renderContinueOnQuiet(copied, opts.target);
2814
+ return;
2815
+ }
2816
+ renderContinueOn(prompt, copied, opts.target);
2817
+ if (opts.open && opts.target && TARGET_URLS[opts.target]) {
2818
+ openUrl(TARGET_URLS[opts.target]);
2819
+ }
2820
+ }
2821
+
2418
2822
  // src/index.ts
2419
2823
  var HELP_TEXT = `
2420
2824
  keepgoing: resume side projects without the mental friction
@@ -2428,6 +2832,7 @@ Commands:
2428
2832
  briefing Get a re-entry briefing for this project
2429
2833
  decisions View decision history (Pro)
2430
2834
  log Browse session checkpoints
2835
+ continue Export context for use in another AI tool
2431
2836
  save Save a checkpoint (auto-generates from git)
2432
2837
  hook Manage the shell hook (zsh, bash, fish)
2433
2838
  activate <key> Activate a Pro license on this device
@@ -2487,6 +2892,8 @@ keepgoing briefing: Get a re-entry briefing for this project
2487
2892
  Usage: keepgoing briefing [options]
2488
2893
 
2489
2894
  Options:
2895
+ --tier <tier> Detail level: compact, standard (default), detailed, full
2896
+ --model <name> Auto-resolve tier from model name (e.g. "claude-opus-4")
2490
2897
  --json Output raw JSON
2491
2898
  --quiet Suppress output
2492
2899
  --cwd <path> Override the working directory
@@ -2582,6 +2989,23 @@ Example:
2582
2989
  keepgoing deactivate: Deactivate the Pro license from this device
2583
2990
 
2584
2991
  Usage: keepgoing deactivate [<key>]
2992
+ `,
2993
+ continue: `
2994
+ keepgoing continue: Export context for use in another AI tool
2995
+
2996
+ Usage: keepgoing continue [options]
2997
+
2998
+ Options:
2999
+ --target <tool> Target AI tool (chatgpt, gemini, copilot, claude, general)
3000
+ --open Auto-open the target tool's URL in your browser
3001
+ --json Output raw JSON (prompt + context)
3002
+ --quiet Suppress output (just copy to clipboard)
3003
+ --cwd <path> Override the working directory
3004
+
3005
+ Examples:
3006
+ keepgoing continue Copy context to clipboard
3007
+ keepgoing continue --target chatgpt --open Copy and open ChatGPT
3008
+ keepgoing continue --json Output as JSON
2585
3009
  `
2586
3010
  };
2587
3011
  function parseArgs(argv) {
@@ -2611,6 +3035,10 @@ function parseArgs(argv) {
2611
3035
  let today = false;
2612
3036
  let week = false;
2613
3037
  let sessions = false;
3038
+ let target = "";
3039
+ let open = false;
3040
+ let tier = "";
3041
+ let model = "";
2614
3042
  for (let i = 0; i < args.length; i++) {
2615
3043
  const arg = args[i];
2616
3044
  if (arg === "--cwd" && i + 1 < args.length) {
@@ -2633,6 +3061,10 @@ function parseArgs(argv) {
2633
3061
  branch = args[++i];
2634
3062
  } else if (arg === "--limit" && i + 1 < args.length) {
2635
3063
  limit = parseInt(args[++i], 10) || 10;
3064
+ } else if (arg === "--target" && i + 1 < args.length) {
3065
+ target = args[++i];
3066
+ } else if (arg === "--open") {
3067
+ open = true;
2636
3068
  } else if (arg === "--json") {
2637
3069
  json = true;
2638
3070
  } else if (arg === "--quiet") {
@@ -2661,6 +3093,10 @@ function parseArgs(argv) {
2661
3093
  week = true;
2662
3094
  } else if (arg === "--sessions") {
2663
3095
  sessions = true;
3096
+ } else if (arg === "--tier" && i + 1 < args.length) {
3097
+ tier = args[++i];
3098
+ } else if (arg === "--model" && i + 1 < args.length) {
3099
+ model = args[++i];
2664
3100
  } else if (arg === "-v" || arg === "--version") {
2665
3101
  command = "version";
2666
3102
  } else if (arg === "-h" || arg === "--help") {
@@ -2697,7 +3133,11 @@ function parseArgs(argv) {
2697
3133
  blockerOnly,
2698
3134
  today,
2699
3135
  week,
2700
- sessions
3136
+ sessions,
3137
+ target,
3138
+ open,
3139
+ tier,
3140
+ model
2701
3141
  };
2702
3142
  }
2703
3143
  async function main() {
@@ -2724,7 +3164,13 @@ async function main() {
2724
3164
  await momentumCommand({ cwd, json, quiet });
2725
3165
  break;
2726
3166
  case "briefing":
2727
- await briefingCommand({ cwd, json, quiet });
3167
+ await briefingCommand({
3168
+ cwd,
3169
+ json,
3170
+ quiet,
3171
+ tier: parsed.tier || void 0,
3172
+ model: parsed.model || void 0
3173
+ });
2728
3174
  break;
2729
3175
  case "decisions":
2730
3176
  await decisionsCommand({ cwd, json, quiet, branch, limit });
@@ -2750,6 +3196,9 @@ async function main() {
2750
3196
  sessions: parsed.sessions
2751
3197
  });
2752
3198
  break;
3199
+ case "continue":
3200
+ await continueCommand({ cwd, json, quiet, target: parsed.target, open: parsed.open });
3201
+ break;
2753
3202
  case "save":
2754
3203
  await saveCommand({
2755
3204
  cwd,
@@ -2770,7 +3219,7 @@ async function main() {
2770
3219
  }
2771
3220
  break;
2772
3221
  case "version":
2773
- console.log(`keepgoing v${"1.0.0"}`);
3222
+ console.log(`keepgoing v${"1.1.0"}`);
2774
3223
  break;
2775
3224
  case "activate":
2776
3225
  await activateCommand({ licenseKey: subcommand });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keepgoingdev/cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Terminal CLI for KeepGoing. Resume side projects without the mental friction.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",