claude-scope 0.8.15 → 0.8.18

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 (3) hide show
  1. package/README.md +223 -82
  2. package/dist/claude-scope.cjs +1207 -401
  3. package/package.json +1 -1
@@ -38,12 +38,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
38
38
  function colorize(text, color) {
39
39
  return `${color}${text}${reset}`;
40
40
  }
41
- var reset, gray;
41
+ var reset, red, gray, lightGray, bold;
42
42
  var init_colors = __esm({
43
43
  "src/ui/utils/colors.ts"() {
44
44
  "use strict";
45
45
  reset = "\x1B[0m";
46
+ red = "\x1B[31m";
46
47
  gray = "\x1B[90m";
48
+ lightGray = "\x1B[37m";
49
+ bold = "\x1B[1m";
47
50
  }
48
51
  });
49
52
 
@@ -1378,7 +1381,7 @@ var init_theme = __esm({
1378
1381
  });
1379
1382
 
1380
1383
  // src/constants.ts
1381
- var TIME, DEFAULTS, DEMO_DATA, DEFAULT_PROGRESS_BAR_WIDTH;
1384
+ var TIME, DEFAULTS, ANSI_COLORS, DEMO_DATA, DEFAULT_PROGRESS_BAR_WIDTH;
1382
1385
  var init_constants = __esm({
1383
1386
  "src/constants.ts"() {
1384
1387
  "use strict";
@@ -1396,6 +1399,16 @@ var init_constants = __esm({
1396
1399
  /** Default width for progress bars in characters */
1397
1400
  PROGRESS_BAR_WIDTH: 20
1398
1401
  };
1402
+ ANSI_COLORS = {
1403
+ /** Green color */
1404
+ GREEN: "\x1B[32m",
1405
+ /** Yellow color */
1406
+ YELLOW: "\x1B[33m",
1407
+ /** Red color */
1408
+ RED: "\x1B[31m",
1409
+ /** Reset color */
1410
+ RESET: "\x1B[0m"
1411
+ };
1399
1412
  DEMO_DATA = {
1400
1413
  /** Demo session cost in USD */
1401
1414
  COST_USD: 0.42,
@@ -1471,7 +1484,7 @@ var init_renderer = __esm({
1471
1484
  for (const widget of widgetsForLine) {
1472
1485
  try {
1473
1486
  const output = await widget.render(context);
1474
- if (output !== null) {
1487
+ if (output && output.trim().length > 0) {
1475
1488
  outputs.push(output);
1476
1489
  }
1477
1490
  } catch (error) {
@@ -1952,27 +1965,58 @@ var init_styles = __esm({
1952
1965
  return compactStyle(data, colors2);
1953
1966
  },
1954
1967
  /**
1955
- * playful: Emojis (📖✏️✨🔄🔍📁) with tool names
1968
+ * playful: Emojis (📖✏️✨🔄🔍📁) with tool counts (like balanced but more fun)
1969
+ * - Shows running + completed counts like balanced style
1970
+ * - Uses emojis instead of text labels for a more playful look
1956
1971
  */
1957
1972
  playful: (data, colors2) => {
1958
1973
  const parts = [];
1974
+ const c = colors2 ?? getDefaultColors();
1959
1975
  const emojis = {
1960
1976
  Read: "\u{1F4D6}",
1961
1977
  Write: "\u270F\uFE0F",
1962
1978
  Edit: "\u2728",
1963
1979
  Bash: "\u{1F504}",
1964
1980
  Grep: "\u{1F50D}",
1965
- Glob: "\u{1F4C1}"
1981
+ Glob: "\u{1F4C1}",
1982
+ Task: "\u{1F4CB}"
1966
1983
  };
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}`);
1984
+ const allToolNames = /* @__PURE__ */ new Set();
1985
+ for (const tool of data.running) {
1986
+ allToolNames.add(tool.name);
1987
+ }
1988
+ for (const [name] of data.completed.slice(0, 3)) {
1989
+ allToolNames.add(name);
1990
+ }
1991
+ const completedMap = new Map(data.completed);
1992
+ const runningCounts = /* @__PURE__ */ new Map();
1993
+ for (const tool of data.running) {
1994
+ runningCounts.set(tool.name, (runningCounts.get(tool.name) ?? 0) + 1);
1995
+ }
1996
+ for (const name of allToolNames) {
1997
+ const runningCount = runningCounts.get(name) ?? 0;
1998
+ const completedCount = completedMap.get(name) ?? 0;
1999
+ const emoji = emojis[name] ?? "\u{1F527}";
2000
+ if (runningCount > 0 && completedCount > 0) {
2001
+ const nameStr = colorize(name, c.tools.name);
2002
+ const runningStr = colorize(`\u25B6${runningCount}`, c.tools.running);
2003
+ const doneStr = colorize(`\u2713${completedCount}`, c.tools.completed);
2004
+ parts.push(`${emoji} ${nameStr} (${runningStr}, ${doneStr})`);
2005
+ } else if (completedCount > 0) {
2006
+ const pluralName = pluralizeTool(name);
2007
+ const nameStr = colorize(pluralName, c.tools.name);
2008
+ const countStr = colorize(`${completedCount}`, c.tools.count);
2009
+ parts.push(`${emoji} ${nameStr}: ${countStr}`);
2010
+ } else if (runningCount > 0) {
2011
+ const nameStr = colorize(name, c.tools.name);
2012
+ const runningStr = colorize(`\u25B6${runningCount}`, c.tools.running);
2013
+ parts.push(`${emoji} ${nameStr} (${runningStr})`);
2014
+ }
1971
2015
  }
1972
2016
  if (parts.length === 0) {
1973
2017
  return "";
1974
2018
  }
1975
- return parts.join(", ");
2019
+ return parts.join(" | ");
1976
2020
  },
1977
2021
  /**
1978
2022
  * verbose: Full text labels "Running:" and "Completed:"
@@ -2318,6 +2362,9 @@ function formatDuration(ms) {
2318
2362
  function formatCostUSD(usd) {
2319
2363
  return `$${usd.toFixed(2)}`;
2320
2364
  }
2365
+ function colorize2(text, color) {
2366
+ return `${color}${text}${ANSI_COLORS.RESET}`;
2367
+ }
2321
2368
  function formatK(n) {
2322
2369
  const absN = Math.abs(n);
2323
2370
  if (absN < 1e3) {
@@ -3189,6 +3236,472 @@ var init_cost_widget = __esm({
3189
3236
  }
3190
3237
  });
3191
3238
 
3239
+ // src/widgets/dev-server/port-detector.ts
3240
+ var import_node_child_process, import_node_util, execFileAsync, PORT_MAPPING, DEV_PORTS, PortDetector;
3241
+ var init_port_detector = __esm({
3242
+ "src/widgets/dev-server/port-detector.ts"() {
3243
+ "use strict";
3244
+ import_node_child_process = require("node:child_process");
3245
+ import_node_util = require("node:util");
3246
+ execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
3247
+ PORT_MAPPING = {
3248
+ 5173: { name: "Vite", icon: "\u26A1" },
3249
+ 4200: { name: "Angular", icon: "\u25B2" },
3250
+ 3e3: { name: "Dev", icon: "\u{1F680}" },
3251
+ 8080: { name: "Webpack", icon: "\u{1F4E6}" },
3252
+ 8e3: { name: "Dev", icon: "\u{1F680}" },
3253
+ 8888: { name: "Dev", icon: "\u{1F680}" }
3254
+ };
3255
+ DEV_PORTS = [5173, 4200, 3e3, 8080, 8e3, 8888];
3256
+ PortDetector = class {
3257
+ execFn;
3258
+ /**
3259
+ * Create a new PortDetector
3260
+ * @param execFn - Optional execFile function for testing (dependency injection)
3261
+ */
3262
+ constructor(execFn) {
3263
+ this.execFn = execFn ?? execFileAsync;
3264
+ }
3265
+ /**
3266
+ * Detect running dev servers by checking listening ports
3267
+ * @returns Detected server info or null
3268
+ */
3269
+ async detect() {
3270
+ try {
3271
+ const args = [
3272
+ "-nP",
3273
+ // No host names, no port names
3274
+ "-iTCP",
3275
+ // Internet TCP
3276
+ "-sTCP:LISTEN"
3277
+ // TCP LISTEN state only
3278
+ ];
3279
+ for (const port of DEV_PORTS) {
3280
+ args.push("-i", `:${port}`);
3281
+ }
3282
+ const { stdout } = await this.execFn("lsof", args, {
3283
+ timeout: 2e3
3284
+ });
3285
+ const lines = stdout.trim().split("\n");
3286
+ for (const line of lines) {
3287
+ if (!line || line.startsWith("COMMAND")) continue;
3288
+ const match = line.match(/:(\d+)\s*\(LISTEN\)/);
3289
+ if (match) {
3290
+ const port = parseInt(match[1], 10);
3291
+ const mapping = PORT_MAPPING[port];
3292
+ if (mapping) {
3293
+ return {
3294
+ name: mapping.name,
3295
+ icon: mapping.icon,
3296
+ port,
3297
+ isRunning: true,
3298
+ isBuilding: false
3299
+ };
3300
+ }
3301
+ }
3302
+ }
3303
+ return null;
3304
+ } catch {
3305
+ return null;
3306
+ }
3307
+ }
3308
+ };
3309
+ }
3310
+ });
3311
+
3312
+ // src/widgets/dev-server/process-detector.ts
3313
+ var import_node_child_process2, import_node_util2, execFileAsync2, ProcessDetector;
3314
+ var init_process_detector = __esm({
3315
+ "src/widgets/dev-server/process-detector.ts"() {
3316
+ "use strict";
3317
+ import_node_child_process2 = require("node:child_process");
3318
+ import_node_util2 = require("node:util");
3319
+ execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
3320
+ ProcessDetector = class {
3321
+ execFn;
3322
+ processPatterns = [
3323
+ // Generic server patterns - more specific to avoid shell history false positives
3324
+ { regex: /^[\w\s]+\/npm\s+(exec|run)\s+serve/i, name: "Server", icon: "\u{1F310}" },
3325
+ { regex: /^[\w\s]+\/npx\s+serve\s+-/i, name: "Server", icon: "\u{1F310}" },
3326
+ { regex: /^[\w\s]+\/(python|python3)\s+-m\s+http\.server/i, name: "HTTP", icon: "\u{1F310}" },
3327
+ // Generic dev/build patterns - require full command path
3328
+ { regex: /^[\w\s]+\/(npm|yarn|pnpm|bun)\s+run\s+dev\s*$/i, name: "Dev", icon: "\u{1F680}" },
3329
+ { regex: /^[\w\s]+\/(npm|yarn|pnpm|bun)\s+run\s+build\s*$/i, name: "Build", icon: "\u{1F528}" },
3330
+ // Framework-specific patterns - require executable path
3331
+ { regex: /\/(nuxt|next|astro|remix|svelte)\s+dev/i, name: "Framework", icon: "\u26A1" },
3332
+ { regex: /\/node.*\/vite\s*$/i, name: "Vite", icon: "\u26A1" },
3333
+ // Fallback: simpler patterns but checked last
3334
+ { regex: /\s(nuxt|next|vite)\s+dev\s/i, name: "DevServer", icon: "\u26A1" }
3335
+ ];
3336
+ /**
3337
+ * Create a new ProcessDetector
3338
+ * @param execFn - Optional execFile function for testing (dependency injection)
3339
+ */
3340
+ constructor(execFn) {
3341
+ this.execFn = execFn ?? execFileAsync2;
3342
+ }
3343
+ /**
3344
+ * Detect running dev server by parsing system process list
3345
+ * @returns Detected server status or null
3346
+ */
3347
+ async detect() {
3348
+ try {
3349
+ const { stdout } = await this.execFn("ps", ["aux"], {
3350
+ timeout: 1e3
3351
+ });
3352
+ for (const pattern of this.processPatterns) {
3353
+ if (pattern.regex.test(stdout)) {
3354
+ const isBuilding = pattern.name.toLowerCase().includes("build");
3355
+ const isRunning = !isBuilding;
3356
+ return {
3357
+ name: pattern.name,
3358
+ icon: pattern.icon,
3359
+ isRunning,
3360
+ isBuilding
3361
+ };
3362
+ }
3363
+ }
3364
+ } catch {
3365
+ }
3366
+ return null;
3367
+ }
3368
+ };
3369
+ }
3370
+ });
3371
+
3372
+ // src/widgets/dev-server/styles.ts
3373
+ var devServerStyles;
3374
+ var init_styles6 = __esm({
3375
+ "src/widgets/dev-server/styles.ts"() {
3376
+ "use strict";
3377
+ init_colors();
3378
+ devServerStyles = {
3379
+ balanced: (data, colors2) => {
3380
+ if (!data.server) return "";
3381
+ const { name, icon, isRunning, isBuilding } = data.server;
3382
+ const status = isRunning ? "running" : isBuilding ? "building" : "stopped";
3383
+ const coloredName = colors2 ? colorize(name, colors2.name) : name;
3384
+ const coloredStatus = colors2 ? colorize(`(${status})`, colors2.status) : `(${status})`;
3385
+ return `${icon} ${coloredName} ${coloredStatus}`;
3386
+ },
3387
+ compact: (data, colors2) => {
3388
+ if (!data.server) return "";
3389
+ const { name, icon, isRunning, isBuilding } = data.server;
3390
+ const statusIcon = isRunning ? "\u{1F680}" : isBuilding ? "\u{1F528}" : "\u{1F4A4}";
3391
+ const coloredName = colors2 ? colorize(name, colors2.name) : name;
3392
+ return `${icon} ${coloredName} ${statusIcon}`;
3393
+ },
3394
+ playful: (data, colors2) => {
3395
+ if (!data.server) return "";
3396
+ const { name, isRunning, isBuilding } = data.server;
3397
+ const emoji = isRunning ? "\u{1F3C3}" : isBuilding ? "\u{1F528}" : "\u{1F4A4}";
3398
+ const coloredName = colors2 ? colorize(name, colors2.name) : name;
3399
+ return `${emoji} ${coloredName}`;
3400
+ },
3401
+ verbose: (data, colors2) => {
3402
+ if (!data.server) return "";
3403
+ const { name, isRunning, isBuilding } = data.server;
3404
+ const status = isRunning ? "running" : isBuilding ? "building" : "stopped";
3405
+ const label = colors2 ? colorize("Dev Server:", colors2.label) : "Dev Server:";
3406
+ const coloredName = colors2 ? colorize(name, colors2.name) : name;
3407
+ const coloredStatus = colors2 ? colorize(`(${status})`, colors2.status) : `(${status})`;
3408
+ return `${label} ${coloredName} ${coloredStatus}`;
3409
+ },
3410
+ labeled: (data, colors2) => {
3411
+ if (!data.server) return "";
3412
+ const { name, icon, isRunning } = data.server;
3413
+ const status = isRunning ? "\u{1F7E2}" : "\u{1F534}";
3414
+ const label = colors2 ? colorize("Server:", colors2.label) : "Server:";
3415
+ const coloredName = colors2 ? colorize(name, colors2.name) : name;
3416
+ return `${label} ${icon} ${coloredName} ${status}`;
3417
+ },
3418
+ indicator: (data, colors2) => {
3419
+ if (!data.server) return "";
3420
+ const { name, icon } = data.server;
3421
+ const coloredName = colors2 ? colorize(name, colors2.name) : name;
3422
+ return `\u25CF ${icon} ${coloredName}`;
3423
+ }
3424
+ };
3425
+ }
3426
+ });
3427
+
3428
+ // src/widgets/dev-server/dev-server-widget.ts
3429
+ var DevServerWidget;
3430
+ var init_dev_server_widget = __esm({
3431
+ "src/widgets/dev-server/dev-server-widget.ts"() {
3432
+ "use strict";
3433
+ init_widget_types();
3434
+ init_theme();
3435
+ init_port_detector();
3436
+ init_process_detector();
3437
+ init_styles6();
3438
+ DevServerWidget = class {
3439
+ id = "dev-server";
3440
+ metadata = createWidgetMetadata(
3441
+ "Dev Server",
3442
+ "Detects running dev server processes using hybrid port+process detection",
3443
+ "1.1.0",
3444
+ "claude-scope",
3445
+ 0
3446
+ );
3447
+ enabled = true;
3448
+ colors;
3449
+ _lineOverride;
3450
+ styleFn = devServerStyles.balanced;
3451
+ cwd = null;
3452
+ portDetector;
3453
+ processDetector;
3454
+ constructor(colors2) {
3455
+ this.colors = colors2 ?? DEFAULT_THEME;
3456
+ this.portDetector = new PortDetector();
3457
+ this.processDetector = new ProcessDetector();
3458
+ }
3459
+ /**
3460
+ * Set display style
3461
+ * @param style - Style to use for rendering
3462
+ */
3463
+ setStyle(style = "balanced") {
3464
+ const fn = devServerStyles[style];
3465
+ if (fn) {
3466
+ this.styleFn = fn;
3467
+ }
3468
+ }
3469
+ /**
3470
+ * Set display line override
3471
+ * @param line - Line number (0-indexed)
3472
+ */
3473
+ setLine(line) {
3474
+ this._lineOverride = line;
3475
+ }
3476
+ /**
3477
+ * Get display line
3478
+ * @returns Line number (0-indexed)
3479
+ */
3480
+ getLine() {
3481
+ return this._lineOverride ?? this.metadata.line ?? 0;
3482
+ }
3483
+ /**
3484
+ * Initialize widget with context
3485
+ * @param context - Widget initialization context
3486
+ */
3487
+ async initialize(context) {
3488
+ this.enabled = context.config?.enabled !== false;
3489
+ }
3490
+ /**
3491
+ * Update widget with new stdin data
3492
+ * @param data - Stdin data from Claude Code
3493
+ */
3494
+ async update(data) {
3495
+ this.cwd = data.cwd;
3496
+ }
3497
+ /**
3498
+ * Check if widget is enabled
3499
+ * @returns true if widget should render
3500
+ */
3501
+ isEnabled() {
3502
+ return this.enabled;
3503
+ }
3504
+ /**
3505
+ * Cleanup resources
3506
+ */
3507
+ async cleanup() {
3508
+ }
3509
+ /**
3510
+ * Render widget output
3511
+ * @param context - Render context
3512
+ * @returns Rendered string, or null if no dev server detected
3513
+ */
3514
+ async render(_context) {
3515
+ if (!this.enabled || !this.cwd) {
3516
+ return null;
3517
+ }
3518
+ const server = await this.detectDevServer();
3519
+ if (!server) {
3520
+ return null;
3521
+ }
3522
+ const renderData = { server };
3523
+ return this.styleFn(renderData, this.colors.devServer);
3524
+ }
3525
+ /**
3526
+ * Detect running dev server using hybrid approach
3527
+ *
3528
+ * 1. Try port-based detection first (more reliable)
3529
+ * 2. Fall back to process-based detection
3530
+ *
3531
+ * @returns Detected server status or null
3532
+ */
3533
+ async detectDevServer() {
3534
+ const portResult = await this.portDetector.detect();
3535
+ if (portResult) {
3536
+ return {
3537
+ name: portResult.name,
3538
+ icon: portResult.icon,
3539
+ isRunning: portResult.isRunning,
3540
+ isBuilding: portResult.isBuilding
3541
+ };
3542
+ }
3543
+ return await this.processDetector.detect();
3544
+ }
3545
+ };
3546
+ }
3547
+ });
3548
+
3549
+ // src/widgets/docker/styles.ts
3550
+ var dockerStyles;
3551
+ var init_styles7 = __esm({
3552
+ "src/widgets/docker/styles.ts"() {
3553
+ "use strict";
3554
+ init_colors();
3555
+ dockerStyles = {
3556
+ balanced: (data, colors2) => {
3557
+ const { running, total } = data.status;
3558
+ if (running === 0) return "";
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 `${label} ${coloredCount} ${status}`;
3564
+ },
3565
+ compact: (data, colors2) => {
3566
+ const { running, total } = data.status;
3567
+ if (running === 0) return "";
3568
+ const count = total > running ? `${running}/${total}` : `${running}`;
3569
+ const coloredCount = colors2 ? colorize(count, colors2.count) : count;
3570
+ return `\u{1F433} ${coloredCount}`;
3571
+ },
3572
+ playful: (data, colors2) => {
3573
+ const { running, total } = data.status;
3574
+ if (running === 0) return "\u{1F433} Docker: \u{1F4A4}";
3575
+ const status = running > 0 ? "\u{1F7E2}" : "\u26AA";
3576
+ const count = total > running ? `${running}/${total}` : `${running}`;
3577
+ const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3578
+ const coloredCount = colors2 ? colorize(count, colors2.count) : count;
3579
+ return `\u{1F433} ${label} ${coloredCount} ${status}`;
3580
+ },
3581
+ verbose: (data, colors2) => {
3582
+ const { running, total } = data.status;
3583
+ if (running === 0) {
3584
+ const label2 = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3585
+ return `${label2} no containers running`;
3586
+ }
3587
+ const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3588
+ const coloredRunning = colors2 ? colorize(String(running), colors2.count) : String(running);
3589
+ return `${label} ${coloredRunning} running${total > running ? ` / ${total} total` : ""}`;
3590
+ },
3591
+ labeled: (data, colors2) => {
3592
+ const { running, total } = data.status;
3593
+ if (running === 0) {
3594
+ const label2 = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3595
+ return `${label2} --`;
3596
+ }
3597
+ const count = total > running ? `${running}/${total}` : `${running}`;
3598
+ const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3599
+ const coloredCount = colors2 ? colorize(count, colors2.count) : count;
3600
+ return `${label} ${coloredCount}`;
3601
+ },
3602
+ indicator: (data, colors2) => {
3603
+ const { running, total } = data.status;
3604
+ if (running === 0) {
3605
+ const label2 = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3606
+ return `\u25CF ${label2} --`;
3607
+ }
3608
+ const count = total > running ? `${running}/${total}` : `${running}`;
3609
+ const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
3610
+ const coloredCount = colors2 ? colorize(count, colors2.count) : count;
3611
+ return `\u25CF ${label} ${coloredCount}`;
3612
+ }
3613
+ };
3614
+ }
3615
+ });
3616
+
3617
+ // src/widgets/docker/docker-widget.ts
3618
+ var import_node_child_process3, import_node_util3, execFileAsync3, DockerWidget;
3619
+ var init_docker_widget = __esm({
3620
+ "src/widgets/docker/docker-widget.ts"() {
3621
+ "use strict";
3622
+ import_node_child_process3 = require("node:child_process");
3623
+ import_node_util3 = require("node:util");
3624
+ init_widget_types();
3625
+ init_theme();
3626
+ init_styles7();
3627
+ execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
3628
+ DockerWidget = class {
3629
+ id = "docker";
3630
+ metadata = createWidgetMetadata(
3631
+ "Docker",
3632
+ "Shows Docker container count and status",
3633
+ "1.0.0",
3634
+ "claude-scope",
3635
+ 0
3636
+ );
3637
+ enabled = true;
3638
+ colors;
3639
+ _lineOverride;
3640
+ styleFn = dockerStyles.balanced;
3641
+ cachedStatus = null;
3642
+ lastCheck = 0;
3643
+ CACHE_TTL = 5e3;
3644
+ constructor(colors2) {
3645
+ this.colors = colors2 ?? DEFAULT_THEME;
3646
+ }
3647
+ setStyle(style = "balanced") {
3648
+ const fn = dockerStyles[style];
3649
+ if (fn) {
3650
+ this.styleFn = fn;
3651
+ }
3652
+ }
3653
+ setLine(line) {
3654
+ this._lineOverride = line;
3655
+ }
3656
+ getLine() {
3657
+ return this._lineOverride ?? this.metadata.line ?? 0;
3658
+ }
3659
+ async initialize(context) {
3660
+ this.enabled = context.config?.enabled !== false;
3661
+ }
3662
+ async update(_data) {
3663
+ }
3664
+ isEnabled() {
3665
+ return this.enabled;
3666
+ }
3667
+ async cleanup() {
3668
+ }
3669
+ async render(_context) {
3670
+ if (!this.enabled) {
3671
+ return null;
3672
+ }
3673
+ const now = Date.now();
3674
+ if (this.cachedStatus && now - this.lastCheck < this.CACHE_TTL) {
3675
+ return this.styleFn({ status: this.cachedStatus }, this.colors.docker);
3676
+ }
3677
+ const status = await this.getDockerStatus();
3678
+ this.cachedStatus = status;
3679
+ this.lastCheck = now;
3680
+ if (!status.isAvailable) {
3681
+ return null;
3682
+ }
3683
+ return this.styleFn({ status }, this.colors.docker);
3684
+ }
3685
+ async getDockerStatus() {
3686
+ try {
3687
+ await execFileAsync3("docker", ["info"], { timeout: 2e3 });
3688
+ const { stdout: runningOutput } = await execFileAsync3("docker", ["ps", "-q"], {
3689
+ timeout: 1e3
3690
+ });
3691
+ const running = runningOutput.trim().split("\n").filter((line) => line).length;
3692
+ const { stdout: allOutput } = await execFileAsync3("docker", ["ps", "-aq"], {
3693
+ timeout: 1e3
3694
+ });
3695
+ const total = allOutput.trim().split("\n").filter((line) => line).length;
3696
+ return { running, total, isAvailable: true };
3697
+ } catch {
3698
+ return { running: 0, total: 0, isAvailable: false };
3699
+ }
3700
+ }
3701
+ };
3702
+ }
3703
+ });
3704
+
3192
3705
  // src/widgets/duration/styles.ts
3193
3706
  function formatDurationWithColors(ms, colors2) {
3194
3707
  if (ms <= 0) return colorize("0s", colors2.value);
@@ -3214,7 +3727,7 @@ function formatDurationWithColors(ms, colors2) {
3214
3727
  return parts.join(" ");
3215
3728
  }
3216
3729
  var durationStyles;
3217
- var init_styles6 = __esm({
3730
+ var init_styles8 = __esm({
3218
3731
  "src/widgets/duration/styles.ts"() {
3219
3732
  "use strict";
3220
3733
  init_colors();
@@ -3294,7 +3807,7 @@ var init_duration_widget = __esm({
3294
3807
  init_widget_types();
3295
3808
  init_theme();
3296
3809
  init_stdin_data_widget();
3297
- init_styles6();
3810
+ init_styles8();
3298
3811
  DurationWidget = class extends StdinDataWidget {
3299
3812
  id = "duration";
3300
3813
  metadata = createWidgetMetadata(
@@ -3339,13 +3852,13 @@ var init_duration_widget = __esm({
3339
3852
  function createGit(cwd) {
3340
3853
  return new NativeGit(cwd);
3341
3854
  }
3342
- var import_node_child_process, import_node_util, execFileAsync, NativeGit;
3855
+ var import_node_child_process4, import_node_util4, execFileAsync4, NativeGit;
3343
3856
  var init_git_provider = __esm({
3344
3857
  "src/providers/git-provider.ts"() {
3345
- "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);
3858
+ "use strict";
3859
+ import_node_child_process4 = require("node:child_process");
3860
+ import_node_util4 = require("node:util");
3861
+ execFileAsync4 = (0, import_node_util4.promisify)(import_node_child_process4.execFile);
3349
3862
  NativeGit = class {
3350
3863
  cwd;
3351
3864
  constructor(cwd) {
@@ -3353,7 +3866,7 @@ var init_git_provider = __esm({
3353
3866
  }
3354
3867
  async status() {
3355
3868
  try {
3356
- const { stdout } = await execFileAsync("git", ["status", "--branch", "--short"], {
3869
+ const { stdout } = await execFileAsync4("git", ["status", "--branch", "--short"], {
3357
3870
  cwd: this.cwd
3358
3871
  });
3359
3872
  const match = stdout.match(/^##\s+(\S+)/m);
@@ -3369,7 +3882,7 @@ var init_git_provider = __esm({
3369
3882
  args.push(...options);
3370
3883
  }
3371
3884
  try {
3372
- const { stdout } = await execFileAsync("git", args, {
3885
+ const { stdout } = await execFileAsync4("git", args, {
3373
3886
  cwd: this.cwd
3374
3887
  });
3375
3888
  const fileMatch = stdout.match(/(\d+)\s+file(s?)\s+changed/);
@@ -3386,7 +3899,7 @@ var init_git_provider = __esm({
3386
3899
  }
3387
3900
  async latestTag() {
3388
3901
  try {
3389
- const { stdout } = await execFileAsync("git", ["describe", "--tags", "--abbrev=0"], {
3902
+ const { stdout } = await execFileAsync4("git", ["describe", "--tags", "--abbrev=0"], {
3390
3903
  cwd: this.cwd
3391
3904
  });
3392
3905
  return stdout.trim();
@@ -3400,7 +3913,7 @@ var init_git_provider = __esm({
3400
3913
 
3401
3914
  // src/widgets/git-tag/styles.ts
3402
3915
  var gitTagStyles;
3403
- var init_styles7 = __esm({
3916
+ var init_styles9 = __esm({
3404
3917
  "src/widgets/git-tag/styles.ts"() {
3405
3918
  "use strict";
3406
3919
  init_colors();
@@ -3450,7 +3963,7 @@ var init_git_tag_widget = __esm({
3450
3963
  init_widget_types();
3451
3964
  init_git_provider();
3452
3965
  init_theme();
3453
- init_styles7();
3966
+ init_styles9();
3454
3967
  GitTagWidget = class {
3455
3968
  id = "git-tag";
3456
3969
  metadata = createWidgetMetadata(
@@ -3522,7 +4035,7 @@ var init_git_tag_widget = __esm({
3522
4035
 
3523
4036
  // src/widgets/git/styles.ts
3524
4037
  var gitStyles;
3525
- var init_styles8 = __esm({
4038
+ var init_styles10 = __esm({
3526
4039
  "src/widgets/git/styles.ts"() {
3527
4040
  "use strict";
3528
4041
  init_colors();
@@ -3624,7 +4137,7 @@ var init_git_widget = __esm({
3624
4137
  init_widget_types();
3625
4138
  init_git_provider();
3626
4139
  init_theme();
3627
- init_styles8();
4140
+ init_styles10();
3628
4141
  GitWidget = class {
3629
4142
  id = "git";
3630
4143
  metadata = createWidgetMetadata(
@@ -3716,7 +4229,7 @@ var init_git_widget = __esm({
3716
4229
 
3717
4230
  // src/widgets/lines/styles.ts
3718
4231
  var linesStyles;
3719
- var init_styles9 = __esm({
4232
+ var init_styles11 = __esm({
3720
4233
  "src/widgets/lines/styles.ts"() {
3721
4234
  "use strict";
3722
4235
  init_colors();
@@ -3776,7 +4289,7 @@ var init_lines_widget = __esm({
3776
4289
  init_widget_types();
3777
4290
  init_theme();
3778
4291
  init_stdin_data_widget();
3779
- init_styles9();
4292
+ init_styles11();
3780
4293
  LinesWidget = class extends StdinDataWidget {
3781
4294
  id = "lines";
3782
4295
  metadata = createWidgetMetadata(
@@ -3821,7 +4334,7 @@ function getShortName(displayName) {
3821
4334
  return displayName.replace(/^Claude\s+/, "");
3822
4335
  }
3823
4336
  var modelStyles;
3824
- var init_styles10 = __esm({
4337
+ var init_styles12 = __esm({
3825
4338
  "src/widgets/model/styles.ts"() {
3826
4339
  "use strict";
3827
4340
  init_colors();
@@ -3876,7 +4389,7 @@ var init_model_widget = __esm({
3876
4389
  init_widget_types();
3877
4390
  init_theme();
3878
4391
  init_stdin_data_widget();
3879
- init_styles10();
4392
+ init_styles12();
3880
4393
  ModelWidget = class extends StdinDataWidget {
3881
4394
  id = "model";
3882
4395
  metadata = createWidgetMetadata(
@@ -4018,6 +4531,16 @@ async function registerWidgetsFromConfig(registry, config, style, themeName) {
4018
4531
  const w = new CacheMetricsWidget(themeColors);
4019
4532
  w.setStyle(s);
4020
4533
  return w;
4534
+ },
4535
+ "dev-server": (s) => {
4536
+ const w = new DevServerWidget(themeColors);
4537
+ w.setStyle(s);
4538
+ return w;
4539
+ },
4540
+ docker: (s) => {
4541
+ const w = new DockerWidget(themeColors);
4542
+ w.setStyle(s);
4543
+ return w;
4021
4544
  }
4022
4545
  };
4023
4546
  for (const [lineNum, widgets] of Object.entries(config.lines)) {
@@ -4065,6 +4588,8 @@ var init_layout_preview = __esm({
4065
4588
  init_config_count_widget();
4066
4589
  init_context_widget();
4067
4590
  init_cost_widget();
4591
+ init_dev_server_widget();
4592
+ init_docker_widget();
4068
4593
  init_duration_widget();
4069
4594
  init_git_tag_widget();
4070
4595
  init_git_widget();
@@ -6639,10 +7164,10 @@ var init_esm2 = __esm({
6639
7164
  });
6640
7165
 
6641
7166
  // node_modules/@inquirer/core/dist/esm/lib/screen-manager.js
6642
- var import_node_util2, height, lastLine, ScreenManager;
7167
+ var import_node_util5, height, lastLine, ScreenManager;
6643
7168
  var init_screen_manager = __esm({
6644
7169
  "node_modules/@inquirer/core/dist/esm/lib/screen-manager.js"() {
6645
- import_node_util2 = require("node:util");
7170
+ import_node_util5 = require("node:util");
6646
7171
  init_utils();
6647
7172
  init_esm2();
6648
7173
  height = (content) => content.split("\n").length;
@@ -6664,7 +7189,7 @@ var init_screen_manager = __esm({
6664
7189
  }
6665
7190
  render(content, bottomContent = "") {
6666
7191
  const promptLine = lastLine(content);
6667
- const rawPromptLine = (0, import_node_util2.stripVTControlCharacters)(promptLine);
7192
+ const rawPromptLine = (0, import_node_util5.stripVTControlCharacters)(promptLine);
6668
7193
  let prompt = rawPromptLine;
6669
7194
  if (this.rl.line.length > 0) {
6670
7195
  prompt = prompt.slice(0, -this.rl.line.length);
@@ -6980,6 +7505,7 @@ function generateBalancedLayout(style, themeName) {
6980
7505
  const theme = getThemeByName(themeName).colors;
6981
7506
  return {
6982
7507
  version: "1.0.0",
7508
+ $aiDocs: "https://github.com/YuriNachos/claude-scope/blob/main/AI-CONFIG-GUIDE.md",
6983
7509
  lines: {
6984
7510
  "0": [
6985
7511
  {
@@ -7048,6 +7574,7 @@ function generateCompactLayout(style, themeName) {
7048
7574
  const theme = getThemeByName(themeName).colors;
7049
7575
  return {
7050
7576
  version: "1.0.0",
7577
+ $aiDocs: "https://github.com/YuriNachos/claude-scope/blob/main/AI-CONFIG-GUIDE.md",
7051
7578
  lines: {
7052
7579
  "0": [
7053
7580
  {
@@ -7088,6 +7615,7 @@ function generateRichLayout(style, themeName) {
7088
7615
  const theme = getThemeByName(themeName).colors;
7089
7616
  return {
7090
7617
  version: "1.0.0",
7618
+ $aiDocs: "https://github.com/YuriNachos/claude-scope/blob/main/AI-CONFIG-GUIDE.md",
7091
7619
  lines: {
7092
7620
  "0": [
7093
7621
  {
@@ -7548,7 +8076,7 @@ async function ensureDefaultConfig() {
7548
8076
  if (!(0, import_node_fs3.existsSync)(configDir)) {
7549
8077
  (0, import_node_fs3.mkdirSync)(configDir, { recursive: true });
7550
8078
  }
7551
- const defaultConfig = generateBalancedLayout("balanced", "cyberpunk-neon");
8079
+ const defaultConfig = generateRichLayout("balanced", "dracula");
7552
8080
  (0, import_node_fs3.writeFileSync)(configPath, JSON.stringify(defaultConfig, null, 2), "utf-8");
7553
8081
  }
7554
8082
 
@@ -7590,382 +8118,626 @@ init_config_count_widget();
7590
8118
  init_context_widget();
7591
8119
  init_cost_widget();
7592
8120
 
7593
- // src/widgets/dev-server/dev-server-widget.ts
7594
- init_widget_types();
7595
- init_theme();
7596
-
7597
- // src/widgets/dev-server/port-detector.ts
7598
- var import_node_child_process2 = require("node:child_process");
7599
- var import_node_util3 = require("node:util");
7600
- var execFileAsync2 = (0, import_node_util3.promisify)(import_node_child_process2.execFile);
7601
- var PORT_MAPPING = {
7602
- 5173: { name: "Vite", icon: "\u26A1" },
7603
- 4200: { name: "Angular", icon: "\u25B2" },
7604
- 3e3: { name: "Dev", icon: "\u{1F680}" },
7605
- 8080: { name: "Webpack", icon: "\u{1F4E6}" },
7606
- 8e3: { name: "Dev", icon: "\u{1F680}" },
7607
- 8888: { name: "Dev", icon: "\u{1F680}" }
7608
- };
7609
- var DEV_PORTS = [5173, 4200, 3e3, 8080, 8e3, 8888];
7610
- var PortDetector = class {
7611
- execFn;
7612
- /**
7613
- * Create a new PortDetector
7614
- * @param execFn - Optional execFile function for testing (dependency injection)
7615
- */
7616
- constructor(execFn) {
7617
- this.execFn = execFn ?? execFileAsync2;
7618
- }
7619
- /**
7620
- * Detect running dev servers by checking listening ports
7621
- * @returns Detected server info or null
7622
- */
7623
- async detect() {
7624
- try {
7625
- const args = [
7626
- "-nP",
7627
- // No host names, no port names
7628
- "-iTCP",
7629
- // Internet TCP
7630
- "-sTCP:LISTEN"
7631
- // TCP LISTEN state only
7632
- ];
7633
- for (const port of DEV_PORTS) {
7634
- args.push("-i", `:${port}`);
7635
- }
7636
- const { stdout } = await this.execFn("lsof", args, {
7637
- timeout: 2e3
7638
- });
7639
- const lines = stdout.trim().split("\n");
7640
- for (const line of lines) {
7641
- if (!line || line.startsWith("COMMAND")) continue;
7642
- const match = line.match(/:(\d+)\s*\(LISTEN\)/);
7643
- if (match) {
7644
- const port = parseInt(match[1], 10);
7645
- const mapping = PORT_MAPPING[port];
7646
- if (mapping) {
7647
- return {
7648
- name: mapping.name,
7649
- icon: mapping.icon,
7650
- port,
7651
- isRunning: true,
7652
- isBuilding: false
7653
- };
7654
- }
7655
- }
7656
- }
7657
- return null;
7658
- } catch {
7659
- return null;
7660
- }
7661
- }
7662
- };
8121
+ // src/widgets/dev-server/index.ts
8122
+ init_dev_server_widget();
8123
+ init_port_detector();
8124
+ init_process_detector();
7663
8125
 
7664
- // src/widgets/dev-server/process-detector.ts
7665
- var import_node_child_process3 = require("node:child_process");
7666
- var import_node_util4 = require("node:util");
7667
- var execFileAsync3 = (0, import_node_util4.promisify)(import_node_child_process3.execFile);
7668
- var ProcessDetector = class {
7669
- execFn;
7670
- processPatterns = [
7671
- // Generic server patterns - more specific to avoid shell history false positives
7672
- { regex: /^[\w\s]+\/npm\s+(exec|run)\s+serve/i, name: "Server", icon: "\u{1F310}" },
7673
- { regex: /^[\w\s]+\/npx\s+serve\s+-/i, name: "Server", icon: "\u{1F310}" },
7674
- { regex: /^[\w\s]+\/(python|python3)\s+-m\s+http\.server/i, name: "HTTP", icon: "\u{1F310}" },
7675
- // Generic dev/build patterns - require full command path
7676
- { regex: /^[\w\s]+\/(npm|yarn|pnpm|bun)\s+run\s+dev\s*$/i, name: "Dev", icon: "\u{1F680}" },
7677
- { regex: /^[\w\s]+\/(npm|yarn|pnpm|bun)\s+run\s+build\s*$/i, name: "Build", icon: "\u{1F528}" },
7678
- // Framework-specific patterns - require executable path
7679
- { regex: /\/(nuxt|next|astro|remix|svelte)\s+dev/i, name: "Framework", icon: "\u26A1" },
7680
- { regex: /\/node.*\/vite\s*$/i, name: "Vite", icon: "\u26A1" },
7681
- // Fallback: simpler patterns but checked last
7682
- { regex: /\s(nuxt|next|vite)\s+dev\s/i, name: "DevServer", icon: "\u26A1" }
7683
- ];
7684
- /**
7685
- * Create a new ProcessDetector
7686
- * @param execFn - Optional execFile function for testing (dependency injection)
7687
- */
7688
- constructor(execFn) {
7689
- this.execFn = execFn ?? execFileAsync3;
7690
- }
7691
- /**
7692
- * Detect running dev server by parsing system process list
7693
- * @returns Detected server status or null
7694
- */
7695
- async detect() {
7696
- try {
7697
- const { stdout } = await this.execFn("ps", ["aux"], {
7698
- timeout: 1e3
7699
- });
7700
- for (const pattern of this.processPatterns) {
7701
- if (pattern.regex.test(stdout)) {
7702
- const isBuilding = pattern.name.toLowerCase().includes("build");
7703
- const isRunning = !isBuilding;
7704
- return {
7705
- name: pattern.name,
7706
- icon: pattern.icon,
7707
- isRunning,
7708
- isBuilding
7709
- };
7710
- }
7711
- }
7712
- } catch {
7713
- }
7714
- return null;
7715
- }
7716
- };
8126
+ // src/widgets/docker/index.ts
8127
+ init_docker_widget();
7717
8128
 
7718
- // src/widgets/dev-server/styles.ts
7719
- init_colors();
7720
- var devServerStyles = {
7721
- balanced: (data, colors2) => {
7722
- if (!data.server) return "";
7723
- const { name, icon, isRunning, isBuilding } = data.server;
7724
- const status = isRunning ? "running" : isBuilding ? "building" : "stopped";
7725
- const coloredName = colors2 ? colorize(name, colors2.name) : name;
7726
- const coloredStatus = colors2 ? colorize(`(${status})`, colors2.status) : `(${status})`;
7727
- return `${icon} ${coloredName} ${coloredStatus}`;
7728
- },
7729
- compact: (data, colors2) => {
7730
- if (!data.server) return "";
7731
- const { name, icon, isRunning, isBuilding } = data.server;
7732
- const statusIcon = isRunning ? "\u{1F680}" : isBuilding ? "\u{1F528}" : "\u{1F4A4}";
7733
- const coloredName = colors2 ? colorize(name, colors2.name) : name;
7734
- return `${icon} ${coloredName} ${statusIcon}`;
7735
- },
7736
- playful: (data, colors2) => {
7737
- if (!data.server) return "";
7738
- const { name, isRunning, isBuilding } = data.server;
7739
- const emoji = isRunning ? "\u{1F3C3}" : isBuilding ? "\u{1F528}" : "\u{1F4A4}";
7740
- const coloredName = colors2 ? colorize(name, colors2.name) : name;
7741
- return `${emoji} ${coloredName}`;
7742
- },
7743
- verbose: (data, colors2) => {
7744
- if (!data.server) return "";
7745
- const { name, isRunning, isBuilding } = data.server;
7746
- const status = isRunning ? "running" : isBuilding ? "building" : "stopped";
7747
- const label = colors2 ? colorize("Dev Server:", colors2.label) : "Dev Server:";
7748
- const coloredName = colors2 ? colorize(name, colors2.name) : name;
7749
- const coloredStatus = colors2 ? colorize(`(${status})`, colors2.status) : `(${status})`;
7750
- return `${label} ${coloredName} ${coloredStatus}`;
7751
- },
7752
- labeled: (data, colors2) => {
7753
- if (!data.server) return "";
7754
- const { name, icon, isRunning } = data.server;
7755
- const status = isRunning ? "\u{1F7E2}" : "\u{1F534}";
7756
- const label = colors2 ? colorize("Server:", colors2.label) : "Server:";
7757
- const coloredName = colors2 ? colorize(name, colors2.name) : name;
7758
- return `${label} ${icon} ${coloredName} ${status}`;
7759
- },
7760
- indicator: (data, colors2) => {
7761
- if (!data.server) return "";
7762
- const { name, icon } = data.server;
7763
- const coloredName = colors2 ? colorize(name, colors2.name) : name;
7764
- return `\u25CF ${icon} ${coloredName}`;
7765
- }
7766
- };
8129
+ // src/core/widget-factory.ts
8130
+ init_duration_widget();
7767
8131
 
7768
- // src/widgets/dev-server/dev-server-widget.ts
7769
- var DevServerWidget = class {
7770
- id = "dev-server";
8132
+ // src/widgets/empty-line-widget.ts
8133
+ init_widget_types();
8134
+ init_stdin_data_widget();
8135
+ var EmptyLineWidget = class extends StdinDataWidget {
8136
+ id = "empty-line";
7771
8137
  metadata = createWidgetMetadata(
7772
- "Dev Server",
7773
- "Detects running dev server processes using hybrid port+process detection",
7774
- "1.1.0",
8138
+ "Empty Line",
8139
+ "Empty line separator",
8140
+ "1.0.0",
7775
8141
  "claude-scope",
7776
- 0
8142
+ 5
8143
+ // Sixth line (0-indexed)
7777
8144
  );
7778
- enabled = true;
7779
- colors;
7780
8145
  _lineOverride;
7781
- styleFn = devServerStyles.balanced;
7782
- cwd = null;
7783
- portDetector;
7784
- processDetector;
7785
- constructor(colors2) {
7786
- this.colors = colors2 ?? DEFAULT_THEME;
7787
- this.portDetector = new PortDetector();
7788
- this.processDetector = new ProcessDetector();
7789
- }
7790
8146
  /**
7791
- * Set display style
7792
- * @param style - Style to use for rendering
8147
+ * All styles return the same value (Braille Pattern Blank).
8148
+ * This method exists for API consistency with other widgets.
7793
8149
  */
7794
- setStyle(style = "balanced") {
7795
- const fn = devServerStyles[style];
7796
- if (fn) {
7797
- this.styleFn = fn;
7798
- }
8150
+ setStyle(_style) {
7799
8151
  }
7800
- /**
7801
- * Set display line override
7802
- * @param line - Line number (0-indexed)
7803
- */
7804
8152
  setLine(line) {
7805
8153
  this._lineOverride = line;
7806
8154
  }
7807
- /**
7808
- * Get display line
7809
- * @returns Line number (0-indexed)
7810
- */
7811
8155
  getLine() {
7812
8156
  return this._lineOverride ?? this.metadata.line ?? 0;
7813
8157
  }
7814
8158
  /**
7815
- * Initialize widget with context
7816
- * @param context - Widget initialization context
8159
+ * Return Braille Pattern Blank to create a visible empty separator line.
8160
+ * U+2800 occupies cell width but appears blank, ensuring the line renders.
7817
8161
  */
7818
- async initialize(context) {
7819
- this.enabled = context.config?.enabled !== false;
8162
+ renderWithData(_data, _context) {
8163
+ return "\u2800";
8164
+ }
8165
+ };
8166
+
8167
+ // src/core/widget-factory.ts
8168
+ init_git_tag_widget();
8169
+ init_git_widget();
8170
+ init_lines_widget();
8171
+ init_model_widget();
8172
+
8173
+ // src/widgets/poker-widget.ts
8174
+ init_style_types();
8175
+ init_widget_types();
8176
+ init_theme();
8177
+ init_stdin_data_widget();
8178
+
8179
+ // src/widgets/poker/deck.ts
8180
+ var import_node_crypto = require("node:crypto");
8181
+
8182
+ // src/widgets/poker/types.ts
8183
+ var Suit = {
8184
+ Spades: "spades",
8185
+ Hearts: "hearts",
8186
+ Diamonds: "diamonds",
8187
+ Clubs: "clubs"
8188
+ };
8189
+ var SUIT_SYMBOLS = {
8190
+ spades: "\u2660",
8191
+ hearts: "\u2665",
8192
+ diamonds: "\u2666",
8193
+ clubs: "\u2663"
8194
+ };
8195
+ var EMOJI_SYMBOLS = {
8196
+ spades: "\u2660\uFE0F",
8197
+ // ♠️
8198
+ hearts: "\u2665\uFE0F",
8199
+ // ♥️
8200
+ diamonds: "\u2666\uFE0F",
8201
+ // ♦️
8202
+ clubs: "\u2663\uFE0F"
8203
+ // ♣️
8204
+ };
8205
+ function isRedSuit(suit) {
8206
+ return suit === "hearts" || suit === "diamonds";
8207
+ }
8208
+ var Rank = {
8209
+ Two: "2",
8210
+ Three: "3",
8211
+ Four: "4",
8212
+ Five: "5",
8213
+ Six: "6",
8214
+ Seven: "7",
8215
+ Eight: "8",
8216
+ Nine: "9",
8217
+ Ten: "10",
8218
+ Jack: "J",
8219
+ Queen: "Q",
8220
+ King: "K",
8221
+ Ace: "A"
8222
+ };
8223
+ function getRankValue(rank) {
8224
+ const values = {
8225
+ "2": 2,
8226
+ "3": 3,
8227
+ "4": 4,
8228
+ "5": 5,
8229
+ "6": 6,
8230
+ "7": 7,
8231
+ "8": 8,
8232
+ "9": 9,
8233
+ "10": 10,
8234
+ J: 11,
8235
+ Q: 12,
8236
+ K: 13,
8237
+ A: 14
8238
+ };
8239
+ return values[rank];
8240
+ }
8241
+ function formatCard(card) {
8242
+ return `${card.rank}${SUIT_SYMBOLS[card.suit]}`;
8243
+ }
8244
+ function formatCardEmoji(card) {
8245
+ return `${card.rank}${EMOJI_SYMBOLS[card.suit]}`;
8246
+ }
8247
+
8248
+ // src/widgets/poker/deck.ts
8249
+ var ALL_SUITS = [Suit.Spades, Suit.Hearts, Suit.Diamonds, Suit.Clubs];
8250
+ var ALL_RANKS = [
8251
+ Rank.Two,
8252
+ Rank.Three,
8253
+ Rank.Four,
8254
+ Rank.Five,
8255
+ Rank.Six,
8256
+ Rank.Seven,
8257
+ Rank.Eight,
8258
+ Rank.Nine,
8259
+ Rank.Ten,
8260
+ Rank.Jack,
8261
+ Rank.Queen,
8262
+ Rank.King,
8263
+ Rank.Ace
8264
+ ];
8265
+ var Deck = class {
8266
+ cards = [];
8267
+ constructor() {
8268
+ this.initialize();
8269
+ this.shuffle();
7820
8270
  }
7821
8271
  /**
7822
- * Update widget with new stdin data
7823
- * @param data - Stdin data from Claude Code
8272
+ * Create a standard 52-card deck
7824
8273
  */
7825
- async update(data) {
7826
- this.cwd = data.cwd;
8274
+ initialize() {
8275
+ this.cards = [];
8276
+ for (const suit of ALL_SUITS) {
8277
+ for (const rank of ALL_RANKS) {
8278
+ this.cards.push({ rank, suit });
8279
+ }
8280
+ }
7827
8281
  }
7828
8282
  /**
7829
- * Check if widget is enabled
7830
- * @returns true if widget should render
8283
+ * Shuffle deck using Fisher-Yates algorithm with crypto.random
7831
8284
  */
7832
- isEnabled() {
7833
- return this.enabled;
8285
+ shuffle() {
8286
+ for (let i = this.cards.length - 1; i > 0; i--) {
8287
+ const j = (0, import_node_crypto.randomInt)(0, i + 1);
8288
+ [this.cards[i], this.cards[j]] = [this.cards[j], this.cards[i]];
8289
+ }
7834
8290
  }
7835
8291
  /**
7836
- * Cleanup resources
8292
+ * Deal one card from the top of the deck
8293
+ * @throws Error if deck is empty
7837
8294
  */
7838
- async cleanup() {
8295
+ deal() {
8296
+ if (this.cards.length === 0) {
8297
+ throw new Error("Deck is empty");
8298
+ }
8299
+ return this.cards.pop();
7839
8300
  }
7840
8301
  /**
7841
- * Render widget output
7842
- * @param context - Render context
7843
- * @returns Rendered string, or null if no dev server detected
8302
+ * Get number of remaining cards in deck
7844
8303
  */
7845
- async render(_context) {
7846
- if (!this.enabled || !this.cwd) {
7847
- return null;
8304
+ remaining() {
8305
+ return this.cards.length;
8306
+ }
8307
+ };
8308
+
8309
+ // src/widgets/poker/hand-evaluator.ts
8310
+ var HAND_DISPLAY = {
8311
+ [10 /* RoyalFlush */]: { name: "Royal Flush", emoji: "\u{1F3C6}" },
8312
+ [9 /* StraightFlush */]: { name: "Straight Flush", emoji: "\u{1F525}" },
8313
+ [8 /* FourOfAKind */]: { name: "Four of a Kind", emoji: "\u{1F48E}" },
8314
+ [7 /* FullHouse */]: { name: "Full House", emoji: "\u{1F3E0}" },
8315
+ [6 /* Flush */]: { name: "Flush", emoji: "\u{1F4A7}" },
8316
+ [5 /* Straight */]: { name: "Straight", emoji: "\u{1F4C8}" },
8317
+ [4 /* ThreeOfAKind */]: { name: "Three of a Kind", emoji: "\u{1F3AF}" },
8318
+ [3 /* TwoPair */]: { name: "Two Pair", emoji: "\u270C\uFE0F" },
8319
+ [2 /* OnePair */]: { name: "One Pair", emoji: "\u{1F44D}" },
8320
+ [1 /* HighCard */]: { name: "High Card", emoji: "\u{1F0CF}" }
8321
+ };
8322
+ function countRanks(cards) {
8323
+ const counts = /* @__PURE__ */ new Map();
8324
+ for (const card of cards) {
8325
+ const value = getRankValue(card.rank);
8326
+ counts.set(value, (counts.get(value) || 0) + 1);
8327
+ }
8328
+ return counts;
8329
+ }
8330
+ function countSuits(cards) {
8331
+ const counts = /* @__PURE__ */ new Map();
8332
+ for (const card of cards) {
8333
+ counts.set(card.suit, (counts.get(card.suit) || 0) + 1);
8334
+ }
8335
+ return counts;
8336
+ }
8337
+ function findCardsOfRank(cards, targetRank) {
8338
+ const indices = [];
8339
+ for (let i = 0; i < cards.length; i++) {
8340
+ if (getRankValue(cards[i].rank) === targetRank) {
8341
+ indices.push(i);
7848
8342
  }
7849
- const server = await this.detectDevServer();
7850
- if (!server) {
7851
- return null;
8343
+ }
8344
+ return indices;
8345
+ }
8346
+ function findCardsOfSuit(cards, targetSuit) {
8347
+ const indices = [];
8348
+ for (let i = 0; i < cards.length; i++) {
8349
+ if (cards[i].suit === targetSuit) {
8350
+ indices.push(i);
7852
8351
  }
7853
- const renderData = { server };
7854
- return this.styleFn(renderData, this.colors.devServer);
7855
8352
  }
7856
- /**
7857
- * Detect running dev server using hybrid approach
7858
- *
7859
- * 1. Try port-based detection first (more reliable)
7860
- * 2. Fall back to process-based detection
7861
- *
7862
- * @returns Detected server status or null
7863
- */
7864
- async detectDevServer() {
7865
- const portResult = await this.portDetector.detect();
7866
- if (portResult) {
8353
+ return indices;
8354
+ }
8355
+ function findFlushSuit(cards) {
8356
+ const suitCounts = countSuits(cards);
8357
+ for (const [suit, count] of suitCounts.entries()) {
8358
+ if (count >= 5) return suit;
8359
+ }
8360
+ return null;
8361
+ }
8362
+ function getStraightIndices(cards, highCard) {
8363
+ const uniqueValues = /* @__PURE__ */ new Set();
8364
+ const cardIndicesByRank = /* @__PURE__ */ new Map();
8365
+ for (let i = 0; i < cards.length; i++) {
8366
+ const value = getRankValue(cards[i].rank);
8367
+ if (!cardIndicesByRank.has(value)) {
8368
+ cardIndicesByRank.set(value, []);
8369
+ uniqueValues.add(value);
8370
+ }
8371
+ cardIndicesByRank.get(value)?.push(i);
8372
+ }
8373
+ const sortedValues = Array.from(uniqueValues).sort((a, b) => b - a);
8374
+ if (sortedValues.includes(14)) {
8375
+ sortedValues.push(1);
8376
+ }
8377
+ for (let i = 0; i <= sortedValues.length - 5; i++) {
8378
+ const current = sortedValues[i];
8379
+ const next1 = sortedValues[i + 1];
8380
+ const next2 = sortedValues[i + 2];
8381
+ const next3 = sortedValues[i + 3];
8382
+ const next4 = sortedValues[i + 4];
8383
+ if (current - next1 === 1 && current - next2 === 2 && current - next3 === 3 && current - next4 === 4) {
8384
+ if (current === highCard) {
8385
+ const indices = [];
8386
+ indices.push(cardIndicesByRank.get(current)[0]);
8387
+ indices.push(cardIndicesByRank.get(next1)[0]);
8388
+ indices.push(cardIndicesByRank.get(next2)[0]);
8389
+ indices.push(cardIndicesByRank.get(next3)[0]);
8390
+ const rank4 = next4 === 1 ? 14 : next4;
8391
+ indices.push(cardIndicesByRank.get(rank4)[0]);
8392
+ return indices;
8393
+ }
8394
+ }
8395
+ }
8396
+ return [];
8397
+ }
8398
+ function getStraightFlushHighCard(cards, suit) {
8399
+ const suitCards = cards.filter((c) => c.suit === suit);
8400
+ return getStraightHighCard(suitCards);
8401
+ }
8402
+ function getStraightFlushIndices(cards, highCard, suit) {
8403
+ const _suitCards = cards.filter((c) => c.suit === suit);
8404
+ const suitCardIndices = [];
8405
+ const indexMap = /* @__PURE__ */ new Map();
8406
+ for (let i = 0; i < cards.length; i++) {
8407
+ if (cards[i].suit === suit) {
8408
+ indexMap.set(suitCardIndices.length, i);
8409
+ suitCardIndices.push(cards[i]);
8410
+ }
8411
+ }
8412
+ const indices = getStraightIndices(suitCardIndices, highCard);
8413
+ return indices.map((idx) => indexMap.get(idx));
8414
+ }
8415
+ function getFullHouseIndices(cards) {
8416
+ const rankCounts = countRanks(cards);
8417
+ let tripsRank = 0;
8418
+ for (const [rank, count] of rankCounts.entries()) {
8419
+ if (count === 3) {
8420
+ tripsRank = rank;
8421
+ break;
8422
+ }
8423
+ }
8424
+ let pairRank = 0;
8425
+ for (const [rank, count] of rankCounts.entries()) {
8426
+ if (count >= 2 && rank !== tripsRank) {
8427
+ pairRank = rank;
8428
+ break;
8429
+ }
8430
+ }
8431
+ if (pairRank === 0) {
8432
+ const tripsRanks = [];
8433
+ for (const [rank, count] of rankCounts.entries()) {
8434
+ if (count === 3) {
8435
+ tripsRanks.push(rank);
8436
+ }
8437
+ }
8438
+ if (tripsRanks.length >= 2) {
8439
+ tripsRanks.sort((a, b) => b - a);
8440
+ tripsRank = tripsRanks[0];
8441
+ pairRank = tripsRanks[1];
8442
+ }
8443
+ }
8444
+ const tripsIndices = findCardsOfRank(cards, tripsRank);
8445
+ const pairIndices = findCardsOfRank(cards, pairRank);
8446
+ return [...tripsIndices.slice(0, 3), ...pairIndices.slice(0, 2)];
8447
+ }
8448
+ function isFlush(cards) {
8449
+ const suitCounts = countSuits(cards);
8450
+ for (const count of suitCounts.values()) {
8451
+ if (count >= 5) return true;
8452
+ }
8453
+ return false;
8454
+ }
8455
+ function getStraightHighCard(cards) {
8456
+ const uniqueValues = /* @__PURE__ */ new Set();
8457
+ for (const card of cards) {
8458
+ uniqueValues.add(getRankValue(card.rank));
8459
+ }
8460
+ const sortedValues = Array.from(uniqueValues).sort((a, b) => b - a);
8461
+ if (sortedValues.includes(14)) {
8462
+ sortedValues.push(1);
8463
+ }
8464
+ for (let i = 0; i <= sortedValues.length - 5; i++) {
8465
+ const current = sortedValues[i];
8466
+ const next1 = sortedValues[i + 1];
8467
+ const next2 = sortedValues[i + 2];
8468
+ const next3 = sortedValues[i + 3];
8469
+ const next4 = sortedValues[i + 4];
8470
+ if (current - next1 === 1 && current - next2 === 2 && current - next3 === 3 && current - next4 === 4) {
8471
+ return current;
8472
+ }
8473
+ }
8474
+ return null;
8475
+ }
8476
+ function getMaxCount(cards) {
8477
+ const rankCounts = countRanks(cards);
8478
+ let maxCount = 0;
8479
+ for (const count of rankCounts.values()) {
8480
+ if (count > maxCount) {
8481
+ maxCount = count;
8482
+ }
8483
+ }
8484
+ return maxCount;
8485
+ }
8486
+ function getPairCount(cards) {
8487
+ const rankCounts = countRanks(cards);
8488
+ let pairCount = 0;
8489
+ for (const count of rankCounts.values()) {
8490
+ if (count === 2) {
8491
+ pairCount++;
8492
+ }
8493
+ }
8494
+ return pairCount;
8495
+ }
8496
+ function getMostCommonRank(cards) {
8497
+ const rankCounts = countRanks(cards);
8498
+ let bestRank = 0;
8499
+ let bestCount = 0;
8500
+ for (const [rank, count] of rankCounts.entries()) {
8501
+ if (count > bestCount) {
8502
+ bestCount = count;
8503
+ bestRank = rank;
8504
+ }
8505
+ }
8506
+ return bestRank > 0 ? bestRank : null;
8507
+ }
8508
+ function getTwoPairRanks(cards) {
8509
+ const rankCounts = countRanks(cards);
8510
+ const pairRanks = [];
8511
+ for (const [rank, count] of rankCounts.entries()) {
8512
+ if (count >= 2) {
8513
+ pairRanks.push(rank);
8514
+ }
8515
+ }
8516
+ pairRanks.sort((a, b) => b - a);
8517
+ return pairRanks.slice(0, 2);
8518
+ }
8519
+ function getHighestCardIndex(cards) {
8520
+ let highestIdx = 0;
8521
+ let highestValue = 0;
8522
+ for (let i = 0; i < cards.length; i++) {
8523
+ const value = getRankValue(cards[i].rank);
8524
+ if (value > highestValue) {
8525
+ highestValue = value;
8526
+ highestIdx = i;
8527
+ }
8528
+ }
8529
+ return highestIdx;
8530
+ }
8531
+ function evaluateHand(hole, board) {
8532
+ const allCards = [...hole, ...board];
8533
+ const flush = isFlush(allCards);
8534
+ const straightHighCard = getStraightHighCard(allCards);
8535
+ const maxCount = getMaxCount(allCards);
8536
+ const pairCount = getPairCount(allCards);
8537
+ if (flush && straightHighCard === 14) {
8538
+ const flushSuit = findFlushSuit(allCards);
8539
+ const sfHighCard = getStraightFlushHighCard(allCards, flushSuit);
8540
+ if (sfHighCard === 14) {
8541
+ const participatingCards = getStraightFlushIndices(allCards, 14, flushSuit);
7867
8542
  return {
7868
- name: portResult.name,
7869
- icon: portResult.icon,
7870
- isRunning: portResult.isRunning,
7871
- isBuilding: portResult.isBuilding
8543
+ rank: 10 /* RoyalFlush */,
8544
+ ...HAND_DISPLAY[10 /* RoyalFlush */],
8545
+ participatingCards
7872
8546
  };
7873
8547
  }
7874
- return await this.processDetector.detect();
7875
8548
  }
7876
- };
7877
-
7878
- // src/widgets/docker/docker-widget.ts
7879
- var import_node_child_process4 = require("node:child_process");
7880
- var import_node_util5 = require("node:util");
7881
- init_widget_types();
7882
- init_theme();
8549
+ if (flush) {
8550
+ const flushSuit = findFlushSuit(allCards);
8551
+ const sfHighCard = getStraightFlushHighCard(allCards, flushSuit);
8552
+ if (sfHighCard !== null) {
8553
+ const participatingCards = getStraightFlushIndices(allCards, sfHighCard, flushSuit);
8554
+ return {
8555
+ rank: 9 /* StraightFlush */,
8556
+ ...HAND_DISPLAY[9 /* StraightFlush */],
8557
+ participatingCards
8558
+ };
8559
+ }
8560
+ }
8561
+ if (maxCount === 4) {
8562
+ const rank = getMostCommonRank(allCards);
8563
+ const participatingCards = findCardsOfRank(allCards, rank);
8564
+ return {
8565
+ rank: 8 /* FourOfAKind */,
8566
+ ...HAND_DISPLAY[8 /* FourOfAKind */],
8567
+ participatingCards
8568
+ };
8569
+ }
8570
+ if (maxCount === 3 && pairCount >= 1) {
8571
+ const participatingCards = getFullHouseIndices(allCards);
8572
+ return { rank: 7 /* FullHouse */, ...HAND_DISPLAY[7 /* FullHouse */], participatingCards };
8573
+ }
8574
+ if (flush) {
8575
+ const flushSuit = findFlushSuit(allCards);
8576
+ const suitIndices = findCardsOfSuit(allCards, flushSuit);
8577
+ const participatingCards = suitIndices.slice(0, 5);
8578
+ return { rank: 6 /* Flush */, ...HAND_DISPLAY[6 /* Flush */], participatingCards };
8579
+ }
8580
+ if (straightHighCard !== null) {
8581
+ const participatingCards = getStraightIndices(allCards, straightHighCard);
8582
+ return { rank: 5 /* Straight */, ...HAND_DISPLAY[5 /* Straight */], participatingCards };
8583
+ }
8584
+ if (maxCount === 3) {
8585
+ const rank = getMostCommonRank(allCards);
8586
+ const participatingCards = findCardsOfRank(allCards, rank);
8587
+ return {
8588
+ rank: 4 /* ThreeOfAKind */,
8589
+ ...HAND_DISPLAY[4 /* ThreeOfAKind */],
8590
+ participatingCards
8591
+ };
8592
+ }
8593
+ if (pairCount >= 2) {
8594
+ const [rank1, rank2] = getTwoPairRanks(allCards);
8595
+ const pair1Indices = findCardsOfRank(allCards, rank1);
8596
+ const pair2Indices = findCardsOfRank(allCards, rank2);
8597
+ const participatingCards = [...pair1Indices, ...pair2Indices];
8598
+ return { rank: 3 /* TwoPair */, ...HAND_DISPLAY[3 /* TwoPair */], participatingCards };
8599
+ }
8600
+ if (pairCount === 1) {
8601
+ const rank = getMostCommonRank(allCards);
8602
+ const participatingCards = findCardsOfRank(allCards, rank);
8603
+ return { rank: 2 /* OnePair */, ...HAND_DISPLAY[2 /* OnePair */], participatingCards };
8604
+ }
8605
+ const highestIdx = getHighestCardIndex(allCards);
8606
+ return {
8607
+ rank: 1 /* HighCard */,
8608
+ ...HAND_DISPLAY[1 /* HighCard */],
8609
+ participatingCards: [highestIdx]
8610
+ };
8611
+ }
7883
8612
 
7884
- // src/widgets/docker/styles.ts
8613
+ // src/widgets/poker/styles.ts
7885
8614
  init_colors();
7886
- var dockerStyles = {
7887
- balanced: (data, colors2) => {
7888
- const { running, total } = data.status;
7889
- if (running === 0) return "";
7890
- const status = running > 0 ? "\u{1F7E2}" : "\u26AA";
7891
- const count = total > running ? `${running}/${total}` : `${running}`;
7892
- const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7893
- const coloredCount = colors2 ? colorize(count, colors2.count) : count;
7894
- return `${label} ${coloredCount} ${status}`;
7895
- },
7896
- compact: (data, colors2) => {
7897
- const { running, total } = data.status;
7898
- if (running === 0) return "";
7899
- const count = total > running ? `${running}/${total}` : `${running}`;
7900
- const coloredCount = colors2 ? colorize(count, colors2.count) : count;
7901
- return `\u{1F433} ${coloredCount}`;
7902
- },
7903
- playful: (data, colors2) => {
7904
- const { running, total } = data.status;
7905
- if (running === 0) return "\u{1F433} Docker: \u{1F4A4}";
7906
- const status = running > 0 ? "\u{1F7E2}" : "\u26AA";
7907
- const count = total > running ? `${running}/${total}` : `${running}`;
7908
- const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7909
- const coloredCount = colors2 ? colorize(count, colors2.count) : count;
7910
- return `\u{1F433} ${label} ${coloredCount} ${status}`;
7911
- },
7912
- verbose: (data, colors2) => {
7913
- const { running, total } = data.status;
7914
- if (running === 0) {
7915
- const label2 = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7916
- return `${label2} no containers running`;
7917
- }
7918
- const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7919
- const coloredRunning = colors2 ? colorize(String(running), colors2.count) : String(running);
7920
- return `${label} ${coloredRunning} running${total > running ? ` / ${total} total` : ""}`;
7921
- },
7922
- labeled: (data, colors2) => {
7923
- const { running, total } = data.status;
7924
- if (running === 0) {
7925
- const label2 = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7926
- return `${label2} --`;
7927
- }
7928
- const count = total > running ? `${running}/${total}` : `${running}`;
7929
- const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7930
- const coloredCount = colors2 ? colorize(count, colors2.count) : count;
7931
- return `${label} ${coloredCount}`;
8615
+ init_formatters();
8616
+ var HAND_ABBREVIATIONS = {
8617
+ "Royal Flush": "RF",
8618
+ "Straight Flush": "SF",
8619
+ "Four of a Kind": "4K",
8620
+ "Full House": "FH",
8621
+ Flush: "FL",
8622
+ Straight: "ST",
8623
+ "Three of a Kind": "3K",
8624
+ "Two Pair": "2P",
8625
+ "One Pair": "1P",
8626
+ "High Card": "HC",
8627
+ Nothing: "\u2014"
8628
+ };
8629
+ function formatCardByParticipation(cardData, isParticipating) {
8630
+ const color = isRedSuit(cardData.card.suit) ? red : gray;
8631
+ const cardText = formatCard(cardData.card);
8632
+ if (isParticipating) {
8633
+ return `${color}${bold}(${cardText})${reset} `;
8634
+ } else {
8635
+ return `${color}${cardText}${reset} `;
8636
+ }
8637
+ }
8638
+ function formatCardCompact(cardData, isParticipating) {
8639
+ const color = isRedSuit(cardData.card.suit) ? red : gray;
8640
+ const cardText = formatCardTextCompact(cardData.card);
8641
+ if (isParticipating) {
8642
+ return `${color}${bold}(${cardText})${reset}`;
8643
+ } else {
8644
+ return `${color}${cardText}${reset}`;
8645
+ }
8646
+ }
8647
+ function formatCardTextCompact(card) {
8648
+ const rankSymbols = {
8649
+ "10": "T",
8650
+ "11": "J",
8651
+ "12": "Q",
8652
+ "13": "K",
8653
+ "14": "A"
8654
+ };
8655
+ const rank = String(card.rank);
8656
+ const rankSymbol = rankSymbols[rank] ?? rank;
8657
+ return `${rankSymbol}${card.suit}`;
8658
+ }
8659
+ function formatCardEmojiByParticipation(cardData, isParticipating) {
8660
+ const cardText = formatCardEmoji(cardData.card);
8661
+ if (isParticipating) {
8662
+ return `${bold}(${cardText})${reset} `;
8663
+ } else {
8664
+ return `${cardText} `;
8665
+ }
8666
+ }
8667
+ function formatHandResult(handResult, colors2) {
8668
+ if (!handResult) {
8669
+ return "\u2014";
8670
+ }
8671
+ const playerParticipates = handResult.participatingIndices.some((idx) => idx < 2);
8672
+ const resultText = !playerParticipates ? `Nothing \u{1F0CF}` : `${handResult.name}! ${handResult.emoji}`;
8673
+ if (!colors2) return resultText;
8674
+ return colorize2(resultText, colors2.result);
8675
+ }
8676
+ function getHandAbbreviation(handResult) {
8677
+ if (!handResult) {
8678
+ return "\u2014 (\u2014)";
8679
+ }
8680
+ const abbreviation = HAND_ABBREVIATIONS[handResult.name] ?? "\u2014";
8681
+ return `${abbreviation} (${handResult.name})`;
8682
+ }
8683
+ function balancedStyle2(data, colors2) {
8684
+ const { holeCards, boardCards, handResult } = data;
8685
+ const participatingSet = new Set(handResult?.participatingIndices || []);
8686
+ const handStr = holeCards.map((hc, idx) => formatCardByParticipation(hc, participatingSet.has(idx))).join("");
8687
+ const boardStr = boardCards.map((bc, idx) => formatCardByParticipation(bc, participatingSet.has(idx + 2))).join("");
8688
+ const labelColor = colors2?.participating ?? lightGray;
8689
+ const handLabel = colorize2("Hand:", labelColor);
8690
+ const boardLabel = colorize2("Board:", labelColor);
8691
+ return `${handLabel} ${handStr}| ${boardLabel} ${boardStr}\u2192 ${formatHandResult(handResult, colors2)}`;
8692
+ }
8693
+ var pokerStyles = {
8694
+ balanced: balancedStyle2,
8695
+ compact: balancedStyle2,
8696
+ playful: balancedStyle2,
8697
+ "compact-verbose": (data, colors2) => {
8698
+ const { holeCards, boardCards, handResult } = data;
8699
+ const participatingSet = new Set(handResult?.participatingIndices || []);
8700
+ const handStr = holeCards.map((hc, idx) => formatCardCompact(hc, participatingSet.has(idx))).join("");
8701
+ const boardStr = boardCards.map((bc, idx) => formatCardCompact(bc, participatingSet.has(idx + 2))).join("");
8702
+ const abbreviation = getHandAbbreviation(handResult);
8703
+ const result = `${handStr}| ${boardStr}\u2192 ${abbreviation}`;
8704
+ if (!colors2) return result;
8705
+ return colorize2(result, colors2.result);
7932
8706
  },
7933
- indicator: (data, colors2) => {
7934
- const { running, total } = data.status;
7935
- if (running === 0) {
7936
- const label2 = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7937
- return `\u25CF ${label2} --`;
7938
- }
7939
- const count = total > running ? `${running}/${total}` : `${running}`;
7940
- const label = colors2 ? colorize("Docker:", colors2.label) : "Docker:";
7941
- const coloredCount = colors2 ? colorize(count, colors2.count) : count;
7942
- return `\u25CF ${label} ${coloredCount}`;
8707
+ emoji: (data, colors2) => {
8708
+ const { holeCards, boardCards, handResult } = data;
8709
+ const participatingSet = new Set(handResult?.participatingIndices || []);
8710
+ const handStr = holeCards.map((hc, idx) => formatCardEmojiByParticipation(hc, participatingSet.has(idx))).join("");
8711
+ const boardStr = boardCards.map((bc, idx) => formatCardEmojiByParticipation(bc, participatingSet.has(idx + 2))).join("");
8712
+ const labelColor = colors2?.participating ?? lightGray;
8713
+ const handLabel = colorize2("Hand:", labelColor);
8714
+ const boardLabel = colorize2("Board:", labelColor);
8715
+ return `${handLabel} ${handStr}| ${boardLabel} ${boardStr}\u2192 ${formatHandResult(handResult, colors2)}`;
7943
8716
  }
7944
8717
  };
7945
8718
 
7946
- // src/widgets/docker/docker-widget.ts
7947
- var execFileAsync4 = (0, import_node_util5.promisify)(import_node_child_process4.execFile);
7948
- var DockerWidget = class {
7949
- id = "docker";
8719
+ // src/widgets/poker-widget.ts
8720
+ var PokerWidget = class extends StdinDataWidget {
8721
+ id = "poker";
7950
8722
  metadata = createWidgetMetadata(
7951
- "Docker",
7952
- "Shows Docker container count and status",
8723
+ "Poker",
8724
+ "Displays random Texas Hold'em hands for entertainment",
7953
8725
  "1.0.0",
7954
8726
  "claude-scope",
7955
- 0
8727
+ 4
8728
+ // Fifth line (0-indexed)
7956
8729
  );
7957
- enabled = true;
8730
+ holeCards = [];
8731
+ boardCards = [];
8732
+ handResult = null;
8733
+ lastUpdateTimestamp = 0;
8734
+ THROTTLE_MS = 5e3;
8735
+ // 5 seconds
7958
8736
  colors;
7959
8737
  _lineOverride;
7960
- styleFn = dockerStyles.balanced;
7961
- cachedStatus = null;
7962
- lastCheck = 0;
7963
- CACHE_TTL = 5e3;
7964
- constructor(colors2) {
7965
- this.colors = colors2 ?? DEFAULT_THEME;
7966
- }
7967
- setStyle(style = "balanced") {
7968
- const fn = dockerStyles[style];
8738
+ styleFn = pokerStyles.balanced;
8739
+ setStyle(style = DEFAULT_WIDGET_STYLE) {
8740
+ const fn = pokerStyles[style];
7969
8741
  if (fn) {
7970
8742
  this.styleFn = fn;
7971
8743
  }
@@ -7976,56 +8748,84 @@ var DockerWidget = class {
7976
8748
  getLine() {
7977
8749
  return this._lineOverride ?? this.metadata.line ?? 0;
7978
8750
  }
7979
- async initialize(context) {
7980
- this.enabled = context.config?.enabled !== false;
7981
- }
7982
- async update(_data) {
7983
- }
7984
- isEnabled() {
7985
- return this.enabled;
7986
- }
7987
- async cleanup() {
8751
+ constructor(colors2) {
8752
+ super();
8753
+ this.colors = colors2 ?? DEFAULT_THEME;
7988
8754
  }
7989
- async render(_context) {
7990
- if (!this.enabled) {
7991
- return null;
7992
- }
8755
+ /**
8756
+ * Generate new poker hand on each update
8757
+ */
8758
+ async update(data) {
8759
+ await super.update(data);
7993
8760
  const now = Date.now();
7994
- if (this.cachedStatus && now - this.lastCheck < this.CACHE_TTL) {
7995
- return this.styleFn({ status: this.cachedStatus }, this.colors.docker);
8761
+ if (this.lastUpdateTimestamp > 0 && now - this.lastUpdateTimestamp < this.THROTTLE_MS) {
8762
+ return;
7996
8763
  }
7997
- const status = await this.getDockerStatus();
7998
- this.cachedStatus = status;
7999
- this.lastCheck = now;
8000
- if (!status.isAvailable) {
8001
- return null;
8764
+ const deck = new Deck();
8765
+ const hole = [deck.deal(), deck.deal()];
8766
+ const board = [deck.deal(), deck.deal(), deck.deal(), deck.deal(), deck.deal()];
8767
+ const result = evaluateHand(hole, board);
8768
+ this.holeCards = hole.map((card) => ({
8769
+ card,
8770
+ formatted: this.formatCardColor(card)
8771
+ }));
8772
+ this.boardCards = board.map((card) => ({
8773
+ card,
8774
+ formatted: this.formatCardColor(card)
8775
+ }));
8776
+ const playerParticipates = result.participatingCards.some((idx) => idx < 2);
8777
+ if (!playerParticipates) {
8778
+ this.handResult = {
8779
+ text: `Nothing \u{1F0CF}`,
8780
+ participatingIndices: result.participatingCards
8781
+ };
8782
+ } else {
8783
+ this.handResult = {
8784
+ text: `${result.name}! ${result.emoji}`,
8785
+ participatingIndices: result.participatingCards
8786
+ };
8002
8787
  }
8003
- return this.styleFn({ status }, this.colors.docker);
8788
+ this.lastUpdateTimestamp = now;
8004
8789
  }
8005
- async getDockerStatus() {
8006
- try {
8007
- await execFileAsync4("docker", ["info"], { timeout: 2e3 });
8008
- const { stdout: runningOutput } = await execFileAsync4("docker", ["ps", "-q"], {
8009
- timeout: 1e3
8010
- });
8011
- const running = runningOutput.trim().split("\n").filter((line) => line).length;
8012
- const { stdout: allOutput } = await execFileAsync4("docker", ["ps", "-aq"], {
8013
- timeout: 1e3
8014
- });
8015
- const total = allOutput.trim().split("\n").filter((line) => line).length;
8016
- return { running, total, isAvailable: true };
8017
- } catch {
8018
- return { running: 0, total: 0, isAvailable: false };
8019
- }
8790
+ /**
8791
+ * Format card with appropriate color (red for ♥♦, gray for ♠♣)
8792
+ */
8793
+ formatCardColor(card) {
8794
+ const _color = isRedSuit(card.suit) ? "red" : "gray";
8795
+ return formatCard(card);
8796
+ }
8797
+ renderWithData(_data, _context) {
8798
+ const holeCardsData = this.holeCards.map((hc, idx) => ({
8799
+ card: hc.card,
8800
+ isParticipating: (this.handResult?.participatingIndices || []).includes(idx)
8801
+ }));
8802
+ const boardCardsData = this.boardCards.map((bc, idx) => ({
8803
+ card: bc.card,
8804
+ isParticipating: (this.handResult?.participatingIndices || []).includes(idx + 2)
8805
+ }));
8806
+ const handResult = this.handResult ? {
8807
+ name: this.getHandName(this.handResult.text),
8808
+ emoji: this.getHandEmoji(this.handResult.text),
8809
+ participatingIndices: this.handResult.participatingIndices
8810
+ } : null;
8811
+ const renderData = {
8812
+ holeCards: holeCardsData,
8813
+ boardCards: boardCardsData,
8814
+ handResult
8815
+ };
8816
+ return this.styleFn(renderData, this.colors.poker);
8817
+ }
8818
+ getHandName(text) {
8819
+ const match = text.match(/^([^!]+)/);
8820
+ return match ? match[1].trim() : "Nothing";
8821
+ }
8822
+ getHandEmoji(text) {
8823
+ const match = text.match(/([🃏♠️♥️♦️♣️🎉✨🌟])/u);
8824
+ return match ? match[1] : "\u{1F0CF}";
8020
8825
  }
8021
8826
  };
8022
8827
 
8023
8828
  // src/core/widget-factory.ts
8024
- init_duration_widget();
8025
- init_git_tag_widget();
8026
- init_git_widget();
8027
- init_lines_widget();
8028
- init_model_widget();
8029
8829
  var WidgetFactory = class {
8030
8830
  transcriptProvider;
8031
8831
  constructor() {
@@ -8062,6 +8862,10 @@ var WidgetFactory = class {
8062
8862
  return new DevServerWidget(DEFAULT_THEME);
8063
8863
  case "docker":
8064
8864
  return new DockerWidget(DEFAULT_THEME);
8865
+ case "poker":
8866
+ return new PokerWidget(DEFAULT_THEME);
8867
+ case "empty-line":
8868
+ return new EmptyLineWidget();
8065
8869
  default:
8066
8870
  return null;
8067
8871
  }
@@ -8082,7 +8886,9 @@ var WidgetFactory = class {
8082
8886
  "cache-metrics",
8083
8887
  "active-tools",
8084
8888
  "dev-server",
8085
- "docker"
8889
+ "docker",
8890
+ "poker",
8891
+ "empty-line"
8086
8892
  ];
8087
8893
  }
8088
8894
  };