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 +47 -9
- package/dist/ccsidekick-render.js +114 -125
- package/dist/ccsidekick.js +1215 -2464
- package/package.json +10 -7
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
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
20
|
-
plain Node.
|
|
21
|
-
- **`ccsidekick`** is the
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
1042
|
-
|
|
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
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
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
|
|
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 =
|
|
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
|
|
3662
|
-
var cachePath = (root) =>
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
3841
|
-
import { basename, dirname as dirname6, join as
|
|
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 =
|
|
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 =
|
|
4117
|
+
entries = readdirSync2(dir);
|
|
4157
4118
|
} catch {
|
|
4158
4119
|
return;
|
|
4159
4120
|
}
|
|
4160
4121
|
for (const name of entries) {
|
|
4161
|
-
const full =
|
|
4122
|
+
const full = join12(dir, name);
|
|
4162
4123
|
let st;
|
|
4163
4124
|
try {
|
|
4164
|
-
st =
|
|
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 =
|
|
4139
|
+
dirs = readdirSync2(root);
|
|
4179
4140
|
} catch {
|
|
4180
4141
|
return;
|
|
4181
4142
|
}
|
|
4182
4143
|
for (const encodedDir of dirs) {
|
|
4183
|
-
yield* walkTree(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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: "
|
|
6467
|
+
id: "diverged",
|
|
6494
6468
|
severity: "medium",
|
|
6495
6469
|
category: "git",
|
|
6496
6470
|
momentary: false,
|
|
6497
|
-
template: "{
|
|
6498
|
-
test: (i) => behind(i.git) > 0,
|
|
6499
|
-
render: (i) =>
|
|
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: "
|
|
6476
|
+
id: "behind_upstream",
|
|
6503
6477
|
severity: "medium",
|
|
6504
6478
|
category: "git",
|
|
6505
6479
|
momentary: false,
|
|
6506
|
-
template: "
|
|
6507
|
-
test: (i) =>
|
|
6508
|
-
render: (i) =>
|
|
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
|
|
6731
|
+
import { fileURLToPath } from "node:url";
|
|
6758
6732
|
|
|
6759
6733
|
// src/packs/allowlist.ts
|
|
6760
|
-
var
|
|
6734
|
+
var PREFIX = "@ccsidekick/pack-";
|
|
6761
6735
|
var NAME_SEGMENT = /^[a-z0-9-]+$/;
|
|
6762
|
-
var packPackageName = (name) => `${
|
|
6736
|
+
var packPackageName = (name) => `${PREFIX}${name}`;
|
|
6763
6737
|
function isAllowedPackPackage(pkg) {
|
|
6764
|
-
if (!pkg.startsWith(
|
|
6738
|
+
if (!pkg.startsWith(PREFIX))
|
|
6765
6739
|
return false;
|
|
6766
|
-
return NAME_SEGMENT.test(pkg.slice(
|
|
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) =>
|
|
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
|
|
7000
|
-
import { join as
|
|
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 =
|
|
7006
|
-
for (const name of
|
|
6992
|
+
newest = statSync4(dir).mtimeMs;
|
|
6993
|
+
for (const name of readdirSync3(dir)) {
|
|
7007
6994
|
try {
|
|
7008
|
-
const m =
|
|
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 =
|
|
7004
|
+
const sessions = join13(root, "sessions");
|
|
7018
7005
|
let names;
|
|
7019
7006
|
try {
|
|
7020
|
-
names =
|
|
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 =
|
|
7013
|
+
const dir = join13(sessions, name);
|
|
7027
7014
|
try {
|
|
7028
|
-
if (!
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
7121
|
+
const projectsRoot = join14(base, "projects");
|
|
7136
7122
|
const dir = payload.workspace.current_dir ?? payload.cwd ?? process.cwd();
|
|
7137
|
-
const globalToml = readTextSafe(
|
|
7138
|
-
const projectToml = readTextSafe(
|
|
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
|
|
7154
|
-
const persona = derivePersona(config, sessionState, session,
|
|
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.
|
|
7189
|
-
currency: {
|
|
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
|
|
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.
|
|
7217
|
+
config: { enabled: config.comments.character }
|
|
7229
7218
|
});
|
|
7230
7219
|
} catch {
|
|
7231
7220
|
charResult = { comment: null, nextState: charBase };
|