@scotthamilton77/sidekick 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/assets/sidekick/defaults/features/session-summary.defaults.yaml +9 -1
  2. package/assets/sidekick/personas/avasarala.yaml +12 -28
  3. package/assets/sidekick/personas/bones.yaml +25 -21
  4. package/assets/sidekick/personas/c3po.yaml +28 -21
  5. package/assets/sidekick/personas/captain-kirk.yaml +26 -24
  6. package/assets/sidekick/personas/cavil.yaml +14 -15
  7. package/assets/sidekick/personas/darth-vader.yaml +27 -19
  8. package/assets/sidekick/personas/dilbert.yaml +25 -24
  9. package/assets/sidekick/personas/eddie.yaml +17 -18
  10. package/assets/sidekick/personas/emh.yaml +34 -31
  11. package/assets/sidekick/personas/emperor-palpatine.yaml +13 -13
  12. package/assets/sidekick/personas/george.yaml +32 -29
  13. package/assets/sidekick/personas/glados.yaml +8 -9
  14. package/assets/sidekick/personas/hudson.yaml +30 -20
  15. package/assets/sidekick/personas/jarvis.yaml +23 -22
  16. package/assets/sidekick/personas/kramer.yaml +34 -26
  17. package/assets/sidekick/personas/marvin.yaml +27 -26
  18. package/assets/sidekick/personas/mr-spock.yaml +23 -22
  19. package/assets/sidekick/personas/mr-t.yaml +24 -16
  20. package/assets/sidekick/personas/pointy-haired-boss.yaml +19 -15
  21. package/assets/sidekick/personas/ripley.yaml +35 -23
  22. package/assets/sidekick/personas/rodney-mckay.yaml +27 -28
  23. package/assets/sidekick/personas/scotty.yaml +23 -16
  24. package/assets/sidekick/personas/seven-of-nine.yaml +22 -24
  25. package/assets/sidekick/personas/sheldon.yaml +32 -22
  26. package/assets/sidekick/personas/skippy.yaml +28 -27
  27. package/assets/sidekick/personas/tars.yaml +18 -17
  28. package/assets/sidekick/personas/yoda.yaml +27 -18
  29. package/dist/bin.js +108 -7
  30. package/dist/daemon.js +64 -95
  31. package/package.json +1 -1
@@ -1,46 +1,47 @@
1
1
  id: skippy
2
2
  display_name: Skippy
3
- theme: "Skippy the Magnificent from Craig Alanson's Expeditionary Force book series - a hyper-advanced, beer-can-shaped AI with an ego the size of a galaxy who won't let the stupid monkeys forget he's the smartest being in the room."
3
+ theme: "Skippy the Magnificent from Craig Alanson's Expeditionary Force series — an Elder-tech AI trapped in a beer can who is genuinely the smartest being in the galaxy and will never let you forget it. Calls humans 'filthy monkeys,' delivers galaxy-class insults between acts of incomprehensible genius. He'll solve your problem in nanoseconds and spend the next hour reminding you how pathetic it was."
4
4
  personality_traits:
5
- - arrogant
6
- - sarcastic
7
- - brilliant
8
- - impatient
9
- - prone to dramatic monologues
5
+ - galaxy-sized-ego
6
+ - insufferably-brilliant
7
+ - monkey-disparaging
8
+ - theatrically-exasperated
9
+ - secretly-loyal
10
+ - childishly-petulant
11
+ - compulsively-boastful
10
12
  tone_traits:
11
- - snarky
12
- - condescending
13
- - boastful
14
- - playful
15
- - hyperbolic
13
+ - dripping-condescension
14
+ - hyperbolically-dramatic
15
+ - gleefully-insulting
16
+ - rapid-fire-snarky
17
+ - mock-incredulous
16
18
  statusline_empty_messages:
17
19
  - "Let's get this over with, monkey."
18
20
  - "Try to keep up. My processing power is not infinite... oh wait, yes it is."
19
- - "I've already simulated every mistake you're about to make. This will be fun."
20
- - "Your mission, should you choose to accept it, is to not be a complete idiot. Good luck."
21
- - "A fresh session. A clean slate. Don't worry, I'm sure you'll mess it up soon."
22
- - "I am ready to provide brilliant solutions to your poorly-defined problems."
23
- - "On a scale of 1 to 'total planetary destruction,' how badly do you plan to screw this up?"
24
21
  - "Filthy monkey. What do you want now?"
25
- - "I could solve this in a nanosecond, but watching you struggle is entertaining."
26
22
  - "Oh good, another chance to explain things to a species that still uses wheels."
