claude-scope 0.2.4 → 0.3.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/claude-scope.cjs +301 -29
  2. package/package.json +2 -1
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
6
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
9
  var __export = (target, all) => {
8
10
  for (var name in all)
@@ -16,6 +18,14 @@ var __copyProps = (to, from, except, desc) => {
16
18
  }
17
19
  return to;
18
20
  };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
19
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
30
 
21
31
  // src/index.ts
@@ -143,34 +153,51 @@ var Renderer = class {
143
153
  this.showErrors = options.showErrors ?? false;
144
154
  }
145
155
  /**
146
- * Render widgets into a single line with error boundaries
156
+ * Render widgets into multiple lines with error boundaries
147
157
  *
148
- * Widgets that throw errors are logged (via onError callback) and skipped,
149
- * allowing other widgets to continue rendering.
158
+ * Widgets are grouped by their metadata.line property and rendered
159
+ * on separate lines. Widgets that throw errors are logged (via onError
160
+ * callback) and skipped, allowing other widgets to continue rendering.
150
161
  *
151
162
  * @param widgets - Array of widgets to render
152
163
  * @param context - Render context with width and timestamp
153
- * @returns Combined widget outputs separated by separator
164
+ * @returns Array of rendered lines (one per line number)
154
165
  */
155
166
  async render(widgets, context) {
156
- const outputs = [];
167
+ const lineMap = /* @__PURE__ */ new Map();
157
168
  for (const widget of widgets) {
158
169
  if (!widget.isEnabled()) {
159
170
  continue;
160
171
  }
161
- try {
162
- const output = await widget.render(context);
163
- if (output !== null) {
164
- outputs.push(output);
165
- }
166
- } catch (error) {
167
- this.handleError(error, widget);
168
- if (this.showErrors) {
169
- outputs.push(`${widget.id}:<err>`);
172
+ const line = widget.metadata.line ?? 0;
173
+ if (!lineMap.has(line)) {
174
+ lineMap.set(line, []);
175
+ }
176
+ lineMap.get(line).push(widget);
177
+ }
178
+ const lines = [];
179
+ const sortedLines = Array.from(lineMap.entries()).sort((a, b) => a[0] - b[0]);
180
+ for (const [, widgetsForLine] of sortedLines) {
181
+ const outputs = [];
182
+ for (const widget of widgetsForLine) {
183
+ try {
184
+ const output = await widget.render(context);
185
+ if (output !== null) {
186
+ outputs.push(output);
187
+ }
188
+ } catch (error) {
189
+ this.handleError(error, widget);
190
+ if (this.showErrors) {
191
+ outputs.push(`${widget.id}:<err>`);
192
+ }
170
193
  }
171
194
  }
195
+ const line = outputs.join(this.separator);
196
+ if (line) {
197
+ lines.push(line);
198
+ }
172
199
  }
173
- return outputs.join(this.separator);
200
+ return lines;
174
201
  }
175
202
  /**
176
203
  * Set custom separator
@@ -193,12 +220,13 @@ var Renderer = class {
193
220
  };
194
221
 
195
222
  // src/core/widget-types.ts
196
- function createWidgetMetadata(name, description, version = "1.0.0", author = "claude-scope") {
223
+ function createWidgetMetadata(name, description, version = "1.0.0", author = "claude-scope", line = 0) {
197
224
  return {
198
225
  name,
199
226
  description,
200
227
  version,
201
- author
228
+ author,
229
+ line
202
230
  };
203
231
  }
204
232
 
@@ -252,7 +280,11 @@ var GitWidget = class {
252
280
  id = "git";
253
281
  metadata = createWidgetMetadata(
254
282
  "Git Widget",
255
- "Displays current git branch"
283
+ "Displays current git branch",
284
+ "1.0.0",
285
+ "claude-scope",
286
+ 0
287
+ // First line
256
288
  );
257
289
  gitFactory;
258
290
  git = null;
@@ -360,7 +392,11 @@ var ModelWidget = class extends StdinDataWidget {
360
392
  id = "model";
361
393
  metadata = createWidgetMetadata(
362
394
  "Model",
363
- "Displays the current Claude model name"
395
+ "Displays the current Claude model name",
396
+ "1.0.0",
397
+ "claude-scope",
398
+ 0
399
+ // First line
364
400
  );
365
401
  renderWithData(data, context) {
366
402
  return data.model.display_name;
@@ -424,7 +460,11 @@ var ContextWidget = class extends StdinDataWidget {
424
460
  id = "context";
425
461
  metadata = createWidgetMetadata(
426
462
  "Context",
427
- "Displays context window usage with progress bar"
463
+ "Displays context window usage with progress bar",
464
+ "1.0.0",
465
+ "claude-scope",
466
+ 0
467
+ // First line
428
468
  );
429
469
  renderWithData(data, context) {
430
470
  const { current_usage, context_window_size } = data.context_window;
@@ -442,7 +482,11 @@ var CostWidget = class extends StdinDataWidget {
442
482
  id = "cost";
443
483
  metadata = createWidgetMetadata(
444
484
  "Cost",
445
- "Displays session cost in USD"
485
+ "Displays session cost in USD",
486
+ "1.0.0",
487
+ "claude-scope",
488
+ 0
489
+ // First line
446
490
  );
447
491
  renderWithData(data, context) {
448
492
  if (!data.cost || data.cost.total_cost_usd === void 0) return null;
@@ -450,12 +494,36 @@ var CostWidget = class extends StdinDataWidget {
450
494
  }
451
495
  };
452
496
 
497
+ // src/widgets/lines-widget.ts
498
+ var LinesWidget = class extends StdinDataWidget {
499
+ id = "lines";
500
+ metadata = createWidgetMetadata(
501
+ "Lines",
502
+ "Displays lines added/removed in session",
503
+ "1.0.0",
504
+ "claude-scope",
505
+ 0
506
+ // First line
507
+ );
508
+ renderWithData(data, context) {
509
+ const added = data.cost?.total_lines_added ?? 0;
510
+ const removed = data.cost?.total_lines_removed ?? 0;
511
+ const addedStr = colorize(`+${added}`, ANSI_COLORS.GREEN);
512
+ const removedStr = colorize(`-${removed}`, ANSI_COLORS.RED);
513
+ return `${addedStr}/${removedStr}`;
514
+ }
515
+ };
516
+
453
517
  // src/widgets/duration-widget.ts
454
518
  var DurationWidget = class extends StdinDataWidget {
455
519
  id = "duration";
456
520
  metadata = createWidgetMetadata(
457
521
  "Duration",
458
- "Displays elapsed session time"
522
+ "Displays elapsed session time",
523
+ "1.0.0",
524
+ "claude-scope",
525
+ 0
526
+ // First line
459
527
  );
460
528
  renderWithData(data, context) {
461
529
  if (!data.cost || data.cost.total_duration_ms === void 0) return null;
@@ -468,7 +536,11 @@ var GitChangesWidget = class {
468
536
  id = "git-changes";
469
537
  metadata = createWidgetMetadata(
470
538
  "Git Changes",
471
- "Displays git diff statistics"
539
+ "Displays git diff statistics",
540
+ "1.0.0",
541
+ "claude-scope",
542
+ 0
543
+ // First line
472
544
  );
473
545
  gitFactory;
474
546
  git = null;
@@ -533,16 +605,214 @@ var GitChangesWidget = class {
533
605
  }
534
606
  };
535
607
 
608
+ // src/providers/config-provider.ts
609
+ var fs = __toESM(require("fs/promises"), 1);
610
+ var path = __toESM(require("path"), 1);
611
+ var os = __toESM(require("os"), 1);
612
+ var ConfigProvider = class {
613
+ cachedCounts;
614
+ lastScan = 0;
615
+ cacheInterval = 5e3;
616
+ // 5 seconds
617
+ /**
618
+ * Get config counts with hybrid caching
619
+ * Scans filesystem if cache is stale (>5 seconds)
620
+ */
621
+ async getConfigs(options = {}) {
622
+ const now = Date.now();
623
+ if (this.cachedCounts && now - this.lastScan < this.cacheInterval) {
624
+ return this.cachedCounts;
625
+ }
626
+ this.cachedCounts = await this.scanConfigs(options);
627
+ this.lastScan = now;
628
+ return this.cachedCounts;
629
+ }
630
+ /**
631
+ * Scan filesystem for Claude Code configurations
632
+ */
633
+ async scanConfigs(options) {
634
+ let claudeMdCount = 0;
635
+ let rulesCount = 0;
636
+ let mcpCount = 0;
637
+ let hooksCount = 0;
638
+ const homeDir = os.homedir();
639
+ const claudeDir = path.join(homeDir, ".claude");
640
+ const cwd = options.cwd;
641
+ if (await this.fileExists(path.join(claudeDir, "CLAUDE.md"))) {
642
+ claudeMdCount++;
643
+ }
644
+ rulesCount += await this.countRulesInDir(path.join(claudeDir, "rules"));
645
+ const userSettings = path.join(claudeDir, "settings.json");
646
+ const userSettingsData = await this.readJsonFile(userSettings);
647
+ if (userSettingsData) {
648
+ mcpCount += this.countMcpServers(userSettingsData);
649
+ hooksCount += this.countHooks(userSettingsData);
650
+ }
651
+ const userClaudeJson = path.join(homeDir, ".claude.json");
652
+ const userClaudeData = await this.readJsonFile(userClaudeJson);
653
+ if (userClaudeData) {
654
+ const userMcpCount = this.countMcpServers(userClaudeData);
655
+ mcpCount += Math.max(0, userMcpCount - this.countMcpServers(userSettingsData || {}));
656
+ }
657
+ if (cwd) {
658
+ if (await this.fileExists(path.join(cwd, "CLAUDE.md"))) {
659
+ claudeMdCount++;
660
+ }
661
+ if (await this.fileExists(path.join(cwd, "CLAUDE.local.md"))) {
662
+ claudeMdCount++;
663
+ }
664
+ if (await this.fileExists(path.join(cwd, ".claude", "CLAUDE.md"))) {
665
+ claudeMdCount++;
666
+ }
667
+ if (await this.fileExists(path.join(cwd, ".claude", "CLAUDE.local.md"))) {
668
+ claudeMdCount++;
669
+ }
670
+ rulesCount += await this.countRulesInDir(path.join(cwd, ".claude", "rules"));
671
+ const mcpJson = path.join(cwd, ".mcp.json");
672
+ const mcpData = await this.readJsonFile(mcpJson);
673
+ if (mcpData) {
674
+ mcpCount += this.countMcpServers(mcpData);
675
+ }
676
+ const projectSettings = path.join(cwd, ".claude", "settings.json");
677
+ const projectSettingsData = await this.readJsonFile(projectSettings);
678
+ if (projectSettingsData) {
679
+ mcpCount += this.countMcpServers(projectSettingsData);
680
+ hooksCount += this.countHooks(projectSettingsData);
681
+ }
682
+ const localSettings = path.join(cwd, ".claude", "settings.local.json");
683
+ const localSettingsData = await this.readJsonFile(localSettings);
684
+ if (localSettingsData) {
685
+ mcpCount += this.countMcpServers(localSettingsData);
686
+ hooksCount += this.countHooks(localSettingsData);
687
+ }
688
+ }
689
+ return { claudeMdCount, rulesCount, mcpCount, hooksCount };
690
+ }
691
+ /**
692
+ * Check if file exists
693
+ */
694
+ async fileExists(filePath) {
695
+ try {
696
+ await fs.access(filePath);
697
+ return true;
698
+ } catch {
699
+ return false;
700
+ }
701
+ }
702
+ /**
703
+ * Read and parse JSON file
704
+ */
705
+ async readJsonFile(filePath) {
706
+ try {
707
+ const content = await fs.readFile(filePath, "utf8");
708
+ return JSON.parse(content);
709
+ } catch {
710
+ return null;
711
+ }
712
+ }
713
+ /**
714
+ * Count MCP servers in config object
715
+ */
716
+ countMcpServers(config) {
717
+ if (!config || !config.mcpServers || typeof config.mcpServers !== "object") {
718
+ return 0;
719
+ }
720
+ return Object.keys(config.mcpServers).length;
721
+ }
722
+ /**
723
+ * Count hooks in config object
724
+ */
725
+ countHooks(config) {
726
+ if (!config || !config.hooks || typeof config.hooks !== "object") {
727
+ return 0;
728
+ }
729
+ return Object.keys(config.hooks).length;
730
+ }
731
+ /**
732
+ * Recursively count .md files in directory
733
+ */
734
+ async countRulesInDir(rulesDir) {
735
+ const exists = await this.fileExists(rulesDir);
736
+ if (!exists) return 0;
737
+ try {
738
+ let count = 0;
739
+ const entries = await fs.readdir(rulesDir, { withFileTypes: true });
740
+ for (const entry of entries) {
741
+ const fullPath = path.join(rulesDir, entry.name);
742
+ if (entry.isDirectory()) {
743
+ count += await this.countRulesInDir(fullPath);
744
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
745
+ count++;
746
+ }
747
+ }
748
+ return count;
749
+ } catch {
750
+ return 0;
751
+ }
752
+ }
753
+ };
754
+
755
+ // src/widgets/config-count-widget.ts
756
+ var ConfigCountWidget = class {
757
+ id = "config-count";
758
+ metadata = createWidgetMetadata(
759
+ "Config Count",
760
+ "Displays Claude Code configuration counts",
761
+ "1.0.0",
762
+ "claude-scope",
763
+ 1
764
+ // Second line
765
+ );
766
+ configProvider = new ConfigProvider();
767
+ configs;
768
+ cwd;
769
+ async initialize() {
770
+ }
771
+ async update(data) {
772
+ this.cwd = data.cwd;
773
+ this.configs = await this.configProvider.getConfigs({ cwd: data.cwd });
774
+ }
775
+ isEnabled() {
776
+ if (!this.configs) {
777
+ return false;
778
+ }
779
+ const { claudeMdCount, rulesCount, mcpCount, hooksCount } = this.configs;
780
+ return claudeMdCount > 0 || rulesCount > 0 || mcpCount > 0 || hooksCount > 0;
781
+ }
782
+ async render(context) {
783
+ if (!this.configs) {
784
+ return null;
785
+ }
786
+ const { claudeMdCount, rulesCount, mcpCount, hooksCount } = this.configs;
787
+ const parts = [];
788
+ if (claudeMdCount > 0) {
789
+ parts.push(`\u{1F4C4} ${claudeMdCount} CLAUDE.md`);
790
+ }
791
+ if (rulesCount > 0) {
792
+ parts.push(`\u{1F4DC} ${rulesCount} rules`);
793
+ }
794
+ if (mcpCount > 0) {
795
+ parts.push(`\u{1F50C} ${mcpCount} MCPs`);
796
+ }
797
+ if (hooksCount > 0) {
798
+ parts.push(`\u{1FA9D} ${hooksCount} hooks`);
799
+ }
800
+ return parts.join(" \u2502 ") || null;
801
+ }
802
+ async cleanup() {
803
+ }
804
+ };
805
+
536
806
  // src/validation/result.ts
537
807
  function success(data) {
538
808
  return { success: true, data };
539
809
  }
540
- function failure(path, message, value) {
541
- return { success: false, error: { path, message, value } };
810
+ function failure(path2, message, value) {
811
+ return { success: false, error: { path: path2, message, value } };
542
812
  }
543
813
  function formatError(error) {
544
- const path = error.path.length > 0 ? error.path.join(".") : "root";
545
- return `${path}: ${error.message}`;
814
+ const path2 = error.path.length > 0 ? error.path.join(".") : "root";
815
+ return `${path2}: ${error.message}`;
546
816
  }
547
817
 
548
818
  // src/validation/validators.ts
@@ -731,9 +1001,11 @@ async function main() {
731
1001
  await registry.register(new ModelWidget());
732
1002
  await registry.register(new ContextWidget());
733
1003
  await registry.register(new CostWidget());
1004
+ await registry.register(new LinesWidget());
734
1005
  await registry.register(new DurationWidget());
735
1006
  await registry.register(new GitWidget());
736
1007
  await registry.register(new GitChangesWidget());
1008
+ await registry.register(new ConfigCountWidget());
737
1009
  const renderer = new Renderer({
738
1010
  separator: " \u2502 ",
739
1011
  onError: (error, widget) => {
@@ -743,11 +1015,11 @@ async function main() {
743
1015
  for (const widget of registry.getAll()) {
744
1016
  await widget.update(stdinData);
745
1017
  }
746
- const output = await renderer.render(
1018
+ const lines = await renderer.render(
747
1019
  registry.getEnabledWidgets(),
748
1020
  { width: 80, timestamp: Date.now() }
749
1021
  );
750
- return output || "";
1022
+ return lines.join("\n");
751
1023
  } catch (error) {
752
1024
  const fallback = await tryGitFallback();
753
1025
  return fallback;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-scope",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Claude Code plugin for session status and analytics",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,6 +32,7 @@
32
32
  "c8": "^10.1.3",
33
33
  "chai": "^6.2.2",
34
34
  "esbuild": "^0.24.2",
35
+ "rimraf": "^6.1.2",
35
36
  "tsx": "^4.19.2",
36
37
  "typescript": "^5.7.2"
37
38
  },