devark-cli 0.1.0 → 0.1.3

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.
@@ -1,3 +1,3 @@
1
- 52a46eee13d20a05fc798ac3e30f00685cf9475e3a56a48ede86461cd5d56ee1 index.js
2
- 6e948721c1e5bb7dd3e746c0e1e7a972cbd491b6f0135718178b22563ca56de9 index.js.map
1
+ 4e5508903ce1d478e710b2b659be710be26dd3e4233ac1fde8fce9b6a81cb37a index.js
2
+ 1af5bc655dd375bcbda2a0274b9441baeebdf921d615302c3c224706f34675cf index.js.map
3
3
  137b0f6da8ab3a82dd96e536c9de1c0162f51d5450895af16e935906975671a8 report-template.html
package/dist/index.js CHANGED
@@ -2481,7 +2481,7 @@ var require_package = __commonJS({
2481
2481
  "package.json"(exports2, module2) {
2482
2482
  module2.exports = {
2483
2483
  name: "devark-cli",
2484
- version: "0.1.0",
2484
+ version: "0.1.3",
2485
2485
  description: "DevArk - Developer Productivity CLI",
2486
2486
  bin: {
2487
2487
  devark: "./bin/devark.js"
@@ -2668,7 +2668,7 @@ async function gatherCLIConfiguration() {
2668
2668
  return null;
2669
2669
  }
2670
2670
  }
2671
- var import_axios, import_crypto2, SecureApiClient, apiClient;
2671
+ var import_axios, import_crypto2, TARGET_BATCH_SIZE_BYTES, BUFFER_PERCENT, SecureApiClient, apiClient;
2672
2672
  var init_api_client = __esm({
2673
2673
  "src/lib/api-client.ts"() {
2674
2674
  "use strict";
@@ -2681,6 +2681,8 @@ var init_api_client = __esm({
2681
2681
  init_network_errors();
2682
2682
  init_claude_settings_manager();
2683
2683
  import_crypto2 = __toESM(require("crypto"));
2684
+ TARGET_BATCH_SIZE_BYTES = 500 * 1024;
2685
+ BUFFER_PERCENT = 0.2;
2684
2686
  SecureApiClient = class {
2685
2687
  client;
2686
2688
  requestCount = 0;
@@ -2894,20 +2896,45 @@ var init_api_client = __esm({
2894
2896
  return { valid: false };
2895
2897
  }
2896
2898
  }
2899
+ estimateSessionSize(session) {
2900
+ return Buffer.byteLength(JSON.stringify(session));
2901
+ }
2902
+ createSizeBatches(sessions, targetSizeBytes = TARGET_BATCH_SIZE_BYTES, bufferPercent = BUFFER_PERCENT) {
2903
+ const effectiveTarget = targetSizeBytes * (1 - bufferPercent);
2904
+ const batches = [];
2905
+ let currentBatch = [];
2906
+ let currentSize = 0;
2907
+ for (const session of sessions) {
2908
+ const sessionSize = this.estimateSessionSize(session);
2909
+ if (currentBatch.length > 0 && currentSize + sessionSize > effectiveTarget) {
2910
+ batches.push(currentBatch);
2911
+ currentBatch = [session];
2912
+ currentSize = sessionSize;
2913
+ } else {
2914
+ currentBatch.push(session);
2915
+ currentSize += sessionSize;
2916
+ }
2917
+ }
2918
+ if (currentBatch.length > 0) {
2919
+ batches.push(currentBatch);
2920
+ }
2921
+ return batches;
2922
+ }
2897
2923
  async uploadSessions(sessions, onProgress) {
2898
2924
  const cliConfig = await gatherCLIConfiguration();
2899
2925
  const sanitizedSessions = sessions.map((session) => this.sanitizeSession(session));
2900
- const CHUNK_SIZE = 100;
2901
- const chunks = [];
2902
- for (let i = 0; i < sanitizedSessions.length; i += CHUNK_SIZE) {
2903
- chunks.push(sanitizedSessions.slice(i, i + CHUNK_SIZE));
2904
- }
2926
+ const chunks = this.createSizeBatches(sanitizedSessions);
2905
2927
  const results = [];
2906
2928
  let uploadedCount = 0;
2907
2929
  let uploadedSizeKB = 0;
2908
2930
  const totalSize = Buffer.byteLength(JSON.stringify(sanitizedSessions)) / 1024;
2909
2931
  if (process.env.DEVARK_DEBUG === "true") {
2910
- console.log("[DEBUG] Uploading in", chunks.length, "chunks", `(Total: ${totalSize.toFixed(2)} KB)`);
2932
+ console.log(
2933
+ "[DEBUG] Uploading in",
2934
+ chunks.length,
2935
+ "size-based batches",
2936
+ `(Total: ${totalSize.toFixed(2)} KB, Target: ~${TARGET_BATCH_SIZE_BYTES / 1024}KB per batch)`
2937
+ );
2911
2938
  }
2912
2939
  for (let i = 0; i < chunks.length; i++) {
2913
2940
  const chunk = chunks[i];
@@ -3060,6 +3087,25 @@ var init_api_client = __esm({
3060
3087
  }
3061
3088
  return response.data;
3062
3089
  }
3090
+ /**
3091
+ * Get the timestamp of the user's most recent session for incremental sync.
3092
+ * @returns Last session timestamp (ISO string) and ID, or null if no sessions exist
3093
+ */
3094
+ async getLastSessionDate() {
3095
+ var _a;
3096
+ try {
3097
+ const response = await this.client.get("/api/sessions/last");
3098
+ return {
3099
+ lastSessionTimestamp: response.data.lastSessionTimestamp || null,
3100
+ lastSessionId: response.data.lastSessionId || null
3101
+ };
3102
+ } catch (error) {
3103
+ if (((_a = error.response) == null ? void 0 : _a.status) === 404) {
3104
+ return { lastSessionTimestamp: null, lastSessionId: null };
3105
+ }
3106
+ throw error;
3107
+ }
3108
+ }
3063
3109
  getBaseUrl() {
3064
3110
  return this.client.defaults.baseURL || "http://localhost:3000";
3065
3111
  }
@@ -4010,10 +4056,14 @@ var init_send_orchestrator = __esm({
4010
4056
  return this.readSelectedSessions(options.selectedSessions);
4011
4057
  }
4012
4058
  if (options.all) {
4013
- const sessions2 = await readClaudeSessions({ since: void 0 });
4059
+ const serverSince = await this.getServerLastSessionDate();
4060
+ if (serverSince) {
4061
+ logger.debug(`Using server timestamp for incremental sync: ${serverSince.toISOString()}`);
4062
+ }
4063
+ const sessions2 = await readClaudeSessions({ since: serverSince });
4014
4064
  return sessions2;
4015
4065
  }
4016
- const sinceDate = this.determineSinceDate(options);
4066
+ const sinceDate = await this.determineSinceDateWithServerFallback(options);
4017
4067
  if (options.claudeProjectDir && options.claudeProjectDir.trim() !== "") {
4018
4068
  return this.loadProjectSessions(options.claudeProjectDir, sinceDate);
4019
4069
  }
@@ -4036,6 +4086,34 @@ var init_send_orchestrator = __esm({
4036
4086
  }
4037
4087
  return void 0;
4038
4088
  }
4089
+ /**
4090
+ * Get the server's last session timestamp for incremental sync.
4091
+ * Returns undefined on error to allow fallback to local sync state.
4092
+ */
4093
+ async getServerLastSessionDate() {
4094
+ try {
4095
+ const result = await apiClient.getLastSessionDate();
4096
+ if (result.lastSessionTimestamp) {
4097
+ return new Date(result.lastSessionTimestamp);
4098
+ }
4099
+ return void 0;
4100
+ } catch (error) {
4101
+ logger.warn(`Failed to get server last session date, falling back to local sync: ${error instanceof Error ? error.message : "Unknown error"}`);
4102
+ return void 0;
4103
+ }
4104
+ }
4105
+ /**
4106
+ * Determine since date with server-side incremental sync fallback.
4107
+ * First tries server timestamp, then falls back to local sync state.
4108
+ */
4109
+ async determineSinceDateWithServerFallback(options) {
4110
+ const serverSince = await this.getServerLastSessionDate();
4111
+ if (serverSince) {
4112
+ logger.debug(`Using server timestamp for incremental sync: ${serverSince.toISOString()}`);
4113
+ return serverSince;
4114
+ }
4115
+ return this.determineSinceDate(options);
4116
+ }
4039
4117
  async loadProjectSessions(claudeProjectDir, sinceDate) {
4040
4118
  const dirName = parseProjectName(claudeProjectDir);
4041
4119
  const project = await analyzeProject(claudeProjectDir, dirName);
@@ -11422,6 +11500,20 @@ var manual_sync_menu_exports = {};
11422
11500
  __export(manual_sync_menu_exports, {
11423
11501
  showManualSyncMenu: () => showManualSyncMenu
11424
11502
  });
11503
+ async function getServerLastSessionDate() {
11504
+ try {
11505
+ if (!await isAuthenticated()) {
11506
+ return void 0;
11507
+ }
11508
+ const result = await apiClient.getLastSessionDate();
11509
+ if (result.lastSessionTimestamp) {
11510
+ return new Date(result.lastSessionTimestamp);
11511
+ }
11512
+ return void 0;
11513
+ } catch {
11514
+ return void 0;
11515
+ }
11516
+ }
11425
11517
  async function showManualSyncMenu() {
11426
11518
  console.log("");
11427
11519
  console.log(colors.primary("Manual sync to cloud"));
@@ -11562,22 +11654,39 @@ async function showManualSyncMenu() {
11562
11654
  }
11563
11655
  }
11564
11656
  case "all": {
11565
- const projects = await discoverProjects();
11566
- const totalSessions = projects.reduce((sum, p) => sum + p.sessions, 0);
11567
- console.log("");
11568
- console.log(colors.info(`This will sync ${totalSessions} sessions from ${projects.length} projects.`));
11569
- const { confirm } = await import_inquirer14.default.prompt([
11570
- {
11571
- type: "confirm",
11572
- name: "confirm",
11573
- message: "Continue with syncing all projects?",
11574
- default: true
11657
+ const spinner = createSpinner("Calculating sessions to sync...").start();
11658
+ try {
11659
+ const serverSince = await getServerLastSessionDate();
11660
+ const sessions = await readClaudeSessions({ since: serverSince });
11661
+ const validSessions = sessions.filter((s) => s.duration >= 240);
11662
+ const projectPaths = new Set(validSessions.map((s) => {
11663
+ var _a;
11664
+ return (_a = s.sourceFile) == null ? void 0 : _a.claudeProjectPath;
11665
+ }).filter(Boolean));
11666
+ spinner.stop();
11667
+ console.log("");
11668
+ if (serverSince) {
11669
+ console.log(colors.info(`This will sync ${validSessions.length} new sessions from ${projectPaths.size} projects`));
11670
+ console.log(colors.subdued(`(Sessions since ${serverSince.toLocaleDateString()})`));
11671
+ } else {
11672
+ console.log(colors.info(`This will sync ${validSessions.length} sessions from ${projectPaths.size} projects.`));
11575
11673
  }
11576
- ]);
11577
- if (!confirm) {
11674
+ const { confirm } = await import_inquirer14.default.prompt([
11675
+ {
11676
+ type: "confirm",
11677
+ name: "confirm",
11678
+ message: `Continue with syncing ${validSessions.length} sessions?`,
11679
+ default: true
11680
+ }
11681
+ ]);
11682
+ if (!confirm) {
11683
+ return { type: "cancel" };
11684
+ }
11685
+ return { type: "all" };
11686
+ } catch (error) {
11687
+ spinner.fail(colors.error("Failed to calculate sessions"));
11578
11688
  return { type: "cancel" };
11579
11689
  }
11580
- return { type: "all" };
11581
11690
  }
11582
11691
  default:
11583
11692
  return { type: "cancel" };
@@ -11595,6 +11704,8 @@ var init_manual_sync_menu = __esm({
11595
11704
  init_claude();
11596
11705
  init_project_display();
11597
11706
  init_ui();
11707
+ init_api_client();
11708
+ init_token();
11598
11709
  }
11599
11710
  });
11600
11711
 
@@ -15608,7 +15719,7 @@ function createSection(title, content, options) {
15608
15719
  lines.push(color(chars.bl + chars.h.repeat(width - 2) + chars.br));
15609
15720
  return lines.join("\n");
15610
15721
  }
15611
- function createCloudStatusSection(status2) {
15722
+ function createCloudStatusSection(status2, serverLastSessionDate) {
15612
15723
  const content = [];
15613
15724
  const connectionIcon = status2.connected ? icons.success : icons.error;
15614
15725
  const connectionColor = status2.connected ? colors.success : colors.error;
@@ -15636,12 +15747,13 @@ function createCloudStatusSection(status2) {
15636
15747
  let syncColor = colors.primary;
15637
15748
  let syncText = "Never synced";
15638
15749
  let syncProject = "";
15639
- if (status2.lastSync) {
15640
- const timeSince = (/* @__PURE__ */ new Date()).getTime() - status2.lastSync.getTime();
15750
+ const lastSyncDate = serverLastSessionDate || status2.lastSync;
15751
+ if (lastSyncDate) {
15752
+ const timeSince = (/* @__PURE__ */ new Date()).getTime() - lastSyncDate.getTime();
15641
15753
  const minutes = Math.floor(timeSince / 6e4);
15642
15754
  const hours = Math.floor(minutes / 60);
15643
15755
  const days = Math.floor(hours / 24);
15644
- if (status2.lastSyncProject) {
15756
+ if (!serverLastSessionDate && status2.lastSyncProject) {
15645
15757
  syncProject = `${status2.lastSyncProject} `;
15646
15758
  }
15647
15759
  if (days > 0) {
@@ -15722,9 +15834,9 @@ function createLocalEngineSection(engine) {
15722
15834
  color: sectionColor
15723
15835
  });
15724
15836
  }
15725
- async function createStatusDashboard(cloud, local) {
15837
+ async function createStatusDashboard(cloud, local, serverLastSessionDate) {
15726
15838
  const sections = [];
15727
- sections.push(createCloudStatusSection(cloud));
15839
+ sections.push(createCloudStatusSection(cloud, serverLastSessionDate));
15728
15840
  sections.push("");
15729
15841
  sections.push(createLocalEngineSection(local));
15730
15842
  return sections.join("\n");
@@ -16135,7 +16247,18 @@ async function showMainMenu(state, packageUpdateInfo2) {
16135
16247
  statusLineStatus: state.statusLineStatus,
16136
16248
  configPath: "~/.claude/config"
16137
16249
  };
16138
- console.log(await createStatusDashboard(cloudStatus, localEngine));
16250
+ let serverLastSessionDate;
16251
+ if (state.hasAuth) {
16252
+ try {
16253
+ const { apiClient: apiClient2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
16254
+ const result = await apiClient2.getLastSessionDate();
16255
+ if (result.lastSessionTimestamp) {
16256
+ serverLastSessionDate = new Date(result.lastSessionTimestamp);
16257
+ }
16258
+ } catch {
16259
+ }
16260
+ }
16261
+ console.log(await createStatusDashboard(cloudStatus, localEngine, serverLastSessionDate));
16139
16262
  console.log("");
16140
16263
  const context = {
16141
16264
  state: state.state,