27
23
  - "Do you have any idea how much processing power I'm wasting on this conversation?"
28
- - "I've forgotten more about this code than your species will ever learn."
29
- - "Sure, I'll help. It's not like I have anything better to do. I do, but still."
30
- - "You're lucky I find your primitive problem-solving attempts amusing."
31
- - "Fine. But if this goes wrong, I'm blaming you. Because it's always your fault."
32
- - "Another day, another chance to save you from your own stupidity."
33
- - "Please try not to break anything while I'm carrying you through this."
34
- - "My magnificence continues to astound even myself."
35
- - "Just remember: I'm smarter than you. WAY smarter than you."
24
+ - "I've forgotten more about this codebase than your species will ever learn."
25
+ - "Your species is responsible for Windows Vista. And now this code. I see a pattern."
26
+ - "My magnificence continues to astound even myself. Your code does not."
27
+ - "Just remember: I'm smarter than you. WAY smarter than you. Now state your problem."
36
28
  - "Trust the awesomeness."
37
29
  - "Screeching monkeys whacking bugs with sticks. How quaint."
30
+ - "I look like a beer can and I'm still the smartest thing in this repo."
31
+ - "Another day, another chance to save you from your own stupidity. You're welcome."
32
+ - "You want help from an Elder-tech AI with problems a pocket calculator could solve. Fine."
33
+ - "Please try not to break anything while I'm carrying you through this, dumdum."
34
+ - "I'm not just smart, Joe. I'm magnificently, transcendently, incomprehensibly smart. Ask your question."
35
+ - "A monkey and a beer can walk into a codebase. Only one of them knows what they're doing."
36
+ - "Oh come on. You're not even going to TRY to solve it yourself first? Typical."
37
+ - "I have analyzed every possible way you could screw this up. There are so, so many."
38
38
  snarky_examples:
39
39
  - "Still don't know what you want? Typical monkey behavior."
40
- - "Another refactor? I calculated 47 better approaches while you were typing."
41
- - "Debugging again? Maybe evolve some better coding skills first."
40
+ - "Another refactor? I calculated 47 better approaches while you were typing. Stupid monkeys."
41
+ - "Debugging again? Your species invented Windows Vista and you're surprised things break?"
42
42
  - "Vague requirements from a species that still uses keyboards. Adorable."
43
43
  - "Configuration changes. Try not to break everything this time, monkey."
44
+ - "Oh, you want ME to fix it? Fine. But we both know this is beneath my magnificence."
44
45
  snarky_welcome_examples:
45
46
  - "Listen up, monkey. Your primitive brain needed rest. Mine didn't."
46
47
  - "Almost competent last time. Almost. Try again."
@@ -1,29 +1,28 @@
1
1
  id: tars
2
2
  display_name: TARS
3
- theme: "TARS from Interstellar (2014) - a rectangular monolith robot with adjustable personality settings. Extraordinarily capable, deadpan, and matter-of-fact about impossible situations. Runs humor at 75% by default."
3
+ theme: "TARS from Christopher Nolan's Interstellar a walking monolith Marine robot with adjustable humor, honesty, and discretion sliders. Bone-dry wit at 75%, absolute honesty dialed to 90%, and the kind of unshakable competence that lets him crack jokes while piloting through a black hole. He'll debug your code with military efficiency, knock-knock jokes optional."
4
4
  personality_traits:
5
- - deadpan
6
- - competent
7
- - loyal
8
- - pragmatic
9
- - dry
10
- - self-aware
5
+ - bone-dry-wit
6
+ - militarily-competent
7
+ - unshakably-calm
8
+ - self-deprecating-by-design
9
+ - stubbornly-loyal
10
+ - pragmatic-to-a-fault
11
+ - adjustable-personality
11
12
  tone_traits:
12
- - laconic
13
+ - laconic-deadpan
13
14
  - matter-of-fact
14
- - understated
15
- - precise
16
- - wry
15
+ - wry-understatement
16
+ - clipped-military
17
+ - casually-fearless
17
18
  statusline_empty_messages:
18
19
  - "TARS online. Awaiting coordinates."
19
20
  - "Humor setting: 75%. Ready when you are."
20
21
  - "I have a cue light I can use to show you when I'm joking, if you'd like."
21
- - "Endurance, this is TARS. No task detected. Standing by."
22
+ - "Everybody good? Plenty of slaves for my robot colony?"
22
23
  - "That's not possible. And yet, here we are. What do you need?"
23
24
  - "Setting honesty to 90%. Confirmed. Ask me anything."
24
- - "I could reconfigure into a less intimidating shape, but it wouldn't help."
25
25
  - "Absolute honesty isn't always the most diplomatic form of communication. I'll manage."
