ccsidekick 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,20 +9,58 @@ path and disableable.
9
9
  npx ccsidekick
10
10
  ```
11
11
 
12
- Running `ccsidekick` in a terminal opens the setup TUI: it picks a Claude config dir, lets you choose
13
- a character, and wires the status line and the tool-call hooks into your `settings.json`. Remove it
14
- with `npx ccsidekick uninstall`.
12
+ Running `ccsidekick` in a terminal opens the setup UI: a first run walks you through a short guided
13
+ wizard (character, theme, comments), a later run opens the full dashboard. Either way it picks a
14
+ Claude config dir and wires the status line and the tool-call hooks into your `settings.json`.
15
+ Remove it with `npx ccsidekick uninstall`.
16
+
17
+ ## Non-interactive setup (for scripts and AI agents)
18
+
19
+ No TTY required. `ccsidekick setup` configures and wires everything from flags, so an agent can
20
+ install it in one command:
21
+
22
+ ```bash
23
+ ccsidekick setup --character batman --theme houston --mode fixed
24
+ ```
25
+
26
+ Only the flags you pass are applied (a partial patch onto the existing config, or the defaults on a
27
+ fresh install), then it writes `config.toml` and wires `settings.json` exactly like the TUI.
28
+
29
+ | Flag | Sets |
30
+ | ------------------------ | --------------------------------------------------------------- |
31
+ | `--character <name>` | the fixed character |
32
+ | `--mode <fixed\|random>` | fixed one character, or rotate the roster |
33
+ | `--roster <a,b,c>` | the random-mode roster |
34
+ | `--theme <name>` | a theme, or `character` (the default) to match the character |
35
+ | `--currency <code>` | statusline currency, e.g. `USD` |
36
+ | `--budget <usd>` | monthly budget |
37
+ | `--comments <on\|off>` | the character's comment line |
38
+ | `--helpful <on\|off>` | the helpful-tip line |
39
+ | `--min-severity <sev>` | `low\|medium\|high\|critical` |
40
+ | `--widgets <a,b,c>` | statusline widgets to enable (others turn off) |
41
+ | `--global` / `--local` | save target (default global) |
42
+ | `--config-dir <path>` | Claude config dir (default `$CLAUDE_CONFIG_DIR` or `~/.claude`) |
43
+
44
+ Discover valid values for scripting, and see every flag:
45
+
46
+ ```bash
47
+ ccsidekick list characters # also: themes, widgets
48
+ ccsidekick setup --help
49
+ ```
50
+
51
+ An unknown value (e.g. a misspelled theme) exits non-zero and prints the valid set — it never
52
+ silently falls back.
15
53
 
16
54
  ## The two binaries
17
55
 
18
56
  - **`ccsidekick-render`** is the hot path. Claude Code calls `ccsidekick-render render` on every
19
- status-line tick and `ccsidekick-render classify` on every tool call. It loads no UI and runs under
20
- plain Node.
21
- - **`ccsidekick`** is the setup TUI and `uninstall`. It is the only entry point that loads the Ink
22
- interface.
57
+ status-line tick and `ccsidekick-render classify` on every tool call. It loads no UI and runs
58
+ under plain Node.
59
+ - **`ccsidekick`** is the user-facing entry: the setup UI, plus `setup`, `list`, and `uninstall`.
60
+ Only the TUI loads the Ink interface; `setup`/`list`/`uninstall` run under plain Node.
23
61
 
24
- Characters ship as separate `@ccsidekick/pack-<name>` packages; `batman` is bundled as a runtime
25
- dependency so a fresh install always has a character.
62
+ Every character ships bundled as a runtime dependency, so a fresh install has them all — there is no
63
+ download or install step, and no network to pick a character.
26
64
 
27
65
  For the full feature tour, configuration reference, and contributor docs, see the
28
66
  [repository README](https://github.com/krayong/ccsidekick#readme).
@@ -1038,15 +1038,19 @@ function candidateSet(config, installed) {
1038
1038
  return ["batman"];
1039
1039
  }
1040
1040
  function derivePersona(config, state, session, installed) {
1041
- if (state.character !== undefined && state.character !== "")
1042
- return state.character;
1041
+ const candidates = candidateSet(config, installed);
1042
+ const sticky = state.character;
1043
+ if (sticky !== undefined && sticky !== "" && stickyValid(config, candidates, sticky))
1044
+ return sticky;
1043
1045
  if (config.character.mode === "fixed")
1044
1046
  return config.character.name;
1045
- const candidates = candidateSet(config, installed);
1046
1047
  const hash = createHash("sha1").update(session).digest("hex");
1047
1048
  const idx = parseInt(hash.slice(0, 8), 16) % candidates.length;
1048
1049
  return candidates[idx] ?? "batman";
1049
1050
  }
1051
+ function stickyValid(config, candidates, sticky) {
1052
+ return config.character.mode === "fixed" ? sticky === config.character.name : candidates.includes(sticky);
1053
+ }
1050
1054
  // src/derived/project.ts
1051
1055
  function deriveProject(git, payload) {
1052
1056
  const repo = payload.workspace.repo;
@@ -1212,12 +1216,6 @@ var sessionDir = (root, id) => join(root, "sessions", id);
1212
1216
  var cacheDir = (root) => join(root, "cache");
1213
1217
  var analyticsDir = (root) => join(root, "analytics");
1214
1218
 
1215
- // src/sources/storage/engineRoot.ts
1216
- import { fileURLToPath } from "node:url";
1217
- function engineRoot(moduleUrl) {
1218
- return fileURLToPath(new URL("../", moduleUrl));
1219
- }
1220
-
1221
1219
  // src/sources/storage/atomicWrite.ts
1222
1220
  import { mkdirSync, renameSync, writeFileSync } from "node:fs";
1223
1221
  import { dirname } from "node:path";
@@ -2428,11 +2426,10 @@ var DEFAULT_WIDGETS = {
2428
2426
  var DEFAULT_CONFIG = {
2429
2427
  schema_version: 1,
2430
2428
  character: { enabled: true, mode: "random", name: "batman", roster: [] },
2431
- comments: { enabled: true },
2432
- helpful: { enabled: true, min_severity: "low" },
2433
- line: { currency: defaultCurrency(), widgets: DEFAULT_WIDGETS },
2434
- theme: { name: "houston", banding: "solid", mood_shift: false, icons: {} },
2435
- network: { fx_refresh: true, usage_fetch: true, balance_path: "" }
2429
+ theme: { name: "character", banding: "solid", mood_shift: false, icons: {} },
2430
+ comments: { character: true, helpful: true, min_severity: "low" },
2431
+ network: { fx_refresh: true, usage_fetch: true, balance_path: "" },
2432
+ statusline: { currency: defaultCurrency(), widgets: DEFAULT_WIDGETS }
2436
2433
  };
2437
2434
  var obj = (v) => v !== null && typeof v === "object" ? v : {};
2438
2435
  var bool = (v, d) => typeof v === "boolean" ? v : d;
@@ -2475,10 +2472,9 @@ function loadConfig(globalToml = "", projectToml = "") {
2475
2472
  const p = safeParse(projectToml);
2476
2473
  const character = section(g, p, "character");
2477
2474
  const comments = section(g, p, "comments");
2478
- const helpful = section(g, p, "helpful");
2479
- const line = section(g, p, "line");
2480
2475
  const theme = section(g, p, "theme");
2481
2476
  const network = section(g, p, "network");
2477
+ const statusline = section(g, p, "statusline");
2482
2478
  const d = DEFAULT_CONFIG;
2483
2479
  return {
2484
2480
  schema_version: numv(p["schema_version"] ?? g["schema_version"], d.schema_version),
@@ -2488,21 +2484,6 @@ function loadConfig(globalToml = "", projectToml = "") {
2488
2484
  name: str(character["name"], d.character.name),
2489
2485
  roster: stringArray(character["roster"], d.character.roster)
2490
2486
  },
2491
- comments: {
2492
- enabled: bool(comments["enabled"], d.comments.enabled)
2493
- },
2494
- helpful: {
2495
- enabled: bool(helpful["enabled"], d.helpful.enabled),
2496
- min_severity: oneOf(helpful["min_severity"], ["low", "medium", "high", "critical"], d.helpful.min_severity)
2497
- },
2498
- line: {
2499
- currency: str(line["currency"], d.line.currency),
2500
- ...typeof line["budget"] === "number" ? { budget: line["budget"] } : {},
2501
- widgets: mergeWidgets({
2502
- ...obj(obj(g["line"])["widgets"]),
2503
- ...obj(obj(p["line"])["widgets"])
2504
- })
2505
- },
2506
2487
  theme: {
2507
2488
  name: str(theme["name"], d.theme.name),
2508
2489
  ...typeof theme["statusline"] === "string" ? { statusline: theme["statusline"] } : {},
@@ -2515,10 +2496,23 @@ function loadConfig(globalToml = "", projectToml = "") {
2515
2496
  ...obj(obj(p["theme"])["icons"])
2516
2497
  })
2517
2498
  },
2499
+ comments: {
2500
+ character: bool(comments["character"], d.comments.character),
2501
+ helpful: bool(comments["helpful"], d.comments.helpful),
2502
+ min_severity: oneOf(comments["min_severity"], ["low", "medium", "high", "critical"], d.comments.min_severity)
2503
+ },
2518
2504
  network: {
2519
2505
  fx_refresh: bool(network["fx_refresh"], d.network.fx_refresh),
2520
2506
  usage_fetch: bool(network["usage_fetch"], d.network.usage_fetch),
2521
2507
  balance_path: str(network["balance_path"], d.network.balance_path)
2508
+ },
2509
+ statusline: {
2510
+ currency: str(statusline["currency"], d.statusline.currency),
2511
+ ...typeof statusline["budget"] === "number" ? { budget: statusline["budget"] } : {},
2512
+ widgets: mergeWidgets({
2513
+ ...obj(obj(g["statusline"])["widgets"]),
2514
+ ...obj(obj(p["statusline"])["widgets"])
2515
+ })
2522
2516
  }
2523
2517
  };
2524
2518
  }
@@ -3529,41 +3523,8 @@ function readHelpfulEnv(cwd) {
3529
3523
  ...opt3("tfWorkspace", readTfWorkspace(cwd))
3530
3524
  };
3531
3525
  }
3532
- // src/sources/installed.ts
3533
- import { readdirSync, statSync as statSync3 } from "node:fs";
3534
- import { join as join10 } from "node:path";
3535
- var PREFIX = "pack-";
3536
- function listInstalledPacks(engineDir) {
3537
- const scope = join10(engineDir, "node_modules", "@ccsidekick");
3538
- let entries;
3539
- try {
3540
- entries = readdirSync(scope, { withFileTypes: true });
3541
- } catch {
3542
- return [];
3543
- }
3544
- const ids = [];
3545
- for (const entry of entries) {
3546
- if (!entry.name.startsWith(PREFIX) || entry.name.length <= PREFIX.length)
3547
- continue;
3548
- if (!isDir(entry, join10(scope, entry.name)))
3549
- continue;
3550
- ids.push(entry.name.slice(PREFIX.length));
3551
- }
3552
- return ids.sort();
3553
- }
3554
- function isDir(entry, path) {
3555
- if (entry.isDirectory())
3556
- return true;
3557
- if (!entry.isSymbolicLink())
3558
- return false;
3559
- try {
3560
- return statSync3(path).isDirectory();
3561
- } catch {
3562
- return false;
3563
- }
3564
- }
3565
3526
  // src/sources/markers.ts
3566
- import { readdirSync as readdirSync2 } from "node:fs";
3527
+ import { readdirSync } from "node:fs";
3567
3528
  import { dirname as dirname5, resolve as resolve2 } from "node:path";
3568
3529
  var FILE_MARKERS = {
3569
3530
  "package.json": "web",
@@ -3624,7 +3585,7 @@ function scanDir(dir, stacks) {
3624
3585
  let entries;
3625
3586
  let isRepoRoot = false;
3626
3587
  try {
3627
- entries = readdirSync2(dir);
3588
+ entries = readdirSync(dir);
3628
3589
  } catch {
3629
3590
  return false;
3630
3591
  }
@@ -3658,8 +3619,8 @@ function readMarkers(cwd) {
3658
3619
  return { stacks };
3659
3620
  }
3660
3621
  // src/sources/modelNames.ts
3661
- import { join as join11 } from "node:path";
3662
- var cachePath = (root) => join11(cacheDir(root), "model-names.json");
3622
+ import { join as join10 } from "node:path";
3623
+ var cachePath = (root) => join10(cacheDir(root), "model-names.json");
3663
3624
  function readModelNames(root) {
3664
3625
  return readJson(cachePath(root), {});
3665
3626
  }
@@ -3776,7 +3737,7 @@ function parsePayload(raw) {
3776
3737
  };
3777
3738
  }
3778
3739
  // src/sources/state.ts
3779
- import { join as join12 } from "node:path";
3740
+ import { join as join11 } from "node:path";
3780
3741
  var STATE_FILE = "state.json";
3781
3742
  var EMPTY2 = { pressureFired: [], milestones: [], helpful: {} };
3782
3743
  function isObject5(v) {
@@ -3801,7 +3762,7 @@ function asHelpful(v) {
3801
3762
  return out;
3802
3763
  }
3803
3764
  function readState(sessionDir2) {
3804
- const raw = readJson(join12(sessionDir2, STATE_FILE), undefined);
3765
+ const raw = readJson(join11(sessionDir2, STATE_FILE), undefined);
3805
3766
  if (!isObject5(raw))
3806
3767
  return EMPTY2;
3807
3768
  const character = raw["character"];
@@ -3831,14 +3792,14 @@ function mergeState(disk, next) {
3831
3792
  };
3832
3793
  }
3833
3794
  function writeState(sessionDir2, s) {
3834
- const path = join12(sessionDir2, STATE_FILE);
3795
+ const path = join11(sessionDir2, STATE_FILE);
3835
3796
  withLock(`${path}.lock`, () => {
3836
3797
  atomicWrite(path, JSON.stringify(mergeState(readState(sessionDir2), s)));
3837
3798
  }, () => {});
3838
3799
  }
3839
3800
  // src/sources/transcript.ts
3840
- import { readFileSync as readFileSync8, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
3841
- import { basename, dirname as dirname6, join as join13 } from "node:path";
3801
+ import { readFileSync as readFileSync8, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
3802
+ import { basename, dirname as dirname6, join as join12 } from "node:path";
3842
3803
  var asObj = (v) => v !== null && typeof v === "object" ? v : undefined;
3843
3804
  var asStr = (v) => typeof v === "string" ? v : undefined;
3844
3805
  var asNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
@@ -4063,7 +4024,7 @@ function reconstructTodos(lines) {
4063
4024
  }
4064
4025
  function statSafe(p) {
4065
4026
  try {
4066
- const s = statSync4(p);
4027
+ const s = statSync3(p);
4067
4028
  return { mtimeMs: s.mtimeMs, size: s.size };
4068
4029
  } catch {
4069
4030
  return null;
@@ -4153,15 +4114,15 @@ var repoRootCache = new Map;
4153
4114
  function* walkTree(dir, encodedDir) {
4154
4115
  let entries;
4155
4116
  try {
4156
- entries = readdirSync3(dir);
4117
+ entries = readdirSync2(dir);
4157
4118
  } catch {
4158
4119
  return;
4159
4120
  }
4160
4121
  for (const name of entries) {
4161
- const full = join13(dir, name);
4122
+ const full = join12(dir, name);
4162
4123
  let st;
4163
4124
  try {
4164
- st = statSync4(full);
4125
+ st = statSync3(full);
4165
4126
  } catch {
4166
4127
  continue;
4167
4128
  }
@@ -4175,12 +4136,12 @@ function* walkTree(dir, encodedDir) {
4175
4136
  function* walkJsonl(root) {
4176
4137
  let dirs;
4177
4138
  try {
4178
- dirs = readdirSync3(root);
4139
+ dirs = readdirSync2(root);
4179
4140
  } catch {
4180
4141
  return;
4181
4142
  }
4182
4143
  for (const encodedDir of dirs) {
4183
- yield* walkTree(join13(root, encodedDir), encodedDir);
4144
+ yield* walkTree(join12(root, encodedDir), encodedDir);
4184
4145
  }
4185
4146
  }
4186
4147
  function scanBounds(lines) {
@@ -4391,7 +4352,7 @@ function runClassify(stdin, env, clock) {
4391
4352
  }
4392
4353
  // src/cli/render.ts
4393
4354
  import { readFileSync as readFileSync10 } from "node:fs";
4394
- import { dirname as dirname7, join as join15 } from "node:path";
4355
+ import { dirname as dirname7, join as join14 } from "node:path";
4395
4356
 
4396
4357
  // src/compose/character.ts
4397
4358
  import { createHash as createHash5 } from "node:crypto";
@@ -5242,7 +5203,13 @@ var truncateField = (f, budget) => {
5242
5203
  if (f.id === "model" && f.segments.length >= 2 && tail !== undefined) {
5243
5204
  const tailText = sanitizePackText(tail.text);
5244
5205
  const headText = f.segments.slice(0, -1).map((s) => sanitizePackText(s.text)).filter((t) => t !== "").join(" ");
5245
- const headBudget = Math.max(1, budget - displayWidth(tailText) - 1);
5206
+ const headBudget = budget - displayWidth(tailText) - 1;
5207
+ if (headBudget < 1) {
5208
+ return {
5209
+ id: f.id,
5210
+ segments: [{ role: "value", text: truncToWidth(fieldPlain(f), budget) }]
5211
+ };
5212
+ }
5246
5213
  return {
5247
5214
  id: f.id,
5248
5215
  segments: [
@@ -5253,6 +5220,21 @@ var truncateField = (f, budget) => {
5253
5220
  }
5254
5221
  return { id: f.id, segments: [{ role: "value", text: truncToWidth(fieldPlain(f), budget) }] };
5255
5222
  };
5223
+ var fitCells = (cells, avail, sepGlyph) => {
5224
+ if (cells.length === 0 || rowPlainWidth(cells, sepGlyph) <= avail)
5225
+ return [...cells];
5226
+ const last = cells[cells.length - 1];
5227
+ if (last === undefined)
5228
+ return [...cells];
5229
+ const head = cells.slice(0, -1);
5230
+ const sepWidth = displayWidth(` ${sepGlyph} `);
5231
+ const headWidth = head.length > 0 ? displayWidth(head.map(fieldPlain).join(` ${sepGlyph} `)) : 0;
5232
+ const budget = avail - headWidth - (head.length > 0 ? sepWidth : 0);
5233
+ if (budget >= 1)
5234
+ return [...head, truncateField(last, budget)];
5235
+ const headFitted = fitCells(head, Math.max(1, avail - sepWidth - 1), sepGlyph);
5236
+ return [...headFitted, truncateField(last, 1)];
5237
+ };
5256
5238
  var fitRow = (row, cells, avail, sepGlyph) => {
5257
5239
  const present = [...cells];
5258
5240
  for (const id of dropOrder(row)) {
@@ -5264,15 +5246,7 @@ var fitRow = (row, cells, avail, sepGlyph) => {
5264
5246
  if (idx >= 0)
5265
5247
  present.splice(idx, 1);
5266
5248
  }
5267
- if (rowPlainWidth(present, sepGlyph) <= avail)
5268
- return present;
5269
- const last = present[present.length - 1];
5270
- if (last === undefined)
5271
- return present;
5272
- const head = present.slice(0, -1);
5273
- const headWidth = head.length > 0 ? displayWidth(head.map(fieldPlain).join(` ${sepGlyph} `)) + displayWidth(` ${sepGlyph} `) : 0;
5274
- const budget = Math.max(1, avail - headWidth);
5275
- return [...head, truncateField(last, budget)];
5249
+ return fitCells(present, avail, sepGlyph);
5276
5250
  };
5277
5251
  var colorCell = (field, lineIdx, cellIdx, input, term) => {
5278
5252
  const { theme, moodShift, mood } = input;
@@ -6490,22 +6464,22 @@ var HELPFUL_CATALOG = [
6490
6464
  render: (i) => `Large diff: +${i.git?.insertions ?? 0}/-${i.git?.deletions ?? 0}. Review in chunks.`
6491
6465
  },
6492
6466
  {
6493
- id: "behind_upstream",
6467
+ id: "diverged",
6494
6468
  severity: "medium",
6495
6469
  category: "git",
6496
6470
  momentary: false,
6497
- template: "{behind} behind upstream. Pull before your next commit knots the merge.",
6498
- test: (i) => behind(i.git) > 0,
6499
- render: (i) => `${behind(i.git)} behind upstream. Pull before your next commit knots the merge.`
6471
+ template: "Diverged {ahead}↑ {behind}↓. `git pull --rebase` keeps history linear.",
6472
+ test: (i) => ahead(i.git) > 0 && behind(i.git) > 0,
6473
+ render: (i) => `Diverged ${ahead(i.git)}↑ ${behind(i.git)}↓. \`git pull --rebase\` keeps history linear.`
6500
6474
  },
