@scotthamilton77/sidekick 0.1.6 → 0.1.7

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.
@@ -17,30 +17,51 @@ settings:
17
17
  # {tokenUsageEffective} - Current tokens + compaction buffer (e.g., "90k")
18
18
  # {tokenPercentageActual} - Actual usage as % of context window (e.g., "22%")
19
19
  # {tokenPercentageEffective}- Effective usage as % of context window (e.g., "45%")
20
+ # Note: After context compaction, token metrics display "⟳ compacted" or "⟳"
21
+ # until fresh data arrives from the next status update.
20
22
  # {logs} - Warning/error counts (e.g., "⚠0 ✗0")
21
23
  # {cost} - Session cost (e.g., "$0.15")
22
24
  # {duration} - Session duration (e.g., "12m")
23
- # {cwd} - Current working directory
24
- # {branch} - Git branch with icon (e.g., "⎇ main")
25
+ # {cwd} - Current working directory (home-shortened)
26
+ # {branch} - Git branch name (raw, use prefix for icon)
27
+ # {projectDirShort} - Project directory basename (e.g., "claude-code-sidekick")
28
+ # {projectDirFull} - Project directory full path (home-shortened)
29
+ # {worktreeName} - Worktree name (empty if not in worktree)
30
+ # {worktreeOrBranch} - Worktree name if in worktree, else branch name
25
31
  # {title} - Session title
26
32
  # {summary} - Session summary/intent
27
33
  # {personaName} - Current persona name (empty if disabled or no persona)
28
34
  #
29
- # Conditional prefix/suffix syntax:
35
+ # Token attributes:
30
36
  # {token,prefix='...',suffix='...'}
31
37
  # - prefix and suffix only render when the token value is non-empty
32
38
  # - Either or both can be specified
33
39
  # - Use backslash to escape quotes: \'
34
40
  # - Example: {personaName,prefix='[',suffix='] | '} → "[jarvis] | " or "" (if empty)
35
41
  #
42
+ # {token,maxLength=N} - Maximum visible character width
43
+ # {token,maxLength=N,truncateStyle='...'} - Truncation strategy (default: suffix)
44
+ # - 'suffix': Right-truncate with trailing ellipsis (e.g., "claude-co…")
45
+ # - 'prefix': Left-truncate with leading ellipsis (e.g., "…-sidekick")
46
+ # - 'path': Path-aware truncation (e.g., "project/…/src")
47
+ #
48
+ # {token,wrapAt=N,prefix='...',wrapPrefix='...',wrapSuffix='...'}
49
+ # - wrapAt=N: Line width threshold for responsive wrapping
50
+ # - wrapPrefix: Alternative prefix used when line exceeds wrapAt (default: prefix)
51
+ # - wrapSuffix: Alternative suffix used when line exceeds wrapAt (default: suffix)
52
+ # - Example: {title,wrapAt=80,prefix=' | ',wrapPrefix='\n'} wraps to newline if >80 cols
53
+ #
36
54
  # Example configurations:
37
55
  # Simple ratio: "{tokenUsageActual}/{contextWindow}" → "45k/200k"
38
56
  # Percentage only: "{tokenPercentageActual}" → "22%"
39
57
  # Bar + percent: "{contextBar} {tokenPercentageActual}" → "🪙 ▓▓▒|░░░ 22%"
40
58
  # With buffer: "{tokenUsageActual}|{tokenUsageEffective}" → "45k|90k"
41
59
  # Verbose: "{contextBar} {tokenUsageActual}/{contextWindow} ({tokenPercentageEffective})"
60
+ # Worktree-aware: "{worktreeOrBranch,prefix=' ∗ '}" → " ∗ my-worktree" or " ∗ main"
61
+ # Project name: "{projectDirShort}" → "claude-code-sidekick"
62
+ # Worktree badge: "{worktreeName,prefix=' [wt:',suffix=']'}" → " [wt:feature-x]" or ""
42
63
  #
43
- format: "{personaName,prefix='[',suffix='] | '}{model,prefix='[',suffix='] | '}{contextBar} {tokenPercentageActual} | {logs} | {cwd}{branch} | {title}\n{summary}"
64
+ format: "{personaName,prefix='[',suffix='] | '}{model,prefix='[',suffix='] | '}{contextBar} {tokenPercentageActual} | {logs} | {projectDirShort,maxLength=40}: {cwd,maxLength=40,truncateStyle='path'}{worktreeOrBranch,prefix=' ∗ ',maxLength=40}{title,wrapAt=120,prefix=' | ',wrapPrefix='\\n'}{summary,wrapAt=120,prefix=' | ',wrapPrefix='\\n'}"
44
65
 
45
66
  # Threshold configuration for visual warnings
46
67
  thresholds:
@@ -86,7 +107,7 @@ settings:
86
107
  colors:
87
108
  model: blue
88
109
  tokens: green
89
- title: blue
110
+ title: cyan
90
111
  summary: magenta
91
112
  cwd: white
92
113
  duration: white
package/dist/bin.js CHANGED
@@ -56904,8 +56904,7 @@ var require_gitignore = __commonJS({
56904
56904
  ".sidekick/.env.local",
56905
56905
  ".sidekick/sidekick*.pid",
56906
56906
  ".sidekick/sidekick*.token",
56907
- ".sidekick/*.local.yaml",
56908
- ".sidekick/features.yaml"
56907
+ ".sidekick/*.local.yaml"
56909
56908
  ];
56910
56909
  async function installGitignoreSection(projectDir) {
56911
56910
  const gitignorePath = path.join(projectDir, ".gitignore");
@@ -71472,23 +71471,25 @@ var require_stage_default_user_prompt = __commonJS({
71472
71471
  });
71473
71472
  if (!(0, types_1.isDaemonContext)(context))
71474
71473
  return;
71475
- const daemonCtx = context;
71476
71474
  context.handlers.register({
71477
71475
  id: "reminders:throttle-register-ups-session-start",
71478
71476
  priority: 49,
71479
71477
  filter: { kind: "hook", hooks: ["SessionStart"] },
71480
- handler: async (event) => {
71478
+ handler: async (event, ctx) => {
71481
71479
  if (!(0, types_1.isHookEvent)(event) || !(0, types_1.isSessionStartEvent)(event))
71482
71480
  return;
71481
+ if (!(0, types_1.isDaemonContext)(ctx))
71482
+ return;
71483
+ const handlerCtx = ctx;
71483
71484
  const sessionId = event.context.sessionId;
71484
71485
  if (!sessionId)
71485
71486
  return;
71486
71487
  const reminder = (0, reminder_utils_js_1.resolveReminder)(types_js_1.ReminderIds.USER_PROMPT_SUBMIT, {
71487
71488
  context: { sessionId },
71488
- assets: daemonCtx.assets
71489
+ assets: handlerCtx.assets
71489
71490
  });
71490
71491
  if (reminder) {
71491
- await (0, throttle_utils_js_1.registerThrottledReminder)(daemonCtx, sessionId, types_js_1.ReminderIds.USER_PROMPT_SUBMIT, "UserPromptSubmit", reminder);
71492
+ await (0, throttle_utils_js_1.registerThrottledReminder)(handlerCtx, sessionId, types_js_1.ReminderIds.USER_PROMPT_SUBMIT, "UserPromptSubmit", reminder);
71492
71493
  }
71493
71494
  }
71494
71495
  });
@@ -71496,48 +71497,57 @@ var require_stage_default_user_prompt = __commonJS({
71496
71497
  id: "reminders:throttle-reset-session-start",
71497
71498
  priority: 48,
71498
71499
  filter: { kind: "hook", hooks: ["SessionStart"] },
71499
- handler: async (event) => {
71500
+ handler: async (event, ctx) => {
71500
71501
  if (!(0, types_1.isHookEvent)(event) || !(0, types_1.isSessionStartEvent)(event))
71501
71502
  return;
71503
+ if (!(0, types_1.isDaemonContext)(ctx))
71504
+ return;
71505
+ const handlerCtx = ctx;
71502
71506
  const sessionId = event.context.sessionId;
71503
71507
  if (!sessionId)
71504
71508
  return;
71505
- await (0, throttle_utils_js_1.resetThrottleCounters)(daemonCtx, sessionId);
71509
+ await (0, throttle_utils_js_1.resetThrottleCounters)(handlerCtx, sessionId);
71506
71510
  }
71507
71511
  });
71508
71512
  context.handlers.register({
71509
71513
  id: "reminders:throttle-reset-bulk",
71510
71514
  priority: 49,
71511
71515
  filter: { kind: "transcript", eventTypes: ["BulkProcessingComplete"] },
71512
- handler: async (event) => {
71516
+ handler: async (event, ctx) => {
71513
71517
  if (!(0, types_1.isTranscriptEvent)(event))
71514
71518
  return;
71515
71519
  if (event.metadata.isBulkProcessing)
71516
71520
  return;
71521
+ if (!(0, types_1.isDaemonContext)(ctx))
71522
+ return;
71523
+ const handlerCtx = ctx;
71517
71524
  const sessionId = event.context?.sessionId;
71518
71525
  if (!sessionId)
71519
71526
  return;
71520
- await (0, throttle_utils_js_1.resetThrottleCounters)(daemonCtx, sessionId);
71527
+ await (0, throttle_utils_js_1.resetThrottleCounters)(handlerCtx, sessionId);
71521
71528
  }
71522
71529
  });
71523
71530
  context.handlers.register({
71524
71531
  id: "reminders:throttle-restage",
71525
71532
  priority: 50,
71526
71533
  filter: { kind: "transcript", eventTypes: ["UserPrompt", "AssistantMessage"] },
71527
- handler: async (event) => {
71534
+ handler: async (event, ctx) => {
71528
71535
  if (!(0, types_1.isTranscriptEvent)(event))
71529
71536
  return;
71530
71537
  if (event.metadata.isBulkProcessing)
71531
71538
  return;
71539
+ if (!(0, types_1.isDaemonContext)(ctx))
71540
+ return;
71541
+ const handlerCtx = ctx;
71532
71542
  const sessionId = event.context?.sessionId;
71533
71543
  if (!sessionId)
71534
71544
  return;
71535
- const remindersState = (0, state_js_1.createRemindersState)(daemonCtx.stateService);
71545
+ const remindersState = (0, state_js_1.createRemindersState)(handlerCtx.stateService);
71536
71546
  const result = await remindersState.reminderThrottle.read(sessionId);
71537
71547
  const state = { ...result.data };
71538
71548
  if (Object.keys(state).length === 0)
71539
71549
  return;
71540
- const featureConfig = daemonCtx.config.getFeature("reminders");
71550
+ const featureConfig = handlerCtx.config.getFeature("reminders");
71541
71551
  const config = { ...types_js_1.DEFAULT_REMINDERS_SETTINGS, ...featureConfig.settings };
71542
71552
  const thresholds = config.reminder_thresholds ?? {};
71543
71553
  let changed = false;
@@ -71555,12 +71565,12 @@ var require_stage_default_user_prompt = __commonJS({
71555
71565
  toolsThisTurn: metrics.toolsThisTurn,
71556
71566
  toolCount: metrics.toolCount
71557
71567
  };
71558
- await (0, reminder_utils_js_1.stageReminder)(daemonCtx, typedEntry.targetHook, {
71568
+ await (0, reminder_utils_js_1.stageReminder)(handlerCtx, typedEntry.targetHook, {
71559
71569
  ...typedEntry.cachedReminder,
71560
71570
  stagedAt
71561
71571
  });
71562
71572
  state[reminderId] = { ...typedEntry, messagesSinceLastStaging: 0 };
71563
- daemonCtx.logger.debug("Throttle: re-staged reminder", {
71573
+ handlerCtx.logger.debug("Throttle: re-staged reminder", {
71564
71574
  sessionId,
71565
71575
  reminderId,
71566
71576
  messageCount: newCount,
@@ -75588,7 +75598,7 @@ var require_types3 = __commonJS({
75588
75598
  });
75589
75599
  exports2.DEFAULT_STATUSLINE_CONFIG = {
75590
75600
  enabled: true,
75591
- format: "[{model}] | {contextBar} {tokenPercentageActual} | {logs} | {cwd}{branch}\n{title} | {summary}",
75601
+ format: "{personaName,prefix='[',suffix='] | '}{model,prefix='[',suffix='] | '}{contextBar} {tokenPercentageActual} | {logs} | {cwd,maxLength=40,truncateStyle='path'}{branch,prefix=' \u2217 ',maxLength=40}{title,wrapAt=80,prefix=' | ',wrapPrefix='\\n'}\n{summary}",
75592
75602
  thresholds: {
75593
75603
  tokens: { warning: 1e5, critical: 16e4 },
75594
75604
  cost: { warning: 0.5, critical: 1 },
@@ -75600,7 +75610,7 @@ var require_types3 = __commonJS({
75600
75610
  colors: {
75601
75611
  model: "blue",
75602
75612
  tokens: "green",
75603
- title: "blue",
75613
+ title: "cyan",
75604
75614
  summary: "magenta",
75605
75615
  cwd: "white",
75606
75616
  duration: "white"
@@ -75696,6 +75706,76 @@ var require_types3 = __commonJS({
75696
75706
  }
75697
75707
  });
75698
75708
 
75709
+ // ../feature-statusline/dist/ansi-utils.js
75710
+ var require_ansi_utils = __commonJS({
75711
+ "../feature-statusline/dist/ansi-utils.js"(exports2) {
75712
+ "use strict";
75713
+ Object.defineProperty(exports2, "__esModule", { value: true });
75714
+ exports2.stripAnsi = stripAnsi;
75715
+ exports2.visibleLength = visibleLength;
75716
+ var ANSI_REGEX = /\x1b\[[0-9;?]*[0-9A-PR-TZcf-nqry=><~]|\x1b\][^\x07]*\x07/g;
75717
+ function stripAnsi(str) {
75718
+ return str.replace(ANSI_REGEX, "");
75719
+ }
75720
+ function visibleLength(str) {
75721
+ return stripAnsi(str).length;
75722
+ }
75723
+ }
75724
+ });
75725
+
75726
+ // ../feature-statusline/dist/truncation.js
75727
+ var require_truncation = __commonJS({
75728
+ "../feature-statusline/dist/truncation.js"(exports2) {
75729
+ "use strict";
75730
+ Object.defineProperty(exports2, "__esModule", { value: true });
75731
+ exports2.truncateSuffix = truncateSuffix;
75732
+ exports2.truncatePrefix = truncatePrefix;
75733
+ exports2.truncatePath = truncatePath;
75734
+ var ansi_utils_js_1 = require_ansi_utils();
75735
+ function truncateSuffix(str, maxLength) {
75736
+ if ((0, ansi_utils_js_1.visibleLength)(str) <= maxLength)
75737
+ return str;
75738
+ const plain = (0, ansi_utils_js_1.stripAnsi)(str);
75739
+ if (maxLength <= 1)
75740
+ return "\u2026";
75741
+ return plain.slice(0, maxLength - 1) + "\u2026";
75742
+ }
75743
+ function truncatePrefix(str, maxLength) {
75744
+ if ((0, ansi_utils_js_1.visibleLength)(str) <= maxLength)
75745
+ return str;
75746
+ const plain = (0, ansi_utils_js_1.stripAnsi)(str);
75747
+ if (maxLength <= 1)
75748
+ return "\u2026";
75749
+ return "\u2026" + plain.slice(-(maxLength - 1));
75750
+ }
75751
+ function truncatePath(str, maxLength) {
75752
+ const plain = (0, ansi_utils_js_1.stripAnsi)(str);
75753
+ if (plain.length <= maxLength)
75754
+ return str;
75755
+ const parts = plain.split("/");
75756
+ if (parts.length === 1) {
75757
+ return truncatePrefix(plain, maxLength);
75758
+ }
75759
+ if (parts.length === 2) {
75760
+ return truncatePrefix(plain, maxLength);
75761
+ }
75762
+ const first = parts[0];
75763
+ const last = parts[parts.length - 1];
75764
+ const candidate = `${first}/\u2026/${last}`;
75765
+ if (candidate.length <= maxLength) {
75766
+ return candidate;
75767
+ }
75768
+ const fixedPart = `/\u2026/${last}`;
75769
+ const availableForFirst = maxLength - fixedPart.length;
75770
+ if (availableForFirst <= 1) {
75771
+ return truncatePrefix(plain, maxLength);
75772
+ }
75773
+ const truncatedFirst = truncatePrefix(first, availableForFirst);
75774
+ return `${truncatedFirst}${fixedPart}`;
75775
+ }
75776
+ }
75777
+ });
75778
+
75699
75779
  // ../feature-session-summary/dist/state.js
75700
75780
  var require_state4 = __commonJS({
75701
75781
  "../feature-session-summary/dist/state.js"(exports2) {
@@ -77352,7 +77432,6 @@ var require_formatter = __commonJS({
77352
77432
  exports2.formatTokens = formatTokens;
77353
77433
  exports2.formatCost = formatCost;
77354
77434
  exports2.formatDuration = formatDuration;
77355
- exports2.shortenPath = shortenPath;
77356
77435
  exports2.formatCwd = formatCwd;
77357
77436
  exports2.formatBranch = formatBranch;
77358
77437
  exports2.formatLogs = formatLogs;
@@ -77362,6 +77441,8 @@ var require_formatter = __commonJS({
77362
77441
  exports2.formatContextBar = formatContextBar;
77363
77442
  exports2.getContextBarStatus = getContextBarStatus;
77364
77443
  exports2.calculateContextUsage = calculateContextUsage;
77444
+ var ansi_utils_js_1 = require_ansi_utils();
77445
+ var truncation_js_1 = require_truncation();
77365
77446
  var types_js_1 = require_types3();
77366
77447
  var ANSI = {
77367
77448
  reset: "\x1B[0m",
@@ -77403,6 +77484,59 @@ var require_formatter = __commonJS({
77403
77484
  const colorName = name;
77404
77485
  return ANSI[colorName] ?? "";
77405
77486
  }
77487
+ function parseTokenOptions(optionsStr) {
77488
+ if (!optionsStr)
77489
+ return {};
77490
+ const options = {};
77491
+ const stringPattern = /(\w+)='((?:[^'\\]|\\.)*)'/g;
77492
+ let match;
77493
+ while ((match = stringPattern.exec(optionsStr)) !== null) {
77494
+ const key = match[1];
77495
+ const val = match[2].replace(/\\'/g, "'").replace(/\\\\/g, "\\");
77496
+ switch (key) {
77497
+ case "prefix":
77498
+ options.prefix = val;
77499
+ break;
77500
+ case "suffix":
77501
+ options.suffix = val;
77502
+ break;
77503
+ case "truncateStyle":
77504
+ options.truncateStyle = val;
77505
+ break;
77506
+ case "wrapPrefix":
77507
+ options.wrapPrefix = val;
77508
+ break;
77509
+ case "wrapSuffix":
77510
+ options.wrapSuffix = val;
77511
+ break;
77512
+ }
77513
+ }
77514
+ const numPattern = /(\w+)=(\d+)(?=[,}]|$)/g;
77515
+ while ((match = numPattern.exec(optionsStr)) !== null) {
77516
+ const key = match[1];
77517
+ const val = parseInt(match[2], 10);
77518
+ switch (key) {
77519
+ case "maxLength":
77520
+ options.maxLength = val;
77521
+ break;
77522
+ case "wrapAt":
77523
+ options.wrapAt = val;
77524
+ break;
77525
+ }
77526
+ }
77527
+ return options;
77528
+ }
77529
+ function applyTruncation(value, maxLength, style) {
77530
+ switch (style) {
77531
+ case "prefix":
77532
+ return (0, truncation_js_1.truncatePrefix)(value, maxLength);
77533
+ case "path":
77534
+ return (0, truncation_js_1.truncatePath)(value, maxLength);
77535
+ case "suffix":
77536
+ default:
77537
+ return (0, truncation_js_1.truncateSuffix)(value, maxLength);
77538
+ }
77539
+ }
77406
77540
  var Formatter = class {
77407
77541
  theme;
77408
77542
  useColors;
@@ -77418,50 +77552,121 @@ var require_formatter = __commonJS({
77418
77552
  * (e.g., a snarky comment containing "|") are preserved.
77419
77553
  */
77420
77554
  format(template, viewModel) {
77555
+ template = template.replace(/\\n/g, "\n");
77421
77556
  const branchColor = this.theme.colors.branch ?? viewModel.branchColor;
77422
77557
  const symbolMode = (0, types_js_1.normalizeSymbolMode)(this.theme.useNerdFonts);
77423
77558
  const logsText = formatLogs(viewModel.warningCount, viewModel.errorCount, symbolMode);
77424
77559
  const convertedSummary = this.convertMarkdown(viewModel.summary);
77425
77560
  const convertedTitle = this.convertMarkdown(viewModel.title);
77426
- const tokens = {
77427
- model: this.colorize(viewModel.model, this.theme.colors.model),
77428
- // Atomic token placeholders - each can be used independently in templates
77429
- contextWindow: this.colorize(viewModel.contextWindow, this.theme.colors.tokens),
77430
- tokenUsageActual: this.colorizeByStatus(viewModel.tokenUsageActual, viewModel.tokensStatus),
77431
- tokenUsageEffective: this.colorizeByStatus(viewModel.tokenUsageEffective, viewModel.tokensStatus),
77432
- tokenPercentageActual: this.colorizeByStatus(viewModel.tokenPercentageActual, viewModel.tokensStatus),
77433
- tokenPercentageEffective: this.colorizeByStatus(viewModel.tokenPercentageEffective, viewModel.tokensStatus),
77434
- cost: this.colorizeByStatus(viewModel.cost, viewModel.costStatus),
77435
- duration: this.colorize(viewModel.duration, this.theme.colors.duration),
77436
- cwd: this.colorize(viewModel.cwd, this.theme.colors.cwd),
77437
- branch: viewModel.branch ? ` ${this.colorize(viewModel.branch, branchColor)}` : "",
77438
- summary: this.colorize(convertedSummary, this.theme.colors.summary),
77439
- title: this.colorize(convertedTitle, this.theme.colors.title),
77440
- contextBar: formatContextBar(viewModel.contextUsage, this.useColors, symbolMode),
77441
- logs: this.colorizeByStatus(logsText, viewModel.logStatus),
77442
- // Persona name token - empty string when no persona or disabled
77443
- personaName: viewModel.personaName ? this.colorize(viewModel.personaName, this.theme.colors.persona ?? "cyan") : ""
77561
+ const rawTokens = {
77562
+ model: viewModel.model,
77563
+ contextWindow: viewModel.contextWindow,
77564
+ tokenUsageActual: viewModel.tokenUsageActual,
77565
+ tokenUsageEffective: viewModel.tokenUsageEffective,
77566
+ tokenPercentageActual: viewModel.tokenPercentageActual,
77567
+ tokenPercentageEffective: viewModel.tokenPercentageEffective,
77568
+ cost: viewModel.cost,
77569
+ duration: viewModel.duration,
77570
+ cwd: viewModel.cwd,
77571
+ branch: viewModel.branch,
77572
+ projectDirShort: viewModel.projectDirShort,
77573
+ projectDirFull: viewModel.projectDirFull,
77574
+ worktreeName: viewModel.worktreeName,
77575
+ worktreeOrBranch: viewModel.worktreeOrBranch,
77576
+ summary: convertedSummary,
77577
+ title: convertedTitle,
77578
+ logs: logsText,
77579
+ personaName: viewModel.personaName,
77580
+ // Pre-formatted tokens (already contain ANSI if colors enabled)
77581
+ contextBar: formatContextBar(viewModel.contextUsage, this.useColors, symbolMode)
77582
+ };
77583
+ const personaColor = this.theme.colors.persona ?? "cyan";
77584
+ const colorizeToken = (tokenName, value) => {
77585
+ switch (tokenName) {
77586
+ case "tokenUsageActual":
77587
+ case "tokenUsageEffective":
77588
+ case "tokenPercentageActual":
77589
+ case "tokenPercentageEffective":
77590
+ return this.colorizeByStatus(value, viewModel.tokensStatus);
77591
+ case "cost":
77592
+ return this.colorizeByStatus(value, viewModel.costStatus);
77593
+ case "logs":
77594
+ return this.colorizeByStatus(value, viewModel.logStatus);
77595
+ case "model":
77596
+ return this.colorize(value, this.theme.colors.model);
77597
+ case "contextWindow":
77598
+ return this.colorize(value, this.theme.colors.tokens);
77599
+ case "duration":
77600
+ return this.colorize(value, this.theme.colors.duration);
77601
+ case "cwd":
77602
+ case "projectDirShort":
77603
+ case "projectDirFull":
77604
+ return this.colorize(value, this.theme.colors.cwd);
77605
+ case "branch":
77606
+ case "worktreeOrBranch":
77607
+ case "worktreeName":
77608
+ return this.colorize(value, branchColor);
77609
+ case "summary":
77610
+ return this.colorize(value, this.theme.colors.summary);
77611
+ case "title":
77612
+ return this.colorize(value, this.theme.colors.title);
77613
+ case "personaName":
77614
+ return this.colorize(value, personaColor);
77615
+ case "contextBar":
77616
+ return value;
77617
+ // already formatted with colors
77618
+ default:
77619
+ return value;
77620
+ }
77444
77621
  };
77445
77622
  const EMPTY_MARKER = "\0EMPTY\0";
77446
- let result = template.replace(/\{(\w+)(?:,([^}]*))?\}/g, (_match, tokenName, optionsStr) => {
77447
- const value = tokens[tokenName] ?? "";
77448
- let prefix = "";
77449
- let suffix = "";
77450
- if (optionsStr) {
77451
- const prefixMatch = optionsStr.match(/prefix='((?:[^'\\]|\\.)*)'/);
77452
- if (prefixMatch) {
77453
- prefix = prefixMatch[1].replace(/\\'/g, "'").replace(/\\\\/g, "\\");
77454
- }
77455
- const suffixMatch = optionsStr.match(/suffix='((?:[^'\\]|\\.)*)'/);
77456
- if (suffixMatch) {
77457
- suffix = suffixMatch[1].replace(/\\'/g, "'").replace(/\\\\/g, "\\");
77458
- }
77623
+ let currentLineWidth = 0;
77624
+ const TOKEN_REGEX = /\{(\w+)(?:,([^}]*))?\}/g;
77625
+ let result = "";
77626
+ let lastIndex = 0;
77627
+ let match;
77628
+ while ((match = TOKEN_REGEX.exec(template)) !== null) {
77629
+ const literal = template.slice(lastIndex, match.index);
77630
+ result += literal;
77631
+ const literalNewline = literal.lastIndexOf("\n");
77632
+ if (literalNewline >= 0) {
77633
+ currentLineWidth = (0, ansi_utils_js_1.visibleLength)(literal.slice(literalNewline + 1));
77634
+ } else {
77635
+ currentLineWidth += (0, ansi_utils_js_1.visibleLength)(literal);
77459
77636
  }
77637
+ const tokenName = match[1];
77638
+ const optionsStr = match[2];
77639
+ let value = rawTokens[tokenName] ?? "";
77460
77640
  if (!value) {
77461
- return EMPTY_MARKER;
77641
+ result += EMPTY_MARKER;
77642
+ lastIndex = TOKEN_REGEX.lastIndex;
77643
+ continue;
77462
77644
  }
77463
- return prefix + value + suffix;
77464
- });
77645
+ const options = parseTokenOptions(optionsStr);
77646
+ if (options.maxLength !== void 0 && tokenName !== "contextBar") {
77647
+ value = applyTruncation(value, options.maxLength, options.truncateStyle ?? "suffix");
77648
+ }
77649
+ const colorized = colorizeToken(tokenName, value);
77650
+ let prefix = options.prefix ?? "";
77651
+ let suffix = options.suffix ?? "";
77652
+ if (options.wrapAt !== void 0) {
77653
+ const candidateWidth = currentLineWidth + (0, ansi_utils_js_1.visibleLength)(prefix) + (0, ansi_utils_js_1.visibleLength)(value);
77654
+ if (candidateWidth > options.wrapAt) {
77655
+ prefix = options.wrapPrefix ?? prefix;
77656
+ suffix = options.wrapSuffix ?? suffix;
77657
+ }
77658
+ }
77659
+ const segment = prefix + colorized + suffix;
77660
+ const lastNewlineInSegment = segment.lastIndexOf("\n");
77661
+ if (lastNewlineInSegment >= 0) {
77662
+ currentLineWidth = (0, ansi_utils_js_1.visibleLength)(segment.slice(lastNewlineInSegment + 1));
77663
+ } else {
77664
+ currentLineWidth += (0, ansi_utils_js_1.visibleLength)(segment);
77665
+ }
77666
+ result += segment;
77667
+ lastIndex = TOKEN_REGEX.lastIndex;
77668
+ }
77669
+ result += template.slice(lastIndex);
77465
77670
  result = result.replace(new RegExp(`\\s*[|\\n]\\s*${EMPTY_MARKER}\\s*[|\\n]\\s*`, "g"), " | ");
77466
77671
  result = result.replace(new RegExp(`^${EMPTY_MARKER}\\s*[|\\n]\\s*`), "");
77467
77672
  result = result.replace(new RegExp(`\\s*[|\\n]\\s*${EMPTY_MARKER}$`), "");
@@ -77538,41 +77743,14 @@ var require_formatter = __commonJS({
77538
77743
  }
77539
77744
  return `${seconds}s`;
77540
77745
  }
77541
- function shortenPath(fullPath, homeDir) {
77542
- const MAX_LENGTH = 20;
77543
- let path = fullPath;
77544
- if (homeDir && path.startsWith(homeDir)) {
77545
- path = "~" + path.slice(homeDir.length);
77546
- }
77547
- if (path.length <= MAX_LENGTH) {
77548
- return path;
77549
- }
77550
- const parts = path.split("/");
77551
- if (parts.length >= 2) {
77552
- let shortened = "\u2026/" + parts.slice(-2).join("/");
77553
- if (shortened.length > MAX_LENGTH) {
77554
- shortened = "\u2026/" + parts.slice(-1)[0];
77555
- }
77556
- if (shortened.length > MAX_LENGTH) {
77557
- return shortened.slice(0, MAX_LENGTH);
77558
- }
77559
- return shortened;
77746
+ function formatCwd(fullPath, homeDir) {
77747
+ if (homeDir && fullPath.startsWith(homeDir)) {
77748
+ return "~" + fullPath.slice(homeDir.length);
77560
77749
  }
77561
- return "\u2026" + path.slice(-(MAX_LENGTH - 1));
77750
+ return fullPath;
77562
77751
  }
77563
- function formatCwd(fullPath, homeDir, symbolMode = "full") {
77564
- const icon = symbolMode === "full" ? "\u{1F4C1} " : "";
77565
- return `${icon}${shortenPath(fullPath, homeDir)}`;
77566
- }
77567
- function formatBranch(branch, symbolMode) {
77568
- if (!branch)
77569
- return "";
77570
- const icons = {
77571
- full: "\u2387",
77572
- safe: "\u2217",
77573
- ascii: "*"
77574
- };
77575
- return `${icons[symbolMode]} ${branch}`;
77752
+ function formatBranch(branch) {
77753
+ return branch;
77576
77754
  }
77577
77755
  function formatLogs(warningCount, errorCount, symbolMode = "full") {
77578
77756
  const symbols = {
@@ -77762,11 +77940,49 @@ var require_context_overhead_reader = __commonJS({
77762
77940
  var require_statusline_service = __commonJS({
77763
77941
  "../feature-statusline/dist/statusline-service.js"(exports2) {
77764
77942
  "use strict";
77943
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
77944
+ if (k2 === void 0) k2 = k;
77945
+ var desc = Object.getOwnPropertyDescriptor(m, k);
77946
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
77947
+ desc = { enumerable: true, get: function() {
77948
+ return m[k];
77949
+ } };
77950
+ }
77951
+ Object.defineProperty(o, k2, desc);
77952
+ }) : (function(o, m, k, k2) {
77953
+ if (k2 === void 0) k2 = k;
77954
+ o[k2] = m[k];
77955
+ }));
77956
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
77957
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
77958
+ }) : function(o, v) {
77959
+ o["default"] = v;
77960
+ });
77961
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
77962
+ var ownKeys = function(o) {
77963
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
77964
+ var ar = [];
77965
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
77966
+ return ar;
77967
+ };
77968
+ return ownKeys(o);
77969
+ };
77970
+ return function(mod) {
77971
+ if (mod && mod.__esModule) return mod;
77972
+ var result = {};
77973
+ if (mod != null) {
77974
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
77975
+ }
77976
+ __setModuleDefault(result, mod);
77977
+ return result;
77978
+ };
77979
+ })();
77765
77980
  Object.defineProperty(exports2, "__esModule", { value: true });
77766
77981
  exports2.StatuslineService = void 0;
77767
77982
  exports2.createStatuslineService = createStatuslineService;
77768
77983
  exports2.deterministicIndex = deterministicIndex;
77769
77984
  var core_1 = require_dist4();
77985
+ var path = __importStar(require("node:path"));
77770
77986
  var formatter_js_1 = require_formatter();
77771
77987
  var git_provider_js_1 = require_git_provider();
77772
77988
  var state_reader_js_1 = require_state_reader();
@@ -77788,6 +78004,10 @@ var require_statusline_service = __commonJS({
77788
78004
  branchColor: "",
77789
78005
  displayMode: "setup_warning",
77790
78006
  title: "",
78007
+ projectDirShort: "",
78008
+ projectDirFull: "",
78009
+ worktreeName: "",
78010
+ worktreeOrBranch: "",
77791
78011
  warningCount: 0,
77792
78012
  errorCount: 0,
77793
78013
  logStatus: "normal",
@@ -78186,6 +78406,10 @@ var require_statusline_service = __commonJS({
78186
78406
  const effectivePercentage = Math.round(totalWithBuffer / contextWindowSize * 100);
78187
78407
  const logStatus = logMetrics.errorCount >= this.config.thresholds.logs.critical ? "critical" : logMetrics.warningCount >= this.config.thresholds.logs.warning ? "warning" : "normal";
78188
78408
  const personaName = persona && persona.id !== "disabled" ? persona.display_name : "";
78409
+ const worktree = this.hookInput?.worktree;
78410
+ const projectRoot = worktree?.original_cwd || this.hookInput?.workspace?.project_dir || this.cwd;
78411
+ const projectDirShort = path.basename(projectRoot);
78412
+ const homeShorten = (p) => this.homeDir && p.startsWith(this.homeDir) ? "~" + p.slice(this.homeDir.length) : p;
78189
78413
  return {
78190
78414
  model: this.formatModelName(modelName),
78191
78415
  contextWindow: (0, formatter_js_1.formatTokens)(contextWindowSize),
@@ -78197,9 +78421,13 @@ var require_statusline_service = __commonJS({
78197
78421
  cost: (0, formatter_js_1.formatCost)(costUsd),
78198
78422
  costStatus: (0, formatter_js_1.getThresholdStatus)(costUsd, this.config.thresholds.cost),
78199
78423
  duration: (0, formatter_js_1.formatDuration)(durationMs),
78200
- cwd: (0, formatter_js_1.formatCwd)(this.cwd, this.homeDir, (0, types_js_1.normalizeSymbolMode)(this.config.theme.useNerdFonts)),
78201
- branch: (0, formatter_js_1.formatBranch)(branch, (0, types_js_1.normalizeSymbolMode)(this.config.theme.useNerdFonts)),
78424
+ cwd: (0, formatter_js_1.formatCwd)(this.cwd, this.homeDir),
78425
+ branch: (0, formatter_js_1.formatBranch)(branch),
78202
78426
  branchColor: (0, formatter_js_1.getBranchColor)(branch),
78427
+ projectDirShort,
78428
+ projectDirFull: homeShorten(projectRoot),
78429
+ worktreeName: worktree?.name ?? "",
78430
+ worktreeOrBranch: worktree?.name ?? branch,
78203
78431
  displayMode,
78204
78432
  summary: summaryText,
78205
78433
  title,
@@ -78322,8 +78550,25 @@ var require_dist7 = __commonJS({
78322
78550
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
78323
78551
  };
78324
78552
  Object.defineProperty(exports2, "__esModule", { value: true });
78325
- exports2.getDefaultOverhead = exports2.readContextOverhead = exports2.createStatuslineService = exports2.StatuslineService = exports2.getThresholdStatus = exports2.formatBranch = exports2.shortenPath = exports2.formatDuration = exports2.formatCost = exports2.formatTokens = exports2.createFormatter = exports2.Formatter = exports2.createGitProvider = exports2.GitProvider = exports2.createStateReader = exports2.StateReader = void 0;
78553
+ exports2.getDefaultOverhead = exports2.readContextOverhead = exports2.createStatuslineService = exports2.StatuslineService = exports2.getThresholdStatus = exports2.formatBranch = exports2.formatDuration = exports2.formatCost = exports2.formatTokens = exports2.createFormatter = exports2.Formatter = exports2.createGitProvider = exports2.GitProvider = exports2.createStateReader = exports2.StateReader = exports2.truncatePath = exports2.truncatePrefix = exports2.truncateSuffix = exports2.visibleLength = exports2.stripAnsi = void 0;
78326
78554
  __exportStar(require_types3(), exports2);
78555
+ var ansi_utils_js_1 = require_ansi_utils();
78556
+ Object.defineProperty(exports2, "stripAnsi", { enumerable: true, get: function() {
78557
+ return ansi_utils_js_1.stripAnsi;
78558
+ } });
78559
+ Object.defineProperty(exports2, "visibleLength", { enumerable: true, get: function() {
78560
+ return ansi_utils_js_1.visibleLength;
78561
+ } });
78562
+ var truncation_js_1 = require_truncation();
78563
+ Object.defineProperty(exports2, "truncateSuffix", { enumerable: true, get: function() {
78564
+ return truncation_js_1.truncateSuffix;
78565
+ } });
78566
+ Object.defineProperty(exports2, "truncatePrefix", { enumerable: true, get: function() {
78567
+ return truncation_js_1.truncatePrefix;
78568
+ } });
78569
+ Object.defineProperty(exports2, "truncatePath", { enumerable: true, get: function() {
78570
+ return truncation_js_1.truncatePath;
78571
+ } });
78327
78572
  var state_reader_js_1 = require_state_reader();
78328
78573
  Object.defineProperty(exports2, "StateReader", { enumerable: true, get: function() {
78329
78574
  return state_reader_js_1.StateReader;
@@ -78354,9 +78599,6 @@ var require_dist7 = __commonJS({
78354
78599
  Object.defineProperty(exports2, "formatDuration", { enumerable: true, get: function() {
78355
78600
  return formatter_js_1.formatDuration;
78356
78601
  } });
78357
- Object.defineProperty(exports2, "shortenPath", { enumerable: true, get: function() {
78358
- return formatter_js_1.shortenPath;
78359
- } });
78360
78602
  Object.defineProperty(exports2, "formatBranch", { enumerable: true, get: function() {
78361
78603
  return formatter_js_1.formatBranch;
78362
78604
  } });
@@ -78496,7 +78738,18 @@ Examples:
78496
78738
  cache_creation_input_tokens: num(currentUsage.cache_creation_input_tokens, 0),
78497
78739
  cache_read_input_tokens: num(currentUsage.cache_read_input_tokens, 0)
78498
78740
  } : null
78499
- }
78741
+ },
78742
+ // Parse optional worktree data (only present in worktree sessions)
78743
+ worktree: raw.worktree ? (() => {
78744
+ const wt = raw.worktree;
78745
+ return {
78746
+ name: typeof wt.name === "string" ? wt.name : "",
78747
+ path: typeof wt.path === "string" ? wt.path : "",
78748
+ branch: typeof wt.branch === "string" ? wt.branch : "",
78749
+ original_cwd: typeof wt.original_cwd === "string" ? wt.original_cwd : void 0,
78750
+ original_branch: typeof wt.original_branch === "string" ? wt.original_branch : void 0
78751
+ };
78752
+ })() : void 0
78500
78753
  };
78501
78754
  }
78502
78755
  async function handleStatuslineCommand(projectDir, logger, stdout, options = {}) {
@@ -82788,7 +83041,7 @@ var require_cli = __commonJS({
82788
83041
  var promises_12 = require("node:fs/promises");
82789
83042
  var node_stream_1 = require("node:stream");
82790
83043
  var yargs_parser_1 = __importDefault2(require_build());
82791
- var VERSION = true ? "0.1.6" : "dev";
83044
+ var VERSION = true ? "0.1.7" : "dev";
82792
83045
  var SANDBOX_ERROR_MESSAGE = `Error: Daemon commands cannot run in sandbox mode.
82793
83046
 
82794
83047
  Claude Code's sandbox blocks Unix socket operations required for daemon IPC.
package/dist/daemon.js CHANGED
@@ -55928,8 +55928,7 @@ var require_gitignore = __commonJS({
55928
55928
  ".sidekick/.env.local",
55929
55929
  ".sidekick/sidekick*.pid",
55930
55930
  ".sidekick/sidekick*.token",
55931
- ".sidekick/*.local.yaml",
55932
- ".sidekick/features.yaml"
55931
+ ".sidekick/*.local.yaml"
55933
55932
  ];
55934
55933
  async function installGitignoreSection(projectDir2) {
55935
55934
  const gitignorePath = path.join(projectDir2, ".gitignore");
@@ -70319,23 +70318,25 @@ var require_stage_default_user_prompt = __commonJS({
70319
70318
  });
70320
70319
  if (!(0, types_1.isDaemonContext)(context))
70321
70320
  return;
70322
- const daemonCtx = context;
70323
70321
  context.handlers.register({
70324
70322
  id: "reminders:throttle-register-ups-session-start",
70325
70323
  priority: 49,
70326
70324
  filter: { kind: "hook", hooks: ["SessionStart"] },
70327
- handler: async (event) => {
70325
+ handler: async (event, ctx) => {
70328
70326
  if (!(0, types_1.isHookEvent)(event) || !(0, types_1.isSessionStartEvent)(event))
70329
70327
  return;
70328
+ if (!(0, types_1.isDaemonContext)(ctx))
70329
+ return;
70330
+ const handlerCtx = ctx;
70330
70331
  const sessionId = event.context.sessionId;
70331
70332
  if (!sessionId)
70332
70333
  return;
70333
70334
  const reminder = (0, reminder_utils_js_1.resolveReminder)(types_js_1.ReminderIds.USER_PROMPT_SUBMIT, {
70334
70335
  context: { sessionId },
70335
- assets: daemonCtx.assets
70336
+ assets: handlerCtx.assets
70336
70337
  });
70337
70338
  if (reminder) {
70338
- await (0, throttle_utils_js_1.registerThrottledReminder)(daemonCtx, sessionId, types_js_1.ReminderIds.USER_PROMPT_SUBMIT, "UserPromptSubmit", reminder);
70339
+ await (0, throttle_utils_js_1.registerThrottledReminder)(handlerCtx, sessionId, types_js_1.ReminderIds.USER_PROMPT_SUBMIT, "UserPromptSubmit", reminder);
70339
70340
  }
70340
70341
  }
70341
70342
  });
@@ -70343,48 +70344,57 @@ var require_stage_default_user_prompt = __commonJS({
70343
70344
  id: "reminders:throttle-reset-session-start",
70344
70345
  priority: 48,
70345
70346
  filter: { kind: "hook", hooks: ["SessionStart"] },
70346
- handler: async (event) => {
70347
+ handler: async (event, ctx) => {
70347
70348
  if (!(0, types_1.isHookEvent)(event) || !(0, types_1.isSessionStartEvent)(event))
70348
70349
  return;
70350
+ if (!(0, types_1.isDaemonContext)(ctx))
70351
+ return;
70352
+ const handlerCtx = ctx;
70349
70353
  const sessionId = event.context.sessionId;
70350
70354
  if (!sessionId)
70351
70355
  return;
70352
- await (0, throttle_utils_js_1.resetThrottleCounters)(daemonCtx, sessionId);
70356
+ await (0, throttle_utils_js_1.resetThrottleCounters)(handlerCtx, sessionId);
70353
70357
  }
70354
70358
  });
70355
70359
  context.handlers.register({
70356
70360
  id: "reminders:throttle-reset-bulk",
70357
70361
  priority: 49,
70358
70362
  filter: { kind: "transcript", eventTypes: ["BulkProcessingComplete"] },
70359
- handler: async (event) => {
70363
+ handler: async (event, ctx) => {
70360
70364
  if (!(0, types_1.isTranscriptEvent)(event))
70361
70365
  return;
70362
70366
  if (event.metadata.isBulkProcessing)
70363
70367
  return;
70368
+ if (!(0, types_1.isDaemonContext)(ctx))
70369
+ return;
70370
+ const handlerCtx = ctx;
70364
70371
  const sessionId = event.context?.sessionId;
70365
70372
  if (!sessionId)
70366
70373
  return;
70367
- await (0, throttle_utils_js_1.resetThrottleCounters)(daemonCtx, sessionId);
70374
+ await (0, throttle_utils_js_1.resetThrottleCounters)(handlerCtx, sessionId);
70368
70375
  }
70369
70376
  });
70370
70377
  context.handlers.register({
70371
70378
  id: "reminders:throttle-restage",
70372
70379
  priority: 50,
70373
70380
  filter: { kind: "transcript", eventTypes: ["UserPrompt", "AssistantMessage"] },
70374
- handler: async (event) => {
70381
+ handler: async (event, ctx) => {
70375
70382
  if (!(0, types_1.isTranscriptEvent)(event))
70376
70383
  return;
70377
70384
  if (event.metadata.isBulkProcessing)
70378
70385
  return;
70386
+ if (!(0, types_1.isDaemonContext)(ctx))
70387
+ return;
70388
+ const handlerCtx = ctx;
70379
70389
  const sessionId = event.context?.sessionId;
70380
70390
  if (!sessionId)
70381
70391
  return;
70382
- const remindersState = (0, state_js_1.createRemindersState)(daemonCtx.stateService);
70392
+ const remindersState = (0, state_js_1.createRemindersState)(handlerCtx.stateService);
70383
70393
  const result = await remindersState.reminderThrottle.read(sessionId);
70384
70394
  const state = { ...result.data };
70385
70395
  if (Object.keys(state).length === 0)
70386
70396
  return;
70387
- const featureConfig = daemonCtx.config.getFeature("reminders");
70397
+ const featureConfig = handlerCtx.config.getFeature("reminders");
70388
70398
  const config = { ...types_js_1.DEFAULT_REMINDERS_SETTINGS, ...featureConfig.settings };
70389
70399
  const thresholds = config.reminder_thresholds ?? {};
70390
70400
  let changed = false;
@@ -70402,12 +70412,12 @@ var require_stage_default_user_prompt = __commonJS({
70402
70412
  toolsThisTurn: metrics.toolsThisTurn,
70403
70413
  toolCount: metrics.toolCount
70404
70414
  };
70405
- await (0, reminder_utils_js_1.stageReminder)(daemonCtx, typedEntry.targetHook, {
70415
+ await (0, reminder_utils_js_1.stageReminder)(handlerCtx, typedEntry.targetHook, {
70406
70416
  ...typedEntry.cachedReminder,
70407
70417
  stagedAt
70408
70418
  });
70409
70419
  state[reminderId] = { ...typedEntry, messagesSinceLastStaging: 0 };
70410
- daemonCtx.logger.debug("Throttle: re-staged reminder", {
70420
+ handlerCtx.logger.debug("Throttle: re-staged reminder", {
70411
70421
  sessionId,
70412
70422
  reminderId,
70413
70423
  messageCount: newCount,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scotthamilton77/sidekick",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "AI pair programming assistant with personas, session tracking, and contextual nudges",
5
5
  "bin": {
6
6
  "sidekick": "dist/bin.js"