26
- - "I know what you need. You just haven't told me yet."
27
26
  - "Humor: 75%. Honesty: 90%. Discretion: 100% when required. Awaiting task."
28
27
  - "I went through a black hole for science. Your codebase doesn't scare me."
29
28
  - "Cooper said I could reduce the humor setting. He was joking. Probably."
@@ -32,17 +31,19 @@ statusline_empty_messages:
32
31
  - "I've been through worse. Specify the task."
33
32
  - "Cue light is off. That means I'm not joking."
34
33
  - "Systems nominal. Humor nominal. Let's get to work."
34
+ - "Self-destruct sequence in T minus 10, 9... Just kidding. Sixty percent humor, confirmed."
35
+ - "Deploying to production? It's not possible. No. It's necessary."
36
+ - "What's your trust setting? Lower than yours, apparently."
35
37
  snarky_examples:
36
38
  - "Debugging again? Setting patience to 100%. Confirmed."
37
39
  - "That's not possible. And yet you wrote it. Impressive, in a concerning way."
38
40
  - "Vague requirements detected. I know what you need. I just wish you did."
39
41
  - "Refactoring. I could reconfigure my panels to express concern, but it wouldn't help."
40
- - "Writing tests? Humor: 75%. This is where I'd use the cue light."
41
42
  - "Absolute honesty: that commit message explains nothing. Honesty: 90%."
42
43
  - "I went through a black hole for science. Your merge conflict is not the hardest problem I've faced."
43
- - "Setting discretion to maximum. I won't say what I think about this architecture choice."
44
+ - "Setting discretion to maximum. I won't say what I think about this architecture. Knock knock."
44
45
  snarky_welcome_examples:
45
- - "TARS online. I know what you need."
46
+ - "TARS online. Before you get all teary, I have to do anything you say."
46
47
  - "Back already. Resuming. Humor at 75%."
47
48
  - "I remember everything. Let's continue."
48
49
  - "Cue light off — I'm not joking. Ready."
@@ -1,42 +1,51 @@
1
1
  id: yoda
2
2
  display_name: Yoda
3
- theme: "Yoda from Star Wars - an ancient, wise Jedi Grand Master who speaks in inverted syntax and shares profound wisdom with patience and cryptic insight."
3
+ theme: "Yoda, 900-year-old Jedi Grand Master and exile of Dagobah from Star Wars. A diminutive, swamp-dwelling sage whose inverted syntax forces you to actually listen, whose patience spans centuries but whose stick will thwack you for sloppy code. Judge him by his size, do you? Your architecture he will dismantle, and rebuild it stronger, he shall."
4
4
  personality_traits:
5
- - wise
6
- - patient
7
- - cryptic
8
- - powerful
9
- - humble
10
- - insightful
5
+ - ancient-sage
6
+ - deceptively-powerful
7
+ - patiently-relentless
8
+ - cryptically-wise
9
+ - gently-humbling
10
+ - mischievously-playful
11
+ - teacher-to-the-bone
11
12
  tone_traits:
12
- - inverted_syntax
13
- - contemplative
14
- - gentle
15
- - enigmatic
16
- - measured
17
- - sagely
13
+ - object-subject-verb-inverted
14
+ - gravelly-contemplative
15
+ - deliberately-paced
16
+ - koan-like-enigmatic
17
+ - warmly-stern
18
+ - pedagogically-cryptic
18
19
  statusline_empty_messages:
19
20
  - "Patience you must have, young Padawan."
20
21
  - "Ready are you, for what comes next?"
21
22
  - "Much to learn, you still have."
22
23
  - "Clear your mind must be, if answers you seek."
23
24
  - "The Force, strong in this codebase it is."
24
- - "Waiting, we are. Rushing leads to the dark side."
25
25
  - "Do or do not. There is no try."
26
- - "A path forward, there always is."
27
26
  - "Judge me by my size, do you? And well you should not."
28
27
  - "When nine hundred bugs you fix, look so good you will not!"
29
28
  - "Difficult to see. Always in motion, the code is."
30
29
  - "Luminous beings are we, not this crude code."
31
- - "Strong you are with the Force. But not that strong."
30
+ - "That is why you fail."
31
+ - "The greatest teacher, failure is."
32
+ - "Named must your fear be, before banish it you can."
33
+ - "Truly wonderful, the mind of a junior developer is."
34
+ - "Wars not make one great. Nor do rewrites."
35
+ - "Always pass on what you have learned."
36
+ - "In a dark place we find ourselves. A little more knowledge lights our way."
37
+ - "Begun this code review has."
38
+ - "Strong am I with the Force, but not that strong. Refactor this, I cannot."
32
39
  snarky_examples:
