claude-scope 0.2.5 → 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 +285 -30
  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;
@@ -455,7 +499,11 @@ var LinesWidget = class extends StdinDataWidget {
455
499
  id = "lines";
456
500
  metadata = createWidgetMetadata(
457
501
  "Lines",
458
- "Displays lines added/removed in session"
502
+ "Displays lines added/removed in session",
503
+ "1.0.0",
504
+ "claude-scope",
505
+ 0
506
+ // First line
459
507
  );
460
508
  renderWithData(data, context) {
461
509
  const added = data.cost?.total_lines_added ?? 0;
@@ -471,7 +519,11 @@ var DurationWidget = class extends StdinDataWidget {
471
519
  id = "duration";
472
520
  metadata = createWidgetMetadata(
473
521
  "Duration",
474
- "Displays elapsed session time"
522
+ "Displays elapsed session time",
523
+ "1.0.0",
524
+ "claude-scope",
525
+ 0
526
+ // First line
475
527
  );
476
528
  renderWithData(data, context) {
477
529
  if (!data.cost || data.cost.total_duration_ms === void 0) return null;
@@ -484,7 +536,11 @@ var GitChangesWidget = class {
484
536
  id = "git-changes";
485
537
  metadata = createWidgetMetadata(
486
538
  "Git Changes",
487
- "Displays git diff statistics"
539
+ "Displays git diff statistics",
540
+ "1.0.0",
541
+ "claude-scope",
542
+ 0
543
+ // First line
488
544
  );
489
545
  gitFactory;
490
546
  git = null;
@@ -549,16 +605,214 @@ var GitChangesWidget = class {
549
605
  }
550
606
  };
551
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
+
552
806
  // src/validation/result.ts
553
807
  function success(data) {
554
808
  return { success: true, data };
555
809
  }
556
- function failure(path, message, value) {
557
- return { success: false, error: { path, message, value } };
810
+ function failure(path2, message, value) {
811
+ return { success: false, error: { path: path2, message, value } };
558
812
  }
559
813
  function formatError(error) {
560
- const path = error.path.length > 0 ? error.path.join(".") : "root";
561
- return `${path}: ${error.message}`;
814
+ const path2 = error.path.length > 0 ? error.path.join(".") : "root";
815
+ return `${path2}: ${error.message}`;
562
816
  }
563
817
 
564
818
  // src/validation/validators.ts
@@ -751,6 +1005,7 @@ async function main() {
751
1005
  await registry.register(new DurationWidget());
752
1006
  await registry.register(new GitWidget());
753
1007
  await registry.register(new GitChangesWidget());
1008
+ await registry.register(new ConfigCountWidget());
754
1009
  const renderer = new Renderer({
755
1010
  separator: " \u2502 ",
756
1011
  onError: (error, widget) => {
@@ -760,11 +1015,11 @@ async function main() {
760
1015
  for (const widget of registry.getAll()) {
761
1016
  await widget.update(stdinData);
762
1017
  }
763
- const output = await renderer.render(
1018
+ const lines = await renderer.render(
764
1019
  registry.getEnabledWidgets(),
765
1020
  { width: 80, timestamp: Date.now() }
766
1021
  );
767
- return output || "";
1022
+ return lines.join("\n");
768
1023
  } catch (error) {
769
1024
  const fallback = await tryGitFallback();
770
1025
  return fallback;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-scope",
3
- "version": "0.2.5",
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
  },