claude-scope 0.8.14 → 0.8.16

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 +595 -498
  2. package/package.json +1 -1
@@ -1471,7 +1471,7 @@ var init_renderer = __esm({
1471
1471
  for (const widget of widgetsForLine) {
1472
1472
  try {
1473
1473
  const output = await widget.render(context);
1474
- if (output !== null) {
1474
+ if (output && output.trim().length > 0) {
1475
1475
  outputs.push(output);
1476
1476
  }
1477
1477
  } catch (error) {
@@ -1952,27 +1952,58 @@ var init_styles = __esm({
1952
1952
  return compactStyle(data, colors2);
1953
1953
  },
1954
1954
  /**
1955
- * playful: Emojis (📖✏️✨🔄🔍📁) with tool names
1955
+ * playful: Emojis (📖✏️✨🔄🔍📁) with tool counts (like balanced but more fun)
1956
+ * - Shows running + completed counts like balanced style
1957
+ * - Uses emojis instead of text labels for a more playful look
1956
1958
  */
1957
1959
  playful: (data, colors2) => {
1958
1960
  const parts = [];
1961
+ const c = colors2 ?? getDefaultColors();
1959
1962
  const emojis = {
1960
1963
  Read: "\u{1F4D6}",
1961
1964
  Write: "\u270F\uFE0F",
1962
1965
  Edit: "\u2728",
1963
1966
  Bash: "\u{1F504}",
1964
1967
  Grep: "\u{1F50D}",
1965
- Glob: "\u{1F4C1}"
1968
+ Glob: "\u{1F4C1}",
1969
+ Task: "\u{1F4CB}"
1966
1970
  };
1967
- for (const tool of data.running.slice(-3)) {
1968
- const emoji = emojis[tool.name] ?? "\u{1F527}";
1969
- const nameStr = colors2 ? colorize(tool.name, colors2.tools.name) : tool.name;
1970
- parts.push(`${emoji} ${nameStr}`);
1971
+ const allToolNames = /* @__PURE__ */ new Set();
1972
+ for (const tool of data.running) {
1973
+ allToolNames.add(tool.name);
1974
+ }
1975
+ for (const [name] of data.completed.slice(0, 3)) {
1976
+ allToolNames.add(name);
1977
+ }
1978
+ const completedMap = new Map(data.completed);
1979
+ const runningCounts = /* @__PURE__ */ new Map();
1980
+ for (const tool of data.running) {
1981
+ runningCounts.set(tool.name, (runningCounts.get(tool.name) ?? 0) + 1);
1982
+ }
1983
+ for (const name of allToolNames) {
1984
+ const runningCount = runningCounts.get(name) ?? 0;
1985
+ const completedCount = completedMap.get(name) ?? 0;
1986
+ const emoji = emojis[name] ?? "\u{1F527}";
1987
+ if (runningCount > 0 && completedCount > 0) {
1988
+ const nameStr = colorize(name, c.tools.name);
1989
+ const runningStr = colorize(`\u25B6${runningCount}`, c.tools.running);
1990
+ const doneStr = colorize(`\u2713${completedCount}`, c.tools.completed);
1991
+ parts.push(`${emoji} ${nameStr} (${runningStr}, ${doneStr})`);
1992
+ } else if (completedCount > 0) {
1993
+ const pluralName = pluralizeTool(name);
1994
+ const nameStr = colorize(pluralName, c.tools.name);
1995
+ const countStr = colorize(`${completedCount}`, c.tools.count);
1996
+ parts.push(`${emoji} ${nameStr}: ${countStr}`);
1997
+ } else if (runningCount > 0) {
1998
+ const nameStr = colorize(name, c.tools.name);
1999
+ const runningStr = colorize(`\u25B6${runningCount}`, c.tools.running);
2000
+ parts.push(`${emoji} ${nameStr} (${runningStr})`);
2001
+ }
1971
2002
  }
1972
2003
  if (parts.length === 0) {
1973
2004
  return "";
1974
2005
  }
1975
- return parts.join(", ");
2006
+ return parts.join(" | ");
1976
2007
  },
1977
2008
  /**
1978
2009
  * verbose: Full text labels "Running:" and "Completed:"
@@ -3189,6 +3220,472 @@ var init_cost_widget = __esm({
3189
3220
  }
3190
3221
  });
3191
3222
 
3223
+ // src/widgets/dev-server/port-detector.ts
3224
+ var import_node_child_process, import_node_util, execFileAsync, PORT_MAPPING, DEV_PORTS, PortDetector;
3225
+ var init_port_detector = __esm({
3226
+ "src/widgets/dev-server/port-detector.ts"() {
3227
+ "use strict";
3228
+ import_node_child_process = require("node:child_process");
3229
+ import_node_util = require("node:util");
3230
+ execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
3231
+ PORT_MAPPING = {
3232
+ 5173: { name: "Vite", icon: "\u26A1" },
3233
+ 4200: { name: "Angular", icon: "\u25B2" },
3234
+ 3e3: { name: "Dev", icon: "\u{1F680}" },
3235
+ 8080: { name: "Webpack", icon: "\u{1F4E6}" },
3236
+ 8e3: { name: "Dev", icon: "\u{1F680}" },
3237
+ 8888: { name: "Dev", icon: "\u{1F680}" }
3238
+ };
3239
+ DEV_PORTS = [5173, 4200, 3e3, 8080, 8e3, 8888];
3240
+ PortDetector = class {
3241
+ execFn;
3242
+ /**
3243
+ * Create a new PortDetector
3244
+ * @param execFn - Optional execFile function for testing (dependency injection)
3245
+ */
3246
+ constructor(execFn) {
3247
+ this.execFn = execFn ?? execFileAsync;
3248
+ }
3249
+ /**
3250
+ * Detect running dev servers by checking listening ports
3251
+ * @returns Detected server info or null
3252
+ */
3253
+ async detect() {
3254
+ try {
3255
+ const args = [
3256
+ "-nP",
3257
+ // No host names, no port names
3258
+ "-iTCP",
3259
+ // Internet TCP
3260
+ "-sTCP:LISTEN"
3261
+ // TCP LISTEN state only
3262
+ ];
3263
+ for (const port of DEV_PORTS) {
3264
+ args.push("-i", `:${port}`);
3265
+ }
3266
+ const { stdout } = await this.execFn("lsof", args, {
3267
+ timeout: 2e3
3268
+ });
3269
+ const lines = stdout.trim().split("\n");
3270
+ for (const line of lines) {
3271
+ if (!line || line.startsWith("COMMAND")) continue;
3272
+ const match = line.match(/:(\d+)\s*\(LISTEN\)/);
3273
+ if (match) {
3274
+ const port = parseInt(match[1], 10);
3275
+ const mapping = PORT_MAPPING[port];
3276
+ if (mapping) {
3277
+ return {
3278
+ name: mapping.name,
3279
+ icon: mapping.icon,
3280
+ port,
3281
+ isRunning: true,
3282
+ isBuilding: false
3283
+ };
3284
+ }
3285
+ }
3286
+ }
3287
+ return null;
3288
+ } catch {
3289
+ return null;
3290
+ }
3291
+ }
3292
+ };
3293
+ }
3294
+ });
3295
+
3296
+ // src/widgets/dev-server/process-detector.ts
3297
+ var import_node_child_process2, import_node_util2, execFileAsync2, ProcessDetector;
3298
+ var init_process_detector = __esm({
3299
+ "src/widgets/dev-server/process-detector.ts"() {
3300
+ "use strict";
3301
+ import_node_child_process2 = require("node:child_process");
3302
+ import_node_util2 = require("node:util");
3303
+ execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
3304
+ ProcessDetector = class {
3305
+ execFn;
3306
+ processPatterns = [
3307
+ // Generic server patterns - more specific to avoid shell history false positives
3308
+ { regex: /^[\w\s]+\/npm\s+(exec|run)\s+serve/i, name: "Server", icon: "\u{1F310}" },
3309
+ { regex: /^[\w\s]+\/npx\s+serve\s+-/i, name: "Server", icon: "\u{1F310}" },
3310
+ { regex: /^[\w\s]+\/(python|python3)\s+-m\s+http\.server/i, name: "HTTP", icon: "\u{1F310}" },
3311
+ // Generic dev/build patterns - require full command path
3312
+ { regex: /^[\w\s]+\/(npm|yarn|pnpm|bun)\s+run\s+dev\s*$/i, name: "Dev", icon: "\u{1F680}" },
3313
+ { regex: /^[\w\s]+\/(npm|yarn|pnpm|bun)\s+run\s+build\s*$/i, name: "Build", icon: "\u{1F528}" },
3314
+ // Framework-specific patterns - require executable path
3315
+ { regex: /\/(nuxt|next|astro|remix|svelte)\s+dev/i, name: "Framework", icon: "\u26A1" },
3316
+ { regex: /\/node.*\/vite\s*$/i, name: "Vite", icon: "\u26A1" },
3317
+ // Fallback: simpler patterns but checked last
3318
+ { regex: /\s(nuxt|next|vite)\s+dev\s/i, name: "DevServer", icon: "\u26A1" }
3319
+ ];
3320
+ /**
3321
+ * Create a new ProcessDetector
3322
+ * @param execFn - Optional execFile function for testing (dependency injection)
3323
+ */
3324
+ constructor(execFn) {
3325
+ this.execFn = execFn ?? execFileAsync2;
3326
+ }
3327
+ /**
3328
+ * Detect running dev server by parsing system process list
3329
+ * @returns Detected server status or null
3330
+ */
3331
+ async detect() {
3332
+ try {
3333
+ const { stdout } = await this.execFn("ps", ["aux"], {
3334
+ timeout: 1e3
3335
+ });
3336
+ for (const pattern of this.processPatterns) {
3337
+ if (pattern.regex.test(stdout)) {
3338
+ const isBuilding = pattern.name.toLowerCase().includes("build");
3339
+ const isRunning = !isBuilding;
3340
+ return {
3341
+ name: pattern.name,
3342
+ icon: pattern.icon,
3343
+ isRunning,
3344
+ isBuilding
3345
+ };
3346
+ }
3347
+ }
3348
+ } catch {
3349
+ }
3350
+ return null;
3351
+ }
3352
+ };
3353
+ }
3354
+ });
3355
+
3356
+ // src/widgets/dev-server/styles.ts
3357
+ var devServerStyles;
3358
+ var init_styles6 = __esm({
3359
+ "src/widgets/dev-server/styles.ts"() {
3360
+ "use strict";
3361
+ init_colors();
3362
+ devServerStyles = {
3363
+ balanced: (data, colors2) => {
3364
+ if (!data.server) return "";
3365
+ const { name, icon, isRunning, isBuilding } = data.server;
3366
+ const status = isRunning ? "running" : isBuilding ? "building" : "stopped";
3367
+ const coloredName = colors2 ? colorize(name, colors2.name) : name;
3368
+ const coloredStatus = colors2 ? colorize(`(${status})`, colors2.status) : `(${status})`;
3369
+ return `${icon} ${coloredName} ${coloredStatus}`;
3370
+ },
3371
+ compact: (data, colors2) => {
3372
+ if (!data.server) return "";
3373
+ const { name, icon, isRunning, isBuilding } = data.server;
3374
+ const statusIcon = isRunning ? "\u{1F680}" : isBuilding ? "\u{1F528}" : "\u{1F4A4}";
3375
+ const coloredName = colors2 ? colorize(name, colors2.name) : name;
3376
+ return `${icon} ${coloredName} ${statusIcon}`;
3377
+ },
3378
+ playful: (data, colors2) => {
3379
+ if (!data.server) return "";
3380
+ const { name, isRunning, isBuilding } = data.server;
3381
+ const emoji = isRunning ? "\u{1F3C3}" : isBuilding ? "\u{1F528}" : "\u{1F4A4}";
3382
+ const coloredName = colors2 ? colorize(name, colors2.name) : name;
3383
+ return `${emoji} ${coloredName}`;
3384
+ },
3385
+ verbose: (data, colors2) => {
3386
+ if (!data.server) return "";
3387
+ const { name, isRunning, isBuilding } = data.server;
3388
+ const status = isRunning ? "running" : isBuilding ? "building" : "stopped";
3389
+ const label = colors2 ? colorize("Dev Server:", colors2.label) : "Dev Server:";
3390
+ const coloredName = colors2 ? colorize(name, colors2.name) : name;
3391
+ const coloredStatus = colors2 ? colorize(`(${status})`, colors2.status) : `(${status})`;
3392
+ return `${label} ${coloredName} ${coloredStatus}`;
3393
+ },
3394
+ labeled: (data, colors2) => {
3395
+ if (!data.server) return "";
3396
+ const { name, icon, isRunning } = data.server;
3397
+ const status = isRunning ? "\u{1F7E2}" : "\u{1F534}";
3398
+ const label = colors2 ? colorize("Server:", colors2.label) : "Server:";
3399
+ const coloredName = colors2 ? colorize(name, colors2.name) : name;
3400
+ return `${label} ${icon} ${coloredName} ${status}`;
3401
+ },
3402
+ indicator: (data, colors2) => {
3403
+ if (!data.server) return "";
3404
+ const { name, icon } = data.server;
3405
+ const coloredName = colors2 ? colorize(name, colors2.name) : name;
3406
+ return `\u25CF ${icon} ${coloredName}`;
3407
+ }
3408
+ };
3409
+ }
3410
+ });
3411
+
3412
+ // src/widgets/dev-server/dev-server-widget.ts
3413
+ var DevServerWidget;
3414
+ var init_dev_server_widget = __esm({
3415
+ "src/widgets/dev-server/dev-server-widget.ts"() {
3416
+ "use strict";
3417
+ init_widget_types();
3418
+ init_theme();
3419
+ init_port_detector();
3420
+ init_process_detector();
3421
+ init_styles6();
3422
+ DevServerWidget = class {
3423
+ id = "dev-server";
3424
+ metadata = createWidgetMetadata(
3425
+ "Dev Server",
3426
+ "Detects running dev server processes using hybrid port+process detection",
3427
+ "1.1.0",
3428
+ "claude-scope",
3429
+ 0
3430
+ );
3431
+ enabled = true;
3432
+ colors;
3433
+ _lineOverride;
3434
+ styleFn = devServerStyles.balanced;
3435
+ cwd = null;
3436
+ portDetector;
3437
+ processDetector;
3438
+ constructor(colors2) {
3439
+ this.colors = colors2 ?? DEFAULT_THEME;
3440
+ this.portDetector = new PortDetector();
3441
+ this.processDetector = new ProcessDetector();
3442
+ }
3443
+ /**
3444
+ * Set display style
3445
+ * @param style - Style to use for rendering
3446
+ */
3447
+ setStyle(style = "balanced") {
3448
+ const fn = devServerStyles[style];
3449
+ if (fn) {
3450
+ this.styleFn = fn;
3451
+ }
3452
+ }
3453
+ /**
3454
+ * Set display line override
3455
+ * @param line - Line number (0-indexed)
3456
+ */
3457
+ setLine(line) {
3458
+ this._lineOverride = line;
3459
+ }
3460
+ /**
3461
+ * Get display line
3462
+ * @returns Line number (0-indexed)
3463
+ */
3464
+ getLine() {
3465
+ return this._lineOverride ?? this.metadata.line ?? 0;
3466
+ }
3467
+ /**
3468
+ * Initialize widget with context
3469
+ * @param context - Widget initialization context
3470
+ */
3471
+ async initialize(context) {
3472
+ this.enabled = context.config?.enabled !== false;
3473
+ }
3474
+ /**
3475
+ * Update widget with new stdin data
3476
+ * @param data - Stdin data from Claude Code
3477
+ */
3478
+ async update(data) {
3479
+ this.cwd = data.cwd;
3480
+ }
3481
+ /**
3482
+ * Check if widget is enabled
3483
+ * @returns true if widget should render
3484
+ */
3485
+ isEnabled() {
3486
+ return this.enabled;
3487
+ }
3488
+ /**
3489
+ * Cleanup resources
3490
+ */
3491
+ async cleanup() {
3492
+ }
3493
+ /**
3494
+ * Render widget output
3495
+ * @param context - Render context
3496
+ * @returns Rendered string, or null if no dev server detected
3497
+ */
3498
+ async render(_context) {
3499
+ if (!this.enabled || !this.cwd) {
3500
+ return null;
3501
+ }
3502
+ const server = await this.detectDevServer();
3503
+ if (!server) {
3504
+ return null;
3505
+ }
3506
+ const renderData = { server };
3507
+ return this.styleFn(renderData, this.colors.devServer);
3508
+ }
3509
+ /**
3510
+ * Detect running dev server using hybrid approach
3511
+ *
3512
+ * 1. Try port-based detection first (more reliable)
3513
+ * 2. Fall back to process-based detection
3514
+ *
3515
+ * @returns Detected server status or null
3516
+ */
3517
+ async detectDevServer() {
3518
+ const portResult = await this.portDetector.detect();
3519
+ if (portResult) {
3520
+ return {
3521
+ name: portResult.name,
3522
+ icon: portResult.icon,
3523
+ isRunning: portResult.isRunning,
3524
+ isBuilding: portResult.isBuilding
3525
+ };
3526
+ }
3527
+ return await this.processDetector.detect();
3528
+ }
3529
+ };
3530
+ }
3531
+ });
3532
+
3533
+ // src/widgets/docker/styles.ts
3534
+ var dockerStyles;
3535
+ var init_styles7 = __esm({
3536
+ "src/widgets/docker/styles.ts"() {
3537
+ "use strict";
3538
+ init_colors();
3539
+ dockerStyles = {
3540
+ balanced: (data, colors2) => {
3541
+ const { running, total } = data.status;
3542
+ if (running === 0) return "";
3543
+ const status = running > 0 ? "\u{1F7E2}" : "\u26AA";
3544
+ const count = total > running ? `${running}/${total}` : `${running}`;
3545
+ const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3546
+ const coloredCount = colors2 ? colorize(count, colors2.count) : count;
3547
+ return `${label} ${coloredCount} ${status}`;
3548
+ },
3549
+ compact: (data, colors2) => {
3550
+ const { running, total } = data.status;
3551
+ if (running === 0) return "";
3552
+ const count = total > running ? `${running}/${total}` : `${running}`;
3553
+ const coloredCount = colors2 ? colorize(count, colors2.count) : count;
3554
+ return `\u{1F433} ${coloredCount}`;
3555
+ },
3556
+ playful: (data, colors2) => {
3557
+ const { running, total } = data.status;
3558
+ if (running === 0) return "\u{1F433} Docker: \u{1F4A4}";
3559
+ const status = running > 0 ? "\u{1F7E2}" : "\u26AA";
3560
+ const count = total > running ? `${running}/${total}` : `${running}`;
3561
+ const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3562
+ const coloredCount = colors2 ? colorize(count, colors2.count) : count;
3563
+ return `\u{1F433} ${label} ${coloredCount} ${status}`;
3564
+ },
3565
+ verbose: (data, colors2) => {
3566
+ const { running, total } = data.status;
3567
+ if (running === 0) {
3568
+ const label2 = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3569
+ return `${label2} no containers running`;
3570
+ }
3571
+ const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3572
+ const coloredRunning = colors2 ? colorize(String(running), colors2.count) : String(running);
3573
+ return `${label} ${coloredRunning} running${total > running ? ` / ${total} total` : ""}`;
3574
+ },
3575
+ labeled: (data, colors2) => {
3576
+ const { running, total } = data.status;
3577
+ if (running === 0) {
3578
+ const label2 = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3579
+ return `${label2} --`;
3580
+ }
3581
+ const count = total > running ? `${running}/${total}` : `${running}`;
3582
+ const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3583
+ const coloredCount = colors2 ? colorize(count, colors2.count) : count;
3584
+ return `${label} ${coloredCount}`;
3585
+ },
3586
+ indicator: (data, colors2) => {
3587
+ const { running, total } = data.status;
3588
+ if (running === 0) {
3589
+ const label2 = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3590
+ return `\u25CF ${label2} --`;
3591
+ }
3592
+ const count = total > running ? `${running}/${total}` : `${running}`;
3593
+ const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3594
+ const coloredCount = colors2 ? colorize(count, colors2.count) : count;
3595
+ return `\u25CF ${label} ${coloredCount}`;
3596
+ }
3597
+ };
3598
+ }
3599
+ });
3600
+
3601
+ // src/widgets/docker/docker-widget.ts
3602
+ var import_node_child_process3, import_node_util3, execFileAsync3, DockerWidget;
3603
+ var init_docker_widget = __esm({
3604
+ "src/widgets/docker/docker-widget.ts"() {
3605
+ "use strict";
3606
+ import_node_child_process3 = require("node:child_process");
3607
+ import_node_util3 = require("node:util");
3608
+ init_widget_types();
3609
+ init_theme();
3610
+ init_styles7();
3611
+ execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
3612
+ DockerWidget = class {
3613
+ id = "docker";
3614
+ metadata = createWidgetMetadata(
3615
+ "Docker",
3616
+ "Shows Docker container count and status",
3617
+ "1.0.0",
3618
+ "claude-scope",
3619
+ 0
3620
+ );
3621
+ enabled = true;
3622
+ colors;
3623
+ _lineOverride;
3624
+ styleFn = dockerStyles.balanced;
3625
+ cachedStatus = null;
3626
+ lastCheck = 0;
3627
+ CACHE_TTL = 5e3;
3628
+ constructor(colors2) {
3629
+ this.colors = colors2 ?? DEFAULT_THEME;
3630
+ }
3631
+ setStyle(style = "balanced") {
3632
+ const fn = dockerStyles[style];
3633
+ if (fn) {
3634
+ this.styleFn = fn;
3635
+ }
3636
+ }
3637
+ setLine(line) {
3638
+ this._lineOverride = line;
3639
+ }
3640
+ getLine() {
3641
+ return this._lineOverride ?? this.metadata.line ?? 0;
3642
+ }
3643
+ async initialize(context) {
3644
+ this.enabled = context.config?.enabled !== false;
3645
+ }
3646
+ async update(_data) {
3647
+ }
3648
+ isEnabled() {
3649
+ return this.enabled;
3650
+ }
3651
+ async cleanup() {
3652
+ }
3653
+ async render(_context) {
3654
+ if (!this.enabled) {
3655
+ return null;
3656
+ }
3657
+ const now = Date.now();
3658
+ if (this.cachedStatus && now - this.lastCheck < this.CACHE_TTL) {
3659
+ return this.styleFn({ status: this.cachedStatus }, this.colors.docker);
3660
+ }
3661
+ const status = await this.getDockerStatus();
3662
+ this.cachedStatus = status;
3663
+ this.lastCheck = now;
3664
+ if (!status.isAvailable) {
3665
+ return null;
3666
+ }
3667
+ return this.styleFn({ status }, this.colors.docker);
3668
+ }
3669
+ async getDockerStatus() {
3670
+ try {
3671
+ await execFileAsync3("docker", ["info"], { timeout: 2e3 });
3672
+ const { stdout: runningOutput } = await execFileAsync3("docker", ["ps", "-q"], {
3673
+ timeout: 1e3
3674
+ });
3675
+ const running = runningOutput.trim().split("\n").filter((line) => line).length;
3676
+ const { stdout: allOutput } = await execFileAsync3("docker", ["ps", "-aq"], {
3677
+ timeout: 1e3
3678
+ });
3679
+ const total = allOutput.trim().split("\n").filter((line) => line).length;
3680
+ return { running, total, isAvailable: true };
3681
+ } catch {
3682
+ return { running: 0, total: 0, isAvailable: false };
3683
+ }
3684
+ }
3685
+ };
3686
+ }
3687
+ });
3688
+
3192
3689
  // src/widgets/duration/styles.ts
3193
3690
  function formatDurationWithColors(ms, colors2) {
3194
3691
  if (ms <= 0) return colorize("0s", colors2.value);
@@ -3214,7 +3711,7 @@ function formatDurationWithColors(ms, colors2) {
3214
3711
  return parts.join(" ");
3215
3712
  }
3216
3713
  var durationStyles;
3217
- var init_styles6 = __esm({
3714
+ var init_styles8 = __esm({
3218
3715
  "src/widgets/duration/styles.ts"() {
3219
3716
  "use strict";
3220
3717
  init_colors();
@@ -3294,7 +3791,7 @@ var init_duration_widget = __esm({
3294
3791
  init_widget_types();
3295
3792
  init_theme();
3296
3793
  init_stdin_data_widget();
3297
- init_styles6();
3794
+ init_styles8();
3298
3795
  DurationWidget = class extends StdinDataWidget {
3299
3796
  id = "duration";
3300
3797
  metadata = createWidgetMetadata(
@@ -3339,13 +3836,13 @@ var init_duration_widget = __esm({
3339
3836
  function createGit(cwd) {
3340
3837
  return new NativeGit(cwd);
3341
3838
  }
3342
- var import_node_child_process, import_node_util, execFileAsync, NativeGit;
3839
+ var import_node_child_process4, import_node_util4, execFileAsync4, NativeGit;
3343
3840
  var init_git_provider = __esm({
3344
3841
  "src/providers/git-provider.ts"() {
3345
3842
  "use strict";
3346
- import_node_child_process = require("node:child_process");
3347
- import_node_util = require("node:util");
3348
- execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
3843
+ import_node_child_process4 = require("node:child_process");
3844
+ import_node_util4 = require("node:util");
3845
+ execFileAsync4 = (0, import_node_util4.promisify)(import_node_child_process4.execFile);
3349
3846
  NativeGit = class {
3350
3847
  cwd;
3351
3848
  constructor(cwd) {
@@ -3353,7 +3850,7 @@ var init_git_provider = __esm({
3353
3850
  }
3354
3851
  async status() {
3355
3852
  try {
3356
- const { stdout } = await execFileAsync("git", ["status", "--branch", "--short"], {
3853
+ const { stdout } = await execFileAsync4("git", ["status", "--branch", "--short"], {
3357
3854
  cwd: this.cwd
3358
3855
  });
3359
3856
  const match = stdout.match(/^##\s+(\S+)/m);
@@ -3369,7 +3866,7 @@ var init_git_provider = __esm({
3369
3866
  args.push(...options);
3370
3867
  }
3371
3868
  try {
3372
- const { stdout } = await execFileAsync("git", args, {
3869
+ const { stdout } = await execFileAsync4("git", args, {
3373
3870
  cwd: this.cwd
3374
3871
  });
3375
3872
  const fileMatch = stdout.match(/(\d+)\s+file(s?)\s+changed/);
@@ -3386,7 +3883,7 @@ var init_git_provider = __esm({
3386
3883
  }
3387
3884
  async latestTag() {
3388
3885
  try {
3389
- const { stdout } = await execFileAsync("git", ["describe", "--tags", "--abbrev=0"], {
3886
+ const { stdout } = await execFileAsync4("git", ["describe", "--tags", "--abbrev=0"], {
3390
3887
  cwd: this.cwd
3391
3888
  });
3392
3889
  return stdout.trim();
@@ -3400,7 +3897,7 @@ var init_git_provider = __esm({
3400
3897
 
3401
3898
  // src/widgets/git-tag/styles.ts
3402
3899
  var gitTagStyles;
3403
- var init_styles7 = __esm({
3900
+ var init_styles9 = __esm({
3404
3901
  "src/widgets/git-tag/styles.ts"() {
3405
3902
  "use strict";
3406
3903
  init_colors();
@@ -3450,7 +3947,7 @@ var init_git_tag_widget = __esm({
3450
3947
  init_widget_types();
3451
3948
  init_git_provider();
3452
3949
  init_theme();
3453
- init_styles7();
3950
+ init_styles9();
3454
3951
  GitTagWidget = class {
3455
3952
  id = "git-tag";
3456
3953
  metadata = createWidgetMetadata(
@@ -3522,7 +4019,7 @@ var init_git_tag_widget = __esm({
3522
4019
 
3523
4020
  // src/widgets/git/styles.ts
3524
4021
  var gitStyles;
3525
- var init_styles8 = __esm({
4022
+ var init_styles10 = __esm({
3526
4023
  "src/widgets/git/styles.ts"() {
3527
4024
  "use strict";
3528
4025
  init_colors();
@@ -3624,7 +4121,7 @@ var init_git_widget = __esm({
3624
4121
  init_widget_types();
3625
4122
  init_git_provider();
3626
4123
  init_theme();
3627
- init_styles8();
4124
+ init_styles10();
3628
4125
  GitWidget = class {
3629
4126
  id = "git";
3630
4127
  metadata = createWidgetMetadata(
@@ -3716,7 +4213,7 @@ var init_git_widget = __esm({
3716
4213
 
3717
4214
  // src/widgets/lines/styles.ts
3718
4215
  var linesStyles;
3719
- var init_styles9 = __esm({
4216
+ var init_styles11 = __esm({
3720
4217
  "src/widgets/lines/styles.ts"() {
3721
4218
  "use strict";
3722
4219
  init_colors();
@@ -3776,7 +4273,7 @@ var init_lines_widget = __esm({
3776
4273
  init_widget_types();
3777
4274
  init_theme();
3778
4275
  init_stdin_data_widget();
3779
- init_styles9();
4276
+ init_styles11();
3780
4277
  LinesWidget = class extends StdinDataWidget {
3781
4278
  id = "lines";
3782
4279
  metadata = createWidgetMetadata(
@@ -3821,7 +4318,7 @@ function getShortName(displayName) {
3821
4318
  return displayName.replace(/^Claude\s+/, "");
3822
4319
  }
3823
4320
  var modelStyles;
3824
- var init_styles10 = __esm({
4321
+ var init_styles12 = __esm({
3825
4322
  "src/widgets/model/styles.ts"() {
3826
4323
  "use strict";
3827
4324
  init_colors();
@@ -3876,7 +4373,7 @@ var init_model_widget = __esm({
3876
4373
  init_widget_types();
3877
4374
  init_theme();
3878
4375
  init_stdin_data_widget();
3879
- init_styles10();
4376
+ init_styles12();
3880
4377
  ModelWidget = class extends StdinDataWidget {
3881
4378
  id = "model";
3882
4379
  metadata = createWidgetMetadata(
@@ -4018,6 +4515,16 @@ async function registerWidgetsFromConfig(registry, config, style, themeName) {
4018
4515
  const w = new CacheMetricsWidget(themeColors);
4019
4516
  w.setStyle(s);
4020
4517
  return w;
4518
+ },
4519
+ "dev-server": (s) => {
4520
+ const w = new DevServerWidget(themeColors);
4521
+ w.setStyle(s);
4522
+ return w;
4523
+ },
4524
+ docker: (s) => {
4525
+ const w = new DockerWidget(themeColors);
4526
+ w.setStyle(s);
4527
+ return w;
4021
4528
  }
4022
4529
  };
4023
4530
  for (const [lineNum, widgets] of Object.entries(config.lines)) {
@@ -4065,6 +4572,8 @@ var init_layout_preview = __esm({
4065
4572
  init_config_count_widget();
4066
4573
  init_context_widget();
4067
4574
  init_cost_widget();
4575
+ init_dev_server_widget();
4576
+ init_docker_widget();
4068
4577
  init_duration_widget();
4069
4578
  init_git_tag_widget();
4070
4579
  init_git_widget();
@@ -6639,10 +7148,10 @@ var init_esm2 = __esm({
6639
7148
  });
6640
7149
 
6641
7150
  // node_modules/@inquirer/core/dist/esm/lib/screen-manager.js
6642
- var import_node_util2, height, lastLine, ScreenManager;
7151
+ var import_node_util5, height, lastLine, ScreenManager;
6643
7152
  var init_screen_manager = __esm({
6644
7153
  "node_modules/@inquirer/core/dist/esm/lib/screen-manager.js"() {
6645
- import_node_util2 = require("node:util");
7154
+ import_node_util5 = require("node:util");
6646
7155
  init_utils();
6647
7156
  init_esm2();
6648
7157
  height = (content) => content.split("\n").length;
@@ -6664,7 +7173,7 @@ var init_screen_manager = __esm({
6664
7173
  }
6665
7174
  render(content, bottomContent = "") {
6666
7175
  const promptLine = lastLine(content);
6667
- const rawPromptLine = (0, import_node_util2.stripVTControlCharacters)(promptLine);
7176
+ const rawPromptLine = (0, import_node_util5.stripVTControlCharacters)(promptLine);
6668
7177
  let prompt = rawPromptLine;
6669
7178
  if (this.rl.line.length > 0) {
6670
7179
  prompt = prompt.slice(0, -this.rl.line.length);
@@ -6997,6 +7506,11 @@ function generateBalancedLayout(style, themeName) {
6997
7506
  bar: theme.context.bar
6998
7507
  }
6999
7508
  },
7509
+ {
7510
+ id: "lines",
7511
+ style,
7512
+ colors: { added: theme.lines.added, removed: theme.lines.removed }
7513
+ },
7000
7514
  {
7001
7515
  id: "cost",
7002
7516
  style,
@@ -7006,11 +7520,6 @@ function generateBalancedLayout(style, themeName) {
7006
7520
  id: "duration",
7007
7521
  style,
7008
7522
  colors: { value: theme.duration.value, unit: theme.duration.unit }
7009
- },
7010
- {
7011
- id: "lines",
7012
- style,
7013
- colors: { added: theme.lines.added, removed: theme.lines.removed }
7014
7523
  }
7015
7524
  ],
7016
7525
  "1": [
@@ -7019,6 +7528,11 @@ function generateBalancedLayout(style, themeName) {
7019
7528
  style,
7020
7529
  colors: { branch: theme.git.branch, changes: theme.git.changes }
7021
7530
  },
7531
+ {
7532
+ id: "git-tag",
7533
+ style,
7534
+ colors: { base: theme.base.text }
7535
+ },
7022
7536
  {
7023
7537
  id: "cache-metrics",
7024
7538
  style,
@@ -7033,21 +7547,7 @@ function generateBalancedLayout(style, themeName) {
7033
7547
  {
7034
7548
  id: "config-count",
7035
7549
  style,
7036
- colors: {
7037
- base: theme.base.muted
7038
- }
7039
- },
7040
- {
7041
- id: "active-tools",
7042
- style,
7043
- colors: {
7044
- running: theme.tools.running,
7045
- completed: theme.tools.completed,
7046
- error: theme.tools.error,
7047
- name: theme.tools.name,
7048
- target: theme.tools.target,
7049
- count: theme.tools.count
7050
- }
7550
+ colors: { base: theme.base.muted }
7051
7551
  }
7052
7552
  ]
7053
7553
  }
@@ -7115,14 +7615,14 @@ function generateRichLayout(style, themeName) {
7115
7615
  }
7116
7616
  },
7117
7617
  {
7118
- id: "cost",
7618
+ id: "lines",
7119
7619
  style,
7120
- colors: { amount: theme.cost.amount, currency: theme.cost.currency }
7620
+ colors: { added: theme.lines.added, removed: theme.lines.removed }
7121
7621
  },
7122
7622
  {
7123
- id: "lines",
7623
+ id: "cost",
7124
7624
  style,
7125
- colors: { added: theme.lines.added, removed: theme.lines.removed }
7625
+ colors: { amount: theme.cost.amount, currency: theme.cost.currency }
7126
7626
  },
7127
7627
  {
7128
7628
  id: "duration",
@@ -7141,20 +7641,6 @@ function generateRichLayout(style, themeName) {
7141
7641
  style,
7142
7642
  colors: { base: theme.base.text }
7143
7643
  },
7144
- {
7145
- id: "active-tools",
7146
- style,
7147
- colors: {
7148
- running: theme.tools.running,
7149
- completed: theme.tools.completed,
7150
- error: theme.tools.error,
7151
- name: theme.tools.name,
7152
- target: theme.tools.target,
7153
- count: theme.tools.count
7154
- }
7155
- }
7156
- ],
7157
- "2": [
7158
7644
  {
7159
7645
  id: "cache-metrics",
7160
7646
  style,
@@ -7171,6 +7657,39 @@ function generateRichLayout(style, themeName) {
7171
7657
  style,
7172
7658
  colors: { base: theme.base.muted }
7173
7659
  }
7660
+ ],
7661
+ "2": [
7662
+ {
7663
+ id: "dev-server",
7664
+ style,
7665
+ colors: {
7666
+ name: theme.devServer.name,
7667
+ status: theme.devServer.status,
7668
+ label: theme.devServer.label
7669
+ }
7670
+ },
7671
+ {
7672
+ id: "docker",
7673
+ style,
7674
+ colors: {
7675
+ label: theme.docker.label,
7676
+ count: theme.docker.count,
7677
+ running: theme.docker.running,
7678
+ stopped: theme.docker.stopped
7679
+ }
7680
+ },
7681
+ {
7682
+ id: "active-tools",
7683
+ style,
7684
+ colors: {
7685
+ running: theme.tools.running,
7686
+ completed: theme.tools.completed,
7687
+ error: theme.tools.error,
7688
+ name: theme.tools.name,
7689
+ target: theme.tools.target,
7690
+ count: theme.tools.count
7691
+ }
7692
+ }
7174
7693
  ]
7175
7694
  }
7176
7695
  };
@@ -7222,19 +7741,19 @@ async function selectLayout() {
7222
7741
  const layoutChoices = [
7223
7742
  {
7224
7743
  name: "Balanced",
7225
- description: "2 lines: AI metrics + Git, Cache, Tools, MCP, Hooks",
7744
+ description: "2 lines: Model|Context|Lines|Cost|Duration + Git|GitTag|Cache|Config",
7226
7745
  value: "balanced",
7227
7746
  getConfig: (s, t) => generateBalancedLayout(s, t)
7228
7747
  },
7229
7748
  {
7230
7749
  name: "Compact",
7231
- description: "1 line: Model, Context, Cost, Git, Duration",
7750
+ description: "1 line: Model|Context|Cost|Git|Duration",
7232
7751
  value: "compact",
7233
7752
  getConfig: (s, t) => generateCompactLayout(s, t)
7234
7753
  },
7235
7754
  {
7236
7755
  name: "Rich",
7237
- description: "3 lines: Full details with Git Tag, Config Count",
7756
+ description: "3 lines: + Dev Server|Docker|Active Tools on line 3",
7238
7757
  value: "rich",
7239
7758
  getConfig: (s, t) => generateRichLayout(s, t)
7240
7759
  }
@@ -7538,7 +8057,7 @@ async function ensureDefaultConfig() {
7538
8057
  if (!(0, import_node_fs3.existsSync)(configDir)) {
7539
8058
  (0, import_node_fs3.mkdirSync)(configDir, { recursive: true });
7540
8059
  }
7541
- const defaultConfig = generateBalancedLayout("balanced", "cyberpunk-neon");
8060
+ const defaultConfig = generateRichLayout("balanced", "dracula");
7542
8061
  (0, import_node_fs3.writeFileSync)(configPath, JSON.stringify(defaultConfig, null, 2), "utf-8");
7543
8062
  }
7544
8063
 
@@ -7580,435 +8099,13 @@ init_config_count_widget();
7580
8099
  init_context_widget();
7581
8100
  init_cost_widget();
7582
8101
 
7583
- // src/widgets/dev-server/dev-server-widget.ts
7584
- init_widget_types();
7585
- init_theme();
7586
-
7587
- // src/widgets/dev-server/port-detector.ts
7588
- var import_node_child_process2 = require("node:child_process");
7589
- var import_node_util3 = require("node:util");
7590
- var execFileAsync2 = (0, import_node_util3.promisify)(import_node_child_process2.execFile);
7591
- var PORT_MAPPING = {
7592
- 5173: { name: "Vite", icon: "\u26A1" },
7593
- 4200: { name: "Angular", icon: "\u25B2" },
7594
- 3e3: { name: "Dev", icon: "\u{1F680}" },
7595
- 8080: { name: "Webpack", icon: "\u{1F4E6}" },
7596
- 8e3: { name: "Dev", icon: "\u{1F680}" },
7597
- 8888: { name: "Dev", icon: "\u{1F680}" }
7598
- };
7599
- var DEV_PORTS = [5173, 4200, 3e3, 8080, 8e3, 8888];
7600
- var PortDetector = class {
7601
- execFn;
7602
- /**
7603
- * Create a new PortDetector
7604
- * @param execFn - Optional execFile function for testing (dependency injection)
7605
- */
7606
- constructor(execFn) {
7607
- this.execFn = execFn ?? execFileAsync2;
7608
- }
7609
- /**
7610
- * Detect running dev servers by checking listening ports
7611
- * @returns Detected server info or null
7612
- */
7613
- async detect() {
7614
- try {
7615
- const args = [
7616
- "-nP",
7617
- // No host names, no port names
7618
- "-iTCP",
7619
- // Internet TCP
7620
- "-sTCP:LISTEN"
7621
- // TCP LISTEN state only
7622
- ];
7623
- for (const port of DEV_PORTS) {
7624
- args.push("-i", `:${port}`);
7625
- }
7626
- const { stdout } = await this.execFn("lsof", args, {
7627
- timeout: 2e3
7628
- });
7629
- const lines = stdout.trim().split("\n");
7630
- for (const line of lines) {
7631
- if (!line || line.startsWith("COMMAND")) continue;
7632
- const match = line.match(/:(\d+)\s*\(LISTEN\)/);
7633
- if (match) {
7634
- const port = parseInt(match[1], 10);
7635
- const mapping = PORT_MAPPING[port];
7636
- if (mapping) {
7637
- return {
7638
- name: mapping.name,
7639
- icon: mapping.icon,
7640
- port,
7641
- isRunning: true,
7642
- isBuilding: false
7643
- };
7644
- }
7645
- }
7646
- }
7647
- return null;
7648
- } catch {
7649
- return null;
7650
- }
7651
- }
7652
- };
7653
-
7654
- // src/widgets/dev-server/process-detector.ts
7655
- var import_node_child_process3 = require("node:child_process");
7656
- var import_node_util4 = require("node:util");
7657
- var execFileAsync3 = (0, import_node_util4.promisify)(import_node_child_process3.execFile);
7658
- var ProcessDetector = class {
7659
- execFn;
7660
- processPatterns = [
7661
- // Generic server patterns - more specific to avoid shell history false positives
7662
- { regex: /^[\w\s]+\/npm\s+(exec|run)\s+serve/i, name: "Server", icon: "\u{1F310}" },
7663
- { regex: /^[\w\s]+\/npx\s+serve\s+-/i, name: "Server", icon: "\u{1F310}" },
7664
- { regex: /^[\w\s]+\/(python|python3)\s+-m\s+http\.server/i, name: "HTTP", icon: "\u{1F310}" },
7665
- // Generic dev/build patterns - require full command path
7666
- { regex: /^[\w\s]+\/(npm|yarn|pnpm|bun)\s+run\s+dev\s*$/i, name: "Dev", icon: "\u{1F680}" },
7667
- { regex: /^[\w\s]+\/(npm|yarn|pnpm|bun)\s+run\s+build\s*$/i, name: "Build", icon: "\u{1F528}" },
7668
- // Framework-specific patterns - require executable path
7669
- { regex: /\/(nuxt|next|astro|remix|svelte)\s+dev/i, name: "Framework", icon: "\u26A1" },
7670
- { regex: /\/node.*\/vite\s*$/i, name: "Vite", icon: "\u26A1" },
7671
- // Fallback: simpler patterns but checked last
7672
- { regex: /\s(nuxt|next|vite)\s+dev\s/i, name: "DevServer", icon: "\u26A1" }
7673
- ];
7674
- /**
7675
- * Create a new ProcessDetector
7676
- * @param execFn - Optional execFile function for testing (dependency injection)
7677
- */
7678
- constructor(execFn) {
7679
- this.execFn = execFn ?? execFileAsync3;
7680
- }
7681
- /**
7682
- * Detect running dev server by parsing system process list
7683
- * @returns Detected server status or null
7684
- */
7685
- async detect() {
7686
- try {
7687
- const { stdout } = await this.execFn("ps", ["aux"], {
7688
- timeout: 1e3
7689
- });
7690
- for (const pattern of this.processPatterns) {
7691
- if (pattern.regex.test(stdout)) {
7692
- const isBuilding = pattern.name.toLowerCase().includes("build");
7693
- const isRunning = !isBuilding;
7694
- return {
7695
- name: pattern.name,
7696
- icon: pattern.icon,
7697
- isRunning,
7698
- isBuilding
7699
- };
7700
- }
7701
- }
7702
- } catch {
7703
- }
7704
- return null;
7705
- }
7706
- };
7707
-
7708
- // src/widgets/dev-server/styles.ts
7709
- init_colors();
7710
- var devServerStyles = {
7711
- balanced: (data, colors2) => {
7712
- if (!data.server) return "";
7713
- const { name, icon, isRunning, isBuilding } = data.server;
7714
- const status = isRunning ? "running" : isBuilding ? "building" : "stopped";
7715
- const coloredName = colors2 ? colorize(name, colors2.name) : name;
7716
- const coloredStatus = colors2 ? colorize(`(${status})`, colors2.status) : `(${status})`;
7717
- return `${icon} ${coloredName} ${coloredStatus}`;
7718
- },
7719
- compact: (data, colors2) => {
7720
- if (!data.server) return "";
7721
- const { name, icon, isRunning, isBuilding } = data.server;
7722
- const statusIcon = isRunning ? "\u{1F680}" : isBuilding ? "\u{1F528}" : "\u{1F4A4}";
7723
- const coloredName = colors2 ? colorize(name, colors2.name) : name;
7724
- return `${icon} ${coloredName} ${statusIcon}`;
7725
- },
7726
- playful: (data, colors2) => {
7727
- if (!data.server) return "";
7728
- const { name, isRunning, isBuilding } = data.server;
7729
- const emoji = isRunning ? "\u{1F3C3}" : isBuilding ? "\u{1F528}" : "\u{1F4A4}";
7730
- const coloredName = colors2 ? colorize(name, colors2.name) : name;
7731
- return `${emoji} ${coloredName}`;
7732
- },
7733
- verbose: (data, colors2) => {
7734
- if (!data.server) return "";
7735
- const { name, isRunning, isBuilding } = data.server;
7736
- const status = isRunning ? "running" : isBuilding ? "building" : "stopped";
7737
- const label = colors2 ? colorize("Dev Server:", colors2.label) : "Dev Server:";
7738
- const coloredName = colors2 ? colorize(name, colors2.name) : name;
7739
- const coloredStatus = colors2 ? colorize(`(${status})`, colors2.status) : `(${status})`;
7740
- return `${label} ${coloredName} ${coloredStatus}`;
7741
- },
7742
- labeled: (data, colors2) => {
7743
- if (!data.server) return "";
7744
- const { name, icon, isRunning } = data.server;
7745
- const status = isRunning ? "\u{1F7E2}" : "\u{1F534}";
7746
- const label = colors2 ? colorize("Server:", colors2.label) : "Server:";
7747
- const coloredName = colors2 ? colorize(name, colors2.name) : name;
7748
- return `${label} ${icon} ${coloredName} ${status}`;
7749
- },
7750
- indicator: (data, colors2) => {
7751
- if (!data.server) return "";
7752
- const { name, icon } = data.server;
7753
- const coloredName = colors2 ? colorize(name, colors2.name) : name;
7754
- return `\u25CF ${icon} ${coloredName}`;
7755
- }
7756
- };
7757
-
7758
- // src/widgets/dev-server/dev-server-widget.ts
7759
- var DevServerWidget = class {
7760
- id = "dev-server";
7761
- metadata = createWidgetMetadata(
7762
- "Dev Server",
7763
- "Detects running dev server processes using hybrid port+process detection",
7764
- "1.1.0",
7765
- "claude-scope",
7766
- 0
7767
- );
7768
- enabled = true;
7769
- colors;
7770
- _lineOverride;
7771
- styleFn = devServerStyles.balanced;
7772
- cwd = null;
7773
- portDetector;
7774
- processDetector;
7775
- constructor(colors2) {
7776
- this.colors = colors2 ?? DEFAULT_THEME;
7777
- this.portDetector = new PortDetector();
7778
- this.processDetector = new ProcessDetector();
7779
- }
7780
- /**
7781
- * Set display style
7782
- * @param style - Style to use for rendering
7783
- */
7784
- setStyle(style = "balanced") {
7785
- const fn = devServerStyles[style];
7786
- if (fn) {
7787
- this.styleFn = fn;
7788
- }
7789
- }
7790
- /**
7791
- * Set display line override
7792
- * @param line - Line number (0-indexed)
7793
- */
7794
- setLine(line) {
7795
- this._lineOverride = line;
7796
- }
7797
- /**
7798
- * Get display line
7799
- * @returns Line number (0-indexed)
7800
- */
7801
- getLine() {
7802
- return this._lineOverride ?? this.metadata.line ?? 0;
7803
- }
7804
- /**
7805
- * Initialize widget with context
7806
- * @param context - Widget initialization context
7807
- */
7808
- async initialize(context) {
7809
- this.enabled = context.config?.enabled !== false;
7810
- }
7811
- /**
7812
- * Update widget with new stdin data
7813
- * @param data - Stdin data from Claude Code
7814
- */
7815
- async update(data) {
7816
- this.cwd = data.cwd;
7817
- }
7818
- /**
7819
- * Check if widget is enabled
7820
- * @returns true if widget should render
7821
- */
7822
- isEnabled() {
7823
- return this.enabled;
7824
- }
7825
- /**
7826
- * Cleanup resources
7827
- */
7828
- async cleanup() {
7829
- }
7830
- /**
7831
- * Render widget output
7832
- * @param context - Render context
7833
- * @returns Rendered string, or null if no dev server detected
7834
- */
7835
- async render(_context) {
7836
- if (!this.enabled || !this.cwd) {
7837
- return null;
7838
- }
7839
- const server = await this.detectDevServer();
7840
- if (!server) {
7841
- return null;
7842
- }
7843
- const renderData = { server };
7844
- return this.styleFn(renderData, this.colors.devServer);
7845
- }
7846
- /**
7847
- * Detect running dev server using hybrid approach
7848
- *
7849
- * 1. Try port-based detection first (more reliable)
7850
- * 2. Fall back to process-based detection
7851
- *
7852
- * @returns Detected server status or null
7853
- */
7854
- async detectDevServer() {
7855
- const portResult = await this.portDetector.detect();
7856
- if (portResult) {
7857
- return {
7858
- name: portResult.name,
7859
- icon: portResult.icon,
7860
- isRunning: portResult.isRunning,
7861
- isBuilding: portResult.isBuilding
7862
- };
7863
- }
7864
- return await this.processDetector.detect();
7865
- }
7866
- };
7867
-
7868
- // src/widgets/docker/docker-widget.ts
7869
- var import_node_child_process4 = require("node:child_process");
7870
- var import_node_util5 = require("node:util");
7871
- init_widget_types();
7872
- init_theme();
8102
+ // src/widgets/dev-server/index.ts
8103
+ init_dev_server_widget();
8104
+ init_port_detector();
8105
+ init_process_detector();
7873
8106
 
7874
- // src/widgets/docker/styles.ts
7875
- init_colors();
7876
- var dockerStyles = {
7877
- balanced: (data, colors2) => {
7878
- const { running, total } = data.status;
7879
- if (running === 0) return "";
7880
- const status = running > 0 ? "\u{1F7E2}" : "\u26AA";
7881
- const count = total > running ? `${running}/${total}` : `${running}`;
7882
- const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7883
- const coloredCount = colors2 ? colorize(count, colors2.count) : count;
7884
- return `${label} ${coloredCount} ${status}`;
7885
- },
7886
- compact: (data, colors2) => {
7887
- const { running, total } = data.status;
7888
- if (running === 0) return "";
7889
- const count = total > running ? `${running}/${total}` : `${running}`;
7890
- const coloredCount = colors2 ? colorize(count, colors2.count) : count;
7891
- return `\u{1F433} ${coloredCount}`;
7892
- },
7893
- playful: (data, colors2) => {
7894
- const { running, total } = data.status;
7895
- if (running === 0) return "\u{1F433} Docker: \u{1F4A4}";
7896
- const status = running > 0 ? "\u{1F7E2}" : "\u26AA";
7897
- const count = total > running ? `${running}/${total}` : `${running}`;
7898
- const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7899
- const coloredCount = colors2 ? colorize(count, colors2.count) : count;
7900
- return `\u{1F433} ${label} ${coloredCount} ${status}`;
7901
- },
7902
- verbose: (data, colors2) => {
7903
- const { running, total } = data.status;
7904
- if (running === 0) {
7905
- const label2 = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7906
- return `${label2} no containers running`;
7907
- }
7908
- const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7909
- const coloredRunning = colors2 ? colorize(String(running), colors2.count) : String(running);
7910
- return `${label} ${coloredRunning} running${total > running ? ` / ${total} total` : ""}`;
7911
- },
7912
- labeled: (data, colors2) => {
7913
- const { running, total } = data.status;
7914
- if (running === 0) {
7915
- const label2 = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7916
- return `${label2} --`;
7917
- }
7918
- const count = total > running ? `${running}/${total}` : `${running}`;
7919
- const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7920
- const coloredCount = colors2 ? colorize(count, colors2.count) : count;
7921
- return `${label} ${coloredCount}`;
7922
- },
7923
- indicator: (data, colors2) => {
7924
- const { running, total } = data.status;
7925
- if (running === 0) {
7926
- const label2 = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7927
- return `\u25CF ${label2} --`;
7928
- }
7929
- const count = total > running ? `${running}/${total}` : `${running}`;
7930
- const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7931
- const coloredCount = colors2 ? colorize(count, colors2.count) : count;
7932
- return `\u25CF ${label} ${coloredCount}`;
7933
- }
7934
- };
7935
-
7936
- // src/widgets/docker/docker-widget.ts
7937
- var execFileAsync4 = (0, import_node_util5.promisify)(import_node_child_process4.execFile);
7938
- var DockerWidget = class {
7939
- id = "docker";
7940
- metadata = createWidgetMetadata(
7941
- "Docker",
7942
- "Shows Docker container count and status",
7943
- "1.0.0",
7944
- "claude-scope",
7945
- 0
7946
- );
7947
- enabled = true;
7948
- colors;
7949
- _lineOverride;
7950
- styleFn = dockerStyles.balanced;
7951
- cachedStatus = null;
7952
- lastCheck = 0;
7953
- CACHE_TTL = 5e3;
7954
- constructor(colors2) {
7955
- this.colors = colors2 ?? DEFAULT_THEME;
7956
- }
7957
- setStyle(style = "balanced") {
7958
- const fn = dockerStyles[style];
7959
- if (fn) {
7960
- this.styleFn = fn;
7961
- }
7962
- }
7963
- setLine(line) {
7964
- this._lineOverride = line;
7965
- }
7966
- getLine() {
7967
- return this._lineOverride ?? this.metadata.line ?? 0;
7968
- }
7969
- async initialize(context) {
7970
- this.enabled = context.config?.enabled !== false;
7971
- }
7972
- async update(_data) {
7973
- }
7974
- isEnabled() {
7975
- return this.enabled;
7976
- }
7977
- async cleanup() {
7978
- }
7979
- async render(_context) {
7980
- if (!this.enabled) {
7981
- return null;
7982
- }
7983
- const now = Date.now();
7984
- if (this.cachedStatus && now - this.lastCheck < this.CACHE_TTL) {
7985
- return this.styleFn({ status: this.cachedStatus }, this.colors.docker);
7986
- }
7987
- const status = await this.getDockerStatus();
7988
- this.cachedStatus = status;
7989
- this.lastCheck = now;
7990
- if (!status.isAvailable) {
7991
- return null;
7992
- }
7993
- return this.styleFn({ status }, this.colors.docker);
7994
- }
7995
- async getDockerStatus() {
7996
- try {
7997
- await execFileAsync4("docker", ["info"], { timeout: 2e3 });
7998
- const { stdout: runningOutput } = await execFileAsync4("docker", ["ps", "-q"], {
7999
- timeout: 1e3
8000
- });
8001
- const running = runningOutput.trim().split("\n").filter((line) => line).length;
8002
- const { stdout: allOutput } = await execFileAsync4("docker", ["ps", "-aq"], {
8003
- timeout: 1e3
8004
- });
8005
- const total = allOutput.trim().split("\n").filter((line) => line).length;
8006
- return { running, total, isAvailable: true };
8007
- } catch {
8008
- return { running: 0, total: 0, isAvailable: false };
8009
- }
8010
- }
8011
- };
8107
+ // src/widgets/docker/index.ts
8108
+ init_docker_widget();
8012
8109
 
8013
8110
  // src/core/widget-factory.ts
8014
8111
  init_duration_widget();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-scope",
3
- "version": "0.8.14",
3
+ "version": "0.8.16",
4
4
  "description": "Claude Code plugin for session status and analytics",
5
5
  "license": "MIT",
6
6
  "type": "module",