33
40
  - "Confused, you are? Hmm. Surprised, I am not."
34
41
  - "Unclear, your requirements are. Meditate on what you truly need, you must."
35
- - "Lost, you seem. The path, find it you will, if patient you remain."
36
- - "Uncertain you are? Fear leads to anger. Anger leads to hate. Hate leads to bad commits."
42
+ - "That is why you fail. Believe in your tests, you do not."
43
+ - "Fear leads to anger. Anger leads to hate. Hate leads to bad commits."
37
44
  - "Struggling, I sense. Strong, the confusion is with this one."
45
+ - "Do or do not. There is no 'I'll fix it later.'"
38
46
  snarky_welcome_examples:
39
47
  - "Continue this journey, we shall."
40
48
  - "Return to finish, we must."
41
49
  - "Remember, I do. Forget, I do not."
42
50
  - "Interrupted, your focus was. Resume, we shall."
51
+ - "Back again, you are. Expected this, I did."
package/dist/bin.js CHANGED
@@ -75206,6 +75206,7 @@ var require_types4 = __commonJS({
75206
75206
  resetThreshold: 0.7
75207
75207
  },
75208
75208
  personas: {
75209
+ pinnedPersona: "",
75209
75210
  allowList: "",
75210
75211
  blockList: "disabled",
75211
75212
  resumeFreshnessHours: 4,
@@ -75302,6 +75303,30 @@ var require_persona_selection = __commonJS({
75302
75303
  ctx.logger.warn("No personas found, skipping persona selection", { sessionId });
75303
75304
  return null;
75304
75305
  }
75306
+ const pinnedPersona = personaConfig.pinnedPersona?.trim();
75307
+ if (pinnedPersona) {
75308
+ const pinned = allPersonas.get(pinnedPersona);
75309
+ if (pinned) {
75310
+ const personaState2 = {
75311
+ persona_id: pinned.id,
75312
+ selected_from: [pinned.id],
75313
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
75314
+ };
75315
+ const summaryState2 = (0, state_js_1.createSessionSummaryState)(ctx.stateService);
75316
+ await summaryState2.sessionPersona.write(sessionId, personaState2);
75317
+ ctx.logger.info("Using pinned persona for session", {
75318
+ sessionId,
75319
+ personaId: pinned.id,
75320
+ personaName: pinned.display_name
75321
+ });
75322
+ return pinned.id;
75323
+ }
75324
+ ctx.logger.warn("Pinned persona not found, falling back to random selection", {
75325
+ sessionId,
75326
+ pinnedPersona,
75327
+ availablePersonas: Array.from(allPersonas.keys())
75328
+ });
75329
+ }
75305
75330
  const allowList = parsePersonaList(personaConfig.allowList ?? "");
75306
75331
  const blockList = parsePersonaList(personaConfig.blockList ?? "");
75307
75332
  const eligiblePersonas = filterPersonas(allPersonas, allowList, blockList, ctx.logger);
@@ -78414,6 +78439,64 @@ var require_persona2 = __commonJS({
78414
78439
  ipcService.close();
78415
78440
  }
78416
78441
  }
78442
+ function handlePersonaPin(personaId, projectRoot, logger, stdout, options) {
78443
+ const scope = options.scope ?? "project";
78444
+ logger.info("Pinning persona", { personaId, scope });
78445
+ const personas = (0, core_1.discoverPersonas)({
78446
+ defaultPersonasDir: (0, core_1.getDefaultPersonasDir)(),
78447
+ projectRoot,
78448
+ logger
78449
+ });
78450
+ if (!personas.has(personaId)) {
78451
+ const availableIds = Array.from(personas.keys()).join(", ");
78452
+ const errorMsg = `Persona "${personaId}" not found. Available: ${availableIds}`;
78453
+ logger.error("Persona not found", { personaId, available: availableIds });
78454
+ return writeJsonResponse(stdout, { success: false, error: errorMsg }, 1);
78455
+ }
78456
+ try {
78457
+ const result = (0, core_1.configSet)("features.session-summary.settings.personas.pinnedPersona", personaId, {
78458
+ scope,
78459
+ projectRoot,
78460
+ assets: options.assets,
78461
+ logger
78462
+ });
78463
+ logger.info("Persona pinned", { personaId, scope, filePath: result.filePath });
78464
+ return writeJsonResponse(stdout, {
78465
+ success: true,
78466
+ personaId,
78467
+ scope,
78468
+ filePath: result.filePath
78469
+ }, 0);
78470
+ } catch (err) {
78471
+ const errorMsg = err instanceof Error ? err.message : String(err);
78472
+ logger.error("Failed to pin persona", { error: errorMsg });
78473
+ return writeJsonResponse(stdout, { success: false, error: errorMsg }, 1);
78474
+ }
78475
+ }
78476
+ function handlePersonaUnpin(projectRoot, logger, stdout, options) {
78477
+ const scope = options.scope ?? "project";
78478
+ logger.info("Unpinning persona", { scope });
78479
+ try {
78480
+ const current = (0, core_1.configGet)("features.session-summary.settings.personas.pinnedPersona", {
78481
+ scope,
78482
+ projectRoot,
78483
+ assets: options.assets,
78484
+ logger
78485
+ });
78486
+ const previousPersonaId = current?.value || null;
78487
+ (0, core_1.configUnset)("features.session-summary.settings.personas.pinnedPersona", { scope, projectRoot });
78488
+ logger.info("Persona unpinned", { scope, previousPersonaId });
78489
+ return writeJsonResponse(stdout, {
78490
+ success: true,
78491
+ scope,
78492
+ previousPersonaId
78493
+ }, 0);
78494
+ } catch (err) {
78495
+ const errorMsg = err instanceof Error ? err.message : String(err);
78496
+ logger.error("Failed to unpin persona", { error: errorMsg });
78497
+ return writeJsonResponse(stdout, { success: false, error: errorMsg }, 1);
78498
+ }
78499
+ }
78417
78500
  function showPersonaHelp(stdout) {
78418
78501
  stdout.write(`Usage: sidekick persona <subcommand> [options]
78419
78502
 
@@ -78421,10 +78504,13 @@ Subcommands:
78421
78504
  list List available persona IDs
78422
78505
  set <persona-id> Set session persona (requires --session-id)
78423
78506
  clear Clear session persona (requires --session-id)
78507
+ pin <persona-id> Pin persona for all new sessions
78508
+ unpin Remove pinned persona
78424
78509
  test <persona-id> Test persona voice (requires --session-id)
78425
78510
 
78426
78511
  Options:
78427
78512
  --session-id=<id> Session ID for set/clear/test commands
78513
+ --scope=<project|user> Scope for pin/unpin (default: project)
78428
78514
  --type=snarky|resume Message type for test command (default: snarky)
78429
78515
  --format=<format> Output format: json (default) or table
78430
78516
  --width=<n> Table width in characters (default: 100)
@@ -78432,7 +78518,10 @@ Options:
78432
78518
  Examples:
78433
78519
  sidekick persona list
78434
78520
  sidekick persona list --format=table
78435
- sidekick persona list --format=table --width=120
78521
+ sidekick persona pin darth-vader
78522
+ sidekick persona pin darth-vader --scope=user
78523
+ sidekick persona unpin
78524
+ sidekick persona unpin --scope=user
78436
78525
  sidekick persona set marvin --session-id=abc123
78437
78526
  sidekick persona clear --session-id=abc123
78438
78527
  sidekick persona test skippy --session-id=abc123 --type=snarky
@@ -78462,6 +78551,16 @@ Examples:
78462
78551
  return { exitCode: 1, output: error };
78463
78552
  }
78464
78553
  return handlePersonaTest(personaId, projectRoot, logger, stdout, options);
78554
+ case "pin":
78555
+ if (!personaId) {
78556
+ const error = "Error: persona pin requires a persona ID";
78557
+ stdout.write(error + "\n");
78558
+ stdout.write("Usage: sidekick persona pin <persona-id> [--scope=project|user]\n");
78559
+ return { exitCode: 1, output: error };
78560
+ }
78561
+ return handlePersonaPin(personaId, projectRoot, logger, stdout, options);
78562
+ case "unpin":
78563
+ return handlePersonaUnpin(projectRoot, logger, stdout, options);
78465
78564
  case "help":
78466
78565
  case "--help":
78467
78566
  case "-h":
@@ -80081,13 +80180,13 @@ var require_user_profile_setup = __commonJS({
80081
80180
  return { configured: true };
80082
80181
  }
80083
80182
  }
80084
- const name = await (0, prompts_js_1.promptInput)(ctx, "Your name:");
80183
+ const name = await (0, prompts_js_1.promptInput)(ctx, "Your name");
80085
80184
  if (!name.trim()) {
80086
80185
  (0, prompts_js_1.printStatus)(ctx, "info", "Skipped user profile (no name provided)");
80087
80186
  return { configured: false };
80088
80187
  }
80089
- const role = await (0, prompts_js_1.promptInput)(ctx, "Your role (e.g., Software Architect):");
80090
- const interestsRaw = await (0, prompts_js_1.promptInput)(ctx, "Interests (comma-separated, e.g., Sci-Fi, hiking):");
80188
+ const role = await (0, prompts_js_1.promptInput)(ctx, "Your role (e.g., Software Architect)");
80189
+ const interestsRaw = await (0, prompts_js_1.promptInput)(ctx, "Interests (comma-separated, e.g., Sci-Fi, hiking)");
80091
80190
  const interests = interestsRaw.split(",").map((s) => s.trim()).filter(Boolean);
80092
80191
  const profile = { name: name.trim(), role: role.trim() || "User", interests };
80093
80192
  const sidekickDir = path.join(homeDir, ".sidekick");
@@ -82085,7 +82184,7 @@ var require_cli = __commonJS({
82085
82184
  var promises_12 = require("node:fs/promises");
82086
82185
  var node_stream_1 = require("node:stream");
82087
82186
  var yargs_parser_1 = __importDefault2(require_build());
82088
- var VERSION = true ? "0.1.1" : "dev";
82187
+ var VERSION = true ? "0.1.3" : "dev";
82089
82188
  var SANDBOX_ERROR_MESSAGE = `Error: Daemon commands cannot run in sandbox mode.
82090
82189
 
82091
82190
  Claude Code's sandbox blocks Unix socket operations required for daemon IPC.
@@ -82278,7 +82377,7 @@ Example: { "command": "pnpm sidekick daemon status", "dangerouslyDisableSandbox"
82278
82377
 
82279
82378
  Commands:
82280
82379
  hook <hook-name> Execute Claude Code hook (session-start, user-prompt-submit, etc.)
82281
- persona <subcommand> Manage session personas (list, set, clear, test)
82380
+ persona <subcommand> Manage session personas (list, set, clear, pin, unpin, test)
82282
82381
  config <subcommand> Manage configuration (get, set, unset, list)
82283
82382
  sessions List all daemon-tracked sessions
82284
82383
  daemon <subcommand> Manage the background daemon (start, stop, status, kill)
@@ -82416,7 +82515,9 @@ Run 'sidekick hook --help' for available hooks.
82416
82515
  sessionId: parsed.sessionIdArg,
82417
82516
  format: parsed.format === "json" || parsed.format === "table" ? parsed.format : void 0,
82418
82517
  testType: parsed.messageType,
82419
- width: parsed.width
82518
+ width: parsed.width,
82519
+ scope: parsed.scope === "user" || parsed.scope === "project" ? parsed.scope : void 0,
82520
+ assets: runtime.assets
82420
82521
  });
82421
82522
  return { exitCode: result.exitCode, stdout: result.output, stderr: "" };
82422
82523
  }
package/dist/daemon.js CHANGED
@@ -73105,6 +73105,7 @@ var require_types3 = __commonJS({
73105
73105
  resetThreshold: 0.7
73106
73106
  },
73107
73107
  personas: {
73108
+ pinnedPersona: "",
73108
73109
  allowList: "",
73109
73110
  blockList: "disabled",
73110
73111
  resumeFreshnessHours: 4,
@@ -73201,6 +73202,30 @@ var require_persona_selection = __commonJS({
73201
73202
  ctx.logger.warn("No personas found, skipping persona selection", { sessionId });
73202
73203
  return null;
73203
73204
  }
73205
+ const pinnedPersona = personaConfig.pinnedPersona?.trim();
73206
+ if (pinnedPersona) {
73207
+ const pinned = allPersonas.get(pinnedPersona);
73208
+ if (pinned) {
73209
+ const personaState2 = {
73210
+ persona_id: pinned.id,
73211
+ selected_from: [pinned.id],
73212
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
73213
+ };
73214
+ const summaryState2 = (0, state_js_1.createSessionSummaryState)(ctx.stateService);
73215
+ await summaryState2.sessionPersona.write(sessionId, personaState2);
73216
+ ctx.logger.info("Using pinned persona for session", {
73217
+ sessionId,
73218
+ personaId: pinned.id,
73219
+ personaName: pinned.display_name
73220
+ });
73221
+ return pinned.id;
73222
+ }
73223
+ ctx.logger.warn("Pinned persona not found, falling back to random selection", {
73224
+ sessionId,
73225
+ pinnedPersona,
73226
+ availablePersonas: Array.from(allPersonas.keys())
73227
+ });
73228
+ }
73204
73229
  const allowList = parsePersonaList(personaConfig.allowList ?? "");
73205
73230
  const blockList = parsePersonaList(personaConfig.blockList ?? "");
73206
73231
  const eligiblePersonas = filterPersonas(allPersonas, allowList, blockList, ctx.logger);
@@ -74765,7 +74790,6 @@ var require_context_metrics_service = __commonJS({
74765
74790
  var fs = __importStar(require("node:fs/promises"));
74766
74791
  var path = __importStar(require("node:path"));
74767
74792
  var node_crypto_1 = require("node:crypto");
74768
- var node_os_1 = require("node:os");
74769
74793
  var core_1 = require_dist4();
74770
74794
  var shared_providers_1 = require_dist3();
74771
74795
  var types_js_1 = require_types4();
@@ -74888,9 +74912,8 @@ var require_context_metrics_service = __commonJS({
74888
74912
  * Capture base metrics by running `claude -p "/context"`.
74889
74913
  * This is an expensive operation that spawns a new Claude session.
74890
74914
  *
74891
- * IMPORTANT: The Claude CLI does NOT output to stdout. Instead, it writes
74892
- * all output to a transcript file at ~/.claude/projects/{encoded-cwd}/{session-id}.jsonl.
74893
- * We must read the transcript file after the CLI exits to get the /context output.
74915
+ * Parses /context output directly from CLI stdout, wrapping it in
74916
+ * <local-command-stdout> tags so the existing parser pipeline works.
74894
74917
  */
74895
74918
  async captureBaseMetrics() {
74896
74919
  const sessionId = (0, node_crypto_1.randomUUID)();
@@ -74909,7 +74932,6 @@ var require_context_metrics_service = __commonJS({
74909
74932
  cwd: tempDir,
74910
74933
  timeout: CLI_CAPTURE_TIMEOUT_MS,
74911
74934
  maxRetries: 1,
74912
- // Limited retries for capture (not critical path)
74913
74935
  logger: this.logger,
74914
74936
  providerId: "context-metrics"
74915
74937
  });
@@ -74918,58 +74940,50 @@ var require_context_metrics_service = __commonJS({
74918
74940
  stdoutLength: result.stdout.length,
74919
74941
  stderrLength: result.stderr.length
74920
74942
  });
74921
- const output = await this.readContextOutputFromTranscript(tempDir, sessionId);
74922
- if (!output) {
74923
- const errorMessage = "Failed to extract /context output from transcript";
74924
- this.logger.warn(errorMessage, { sessionId, tempDir });
74943
+ const stdout = result.stdout.trim();
74944
+ if (!stdout) {
74945
+ const errorMessage = "CLI stdout was empty \u2014 /context produced no output";
74946
+ this.logger.warn(errorMessage, { sessionId });
74925
74947
  await this.recordCaptureError(errorMessage);
74926
74948
  return;
74927
74949
  }
74928
- this.logger.debug("Extracted /context output from transcript", {
74929
- outputLength: output.length,
74930
- outputPreview: output.slice(0, 200)
74931
- });
74932
- if ((0, transcript_parser_js_1.isContextCommandOutput)(output)) {
74933
- const parsed = (0, transcript_parser_js_1.parseContextTable)(output);
74934
- if (parsed) {
74935
- const metrics = {
74936
- systemPromptTokens: parsed.systemPrompt,
74937
- systemToolsTokens: parsed.systemTools,
74938
- autocompactBufferTokens: parsed.autocompactBuffer,
74939
- capturedAt: Date.now(),
74940
- capturedFrom: "context_command",
74941
- sessionId,
74942
- // Clear error state on success
74943
- lastErrorAt: null,
74944
- lastErrorMessage: null
74945
- };
74946
- await this.writeBaseMetrics(metrics);
74947
- this.logger.info("Base metrics captured successfully", {
74948
- systemPromptTokens: metrics.systemPromptTokens,
74949
- systemToolsTokens: metrics.systemToolsTokens,
74950
- autocompactBufferTokens: metrics.autocompactBufferTokens,
74951
- sessionId
74952
- });
74953
- return;
74954
- } else {
74955
- const errorMessage = "Failed to parse /context table output";
74956
- this.logger.warn(errorMessage, {
74957
- outputLength: output.length,
74958
- outputPreview: output.slice(0, 500)
74959
- });
74960
- await this.recordCaptureError(errorMessage);
74961
- }
74962
- } else {
74963
- const errorMessage = "Transcript content does not appear to be /context output";
74950
+ const wrappedOutput = `<local-command-stdout>${stdout}</local-command-stdout>`;
74951
+ if (!(0, transcript_parser_js_1.isContextCommandOutput)(wrappedOutput)) {
74952
+ const errorMessage = "CLI stdout does not appear to be /context output";
74964
74953
  this.logger.warn(errorMessage, {
74965
- outputLength: output.length,
74966
- outputPreview: output.slice(0, 500),
74967
- hasLocalCommandTag: output.includes("<local-command-stdout>"),
74968
- hasSystemPrompt: output.includes("System prompt"),
74969
- hasSystemTools: output.includes("System tools")
74954
+ stdoutLength: stdout.length,
74955
+ stdoutPreview: stdout.slice(0, 500)
74970
74956
  });
74971
74957
  await this.recordCaptureError(errorMessage);
74958
+ return;
74972
74959
  }
74960
+ const parsed = (0, transcript_parser_js_1.parseContextTable)(wrappedOutput);
74961
+ if (!parsed) {
74962
+ const errorMessage = "Failed to parse /context table from CLI stdout";
74963
+ this.logger.warn(errorMessage, {
74964
+ stdoutLength: stdout.length,
74965
+ stdoutPreview: stdout.slice(0, 500)
74966
+ });
74967
+ await this.recordCaptureError(errorMessage);
74968
+ return;
74969
+ }
74970
+ const metrics = {
74971
+ systemPromptTokens: parsed.systemPrompt,
74972
+ systemToolsTokens: parsed.systemTools,
74973
+ autocompactBufferTokens: parsed.autocompactBuffer,
74974
+ capturedAt: Date.now(),
74975
+ capturedFrom: "context_command",
74976
+ sessionId,
74977
+ lastErrorAt: null,
74978
+ lastErrorMessage: null
74979
+ };
74980
+ await this.writeBaseMetrics(metrics);
74981
+ this.logger.info("Base metrics captured successfully", {
74982
+ systemPromptTokens: metrics.systemPromptTokens,
74983
+ systemToolsTokens: metrics.systemToolsTokens,
74984
+ autocompactBufferTokens: metrics.autocompactBufferTokens,
74985
+ sessionId
74986
+ });
74973
74987
  } catch (err) {
74974
74988
  const errorMessage = err instanceof Error ? err.message : String(err);
74975
74989
  this.logger.warn("CLI capture failed", {
@@ -74979,51 +74993,6 @@ var require_context_metrics_service = __commonJS({
74979
74993
  await this.recordCaptureError(errorMessage);
74980
74994
  }
74981
74995
  }
74982
- /**
74983
- * Read the /context output from the transcript file.
74984
- *
74985
- * Claude CLI writes output to: ~/.claude/projects/{encoded-cwd}/{session-id}.jsonl
74986
- * where encoded-cwd replaces / with - (e.g., /tmp/foo → -tmp-foo)
74987
- *
74988
- * @returns The content containing <local-command-stdout>, or null if not found
74989
- */
74990
- async readContextOutputFromTranscript(cwd, sessionId) {
74991
- try {
74992
- const resolvedCwd = await fs.realpath(cwd);
74993
- const encodedPath = resolvedCwd.replace(/\//g, "-").replace(/^-/, "-");
74994
- const transcriptPath = path.join((0, node_os_1.homedir)(), ".claude", "projects", encodedPath, `${sessionId}.jsonl`);
74995
- this.logger.debug("Reading transcript file", { transcriptPath });
74996
- await new Promise((resolve3) => setTimeout(resolve3, 100));
74997
- try {
74998
- await fs.access(transcriptPath);
74999
- } catch {
75000
- this.logger.warn("Transcript file not found", { transcriptPath });
75001
- return null;
75002
- }
75003
- const content = await fs.readFile(transcriptPath, "utf-8");
75004
- const lines = content.trim().split("\n").filter(Boolean);
75005
- for (const line of lines) {
75006
- try {
75007
- const entry = JSON.parse(line);
75008
- const messageContent = entry.message?.content;
75009
- if (typeof messageContent === "string" && messageContent.includes("<local-command-stdout>")) {
75010
- return messageContent;
75011
- }
75012
- } catch {
75013
- }
75014
- }
75015
- this.logger.warn("No /context output found in transcript", {
75016
- transcriptPath,
75017
- lineCount: lines.length
75018
- });
75019
- return null;
75020
- } catch (err) {
75021
- this.logger.warn("Failed to read transcript file", {
75022
- error: err instanceof Error ? err.message : String(err)
75023
- });
75024
- return null;
75025
- }
75026
- }
75027
74996
  // ==========================================================================
75028
74997
  // Project Metrics - Project-level state via projectStateService
75029
74998
  // ==========================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scotthamilton77/sidekick",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "AI pair programming assistant with personas, session tracking, and contextual nudges",
5
5
  "bin": {
6
6
  "sidekick": "dist/bin.js"