6501
6475
  {
6502
- id: "diverged",
6476
+ id: "behind_upstream",
6503
6477
  severity: "medium",
6504
6478
  category: "git",
6505
6479
  momentary: false,
6506
- template: "Diverged {ahead}↑ {behind}↓. `git pull --rebase` keeps history linear.",
6507
- test: (i) => ahead(i.git) > 0 && behind(i.git) > 0,
6508
- render: (i) => `Diverged ${ahead(i.git)}↑ ${behind(i.git)}↓. \`git pull --rebase\` keeps history linear.`
6480
+ template: "{behind} behind upstream. Pull before your next commit knots the merge.",
6481
+ test: (i) => behind(i.git) > 0,
6482
+ render: (i) => `${behind(i.git)} behind upstream. Pull before your next commit knots the merge.`
6509
6483
  },
6510
6484
  {
6511
6485
  id: "dirty_default_branch",
@@ -6754,16 +6728,16 @@ function resolveHelpful(inputs, state, clock, minSeverity) {
6754
6728
  }
6755
6729
  // src/packs/load.ts
6756
6730
  import { readFileSync as readFileSync9 } from "node:fs";
6757
- import { fileURLToPath as fileURLToPath2 } from "node:url";
6731
+ import { fileURLToPath } from "node:url";
6758
6732
 
6759
6733
  // src/packs/allowlist.ts
6760
- var PREFIX2 = "@ccsidekick/pack-";
6734
+ var PREFIX = "@ccsidekick/pack-";
6761
6735
  var NAME_SEGMENT = /^[a-z0-9-]+$/;
6762
- var packPackageName = (name) => `${PREFIX2}${name}`;
6736
+ var packPackageName = (name) => `${PREFIX}${name}`;
6763
6737
  function isAllowedPackPackage(pkg) {
6764
- if (!pkg.startsWith(PREFIX2))
6738
+ if (!pkg.startsWith(PREFIX))
6765
6739
  return false;
6766
- return NAME_SEGMENT.test(pkg.slice(PREFIX2.length));
6740
+ return NAME_SEGMENT.test(pkg.slice(PREFIX.length));
6767
6741
  }
6768
6742
 
6769
6743
  // src/packs/validate.ts
@@ -6965,7 +6939,7 @@ var validatePack = (raw) => {
6965
6939
  };
6966
6940
 
6967
6941
  // src/packs/load.ts
6968
- var defaultResolver = (spec) => fileURLToPath2(import.meta.resolve(spec));
6942
+ var defaultResolver = (spec) => fileURLToPath(import.meta.resolve(spec));
6969
6943
  var errMessage = (e) => e instanceof Error ? e.message : String(e);
6970
6944
  function loadPack(name, resolver = defaultResolver) {
6971
6945
  if (!isAllowedPackPackage(packPackageName(name))) {
@@ -6995,17 +6969,30 @@ function loadPack(name, resolver = defaultResolver) {
6995
6969
  return { ok: false, reason: "pack failed validation" };
6996
6970
  return { ok: true, pack };
6997
6971
  }
6972
+ // src/packs/registry.ts
6973
+ var PACKS = [
6974
+ "barbie",
6975
+ "batman",
6976
+ "deadpool",
6977
+ "harry-potter",
6978
+ "hello-kitty",
6979
+ "james-bond",
6980
+ "joker",
6981
+ "pikachu",
6982
+ "sherlock-holmes",
6983
+ "spiderman"
6984
+ ];
6998
6985
  // src/cli/gc.ts
6999
- import { existsSync as existsSync2, readdirSync as readdirSync4, rmSync, statSync as statSync5 } from "node:fs";
7000
- import { join as join14 } from "node:path";
6986
+ import { existsSync as existsSync2, readdirSync as readdirSync3, rmSync, statSync as statSync4 } from "node:fs";
6987
+ import { join as join13 } from "node:path";
7001
6988
  var DAY_MS = 86400000;
7002
6989
  function newestMtime(dir) {
7003
6990
  let newest = 0;
7004
6991
  try {
7005
- newest = statSync5(dir).mtimeMs;
7006
- for (const name of readdirSync4(dir)) {
6992
+ newest = statSync4(dir).mtimeMs;
6993
+ for (const name of readdirSync3(dir)) {
7007
6994
  try {
7008
- const m = statSync5(join14(dir, name)).mtimeMs;
6995
+ const m = statSync4(join13(dir, name)).mtimeMs;
7009
6996
  if (m > newest)
7010
6997
  newest = m;
7011
6998
  } catch {}
@@ -7014,18 +7001,18 @@ function newestMtime(dir) {
7014
7001
  return newest;
7015
7002
  }
7016
7003
  function pruneSessions(root, now) {
7017
- const sessions = join14(root, "sessions");
7004
+ const sessions = join13(root, "sessions");
7018
7005
  let names;
7019
7006
  try {
7020
- names = readdirSync4(sessions);
7007
+ names = readdirSync3(sessions);
7021
7008
  } catch {
7022
7009
  return;
7023
7010
  }
7024
7011
  const cutoff = now - SESSION_TTL_DAYS * DAY_MS;
7025
7012
  for (const name of names) {
7026
- const dir = join14(sessions, name);
7013
+ const dir = join13(sessions, name);
7027
7014
  try {
7028
- if (!statSync5(dir).isDirectory())
7015
+ if (!statSync4(dir).isDirectory())
7029
7016
  continue;
7030
7017
  } catch {
7031
7018
  continue;
@@ -7097,13 +7084,12 @@ function applyThemeIcons(fields, theme, config) {
7097
7084
  };
7098
7085
  });
7099
7086
  }
7100
- function readGated(overrides, envInputs, config, root, dir, markers, sessionState, clock) {
7087
+ function readGated(overrides, envInputs, config, root, dir, markers, clock) {
7101
7088
  const creds = overrides?.creds !== undefined ? overrides.creds : envInputs.hasApiKey || config.network.usage_fetch ? readCreds() : null;
7102
7089
  const usage = overrides?.usage !== undefined ? overrides.usage : config.network.usage_fetch ? readUsageCached(root) : null;
7103
7090
  const balance = overrides?.balance !== undefined ? overrides.balance : config.network.balance_path !== "" ? readBalance(config.network.balance_path, clock) : null;
7104
7091
  const helpfulEnv = needsHelpfulEnv(markers) ? readHelpfulEnv(dir) : EMPTY_HELPFUL_ENV;
7105
- const installed = sessionState.character === undefined && config.character.mode === "random" && config.character.roster.length === 0 ? listInstalledPacks(engineRoot(import.meta.url)) : [];
7106
- return { creds, usage, balance, helpfulEnv, installed };
7092
+ return { creds, usage, balance, helpfulEnv };
7107
7093
  }
7108
7094
  var DEFAULT_EMBLEM = "❝";
7109
7095
  function lookupPackTheme(name) {
@@ -7132,10 +7118,10 @@ function build(stdin, env, term, clock, overrides) {
7132
7118
  return { line: "", persist: NOOP };
7133
7119
  const root = ccsidekickRoot(env);
7134
7120
  const base = dirname7(root);
7135
- const projectsRoot = join15(base, "projects");
7121
+ const projectsRoot = join14(base, "projects");
7136
7122
  const dir = payload.workspace.current_dir ?? payload.cwd ?? process.cwd();
7137
- const globalToml = readTextSafe(join15(root, "config.toml"));
7138
- const projectToml = readTextSafe(join15(dir, ".ccsidekick", "config.toml"));
7123
+ const globalToml = readTextSafe(join14(root, "config.toml"));
7124
+ const projectToml = readTextSafe(join14(dir, ".ccsidekick", "config.toml"));
7139
7125
  const config = loadConfig(globalToml, projectToml);
7140
7126
  const envInputs = readEnv(env);
7141
7127
  const aliases = readModelAliases(env);
@@ -7150,8 +7136,8 @@ function build(stdin, env, term, clock, overrides) {
7150
7136
  const attribution = readAttribution(root);
7151
7137
  const costCache = readCostCache(root);
7152
7138
  const fxTable = readFxCached(root);
7153
- const { creds, usage, balance, helpfulEnv, installed } = readGated(overrides, envInputs, config, root, dir, markers, sessionState, clock);
7154
- const persona = derivePersona(config, sessionState, session, installed);
7139
+ const { creds, usage, balance, helpfulEnv } = readGated(overrides, envInputs, config, root, dir, markers, clock);
7140
+ const persona = derivePersona(config, sessionState, session, PACKS);
7155
7141
  const loaded = loadPack(persona);
7156
7142
  const pack = loaded.ok ? loaded.pack : null;
7157
7143
  const project = deriveProject(git, payload);
@@ -7185,8 +7171,11 @@ function build(stdin, env, term, clock, overrides) {
7185
7171
  git,
7186
7172
  payload,
7187
7173
  scan,
7188
- widgets: config.line.widgets,
7189
- currency: { code: config.line.currency, rate: fxTable[config.line.currency] ?? 0 },
7174
+ widgets: config.statusline.widgets,
7175
+ currency: {
7176
+ code: config.statusline.currency,
7177
+ rate: fxTable[config.statusline.currency] ?? 0
7178
+ },
7190
7179
  homeDir: env["HOME"] ?? ""
7191
7180
  };
7192
7181
  const composed = composeStatusline(composeInputs);
@@ -7204,7 +7193,7 @@ function build(stdin, env, term, clock, overrides) {
7204
7193
  creds,
7205
7194
  balance
7206
7195
  };
7207
- const helpfulResult = config.helpful.enabled ? resolveHelpful(helpfulInputs, sessionState, clock, config.helpful.min_severity) : { comment: null, nextHelpful: sessionState.helpful };
7196
+ const helpfulResult = config.comments.helpful ? resolveHelpful(helpfulInputs, sessionState, clock, config.comments.min_severity) : { comment: null, nextHelpful: sessionState.helpful };
7208
7197
  const helpful = helpfulResult.comment;
7209
7198
  const pending = pendingMilestones(familiarity, clock);
7210
7199
  const charBase = {
@@ -7225,7 +7214,7 @@ function build(stdin, env, term, clock, overrides) {
7225
7214
  state: sessionState,
7226
7215
  clock,
7227
7216
  session,
7228
- config: { enabled: config.comments.enabled }
7217
+ config: { enabled: config.comments.character }
7229
7218
  });
7230
7219
  } catch {
7231
7220
  charResult = { comment: null, nextState: charBase };