@litfamily/litclaude 1.0.23 → 1.0.25

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.25 - 2026-10-12
4
+
5
+ - The LIT card now moves while LitClaude is at work, so a glance tells you it is busy. The LIT pill and the border slowly glow from pink to flame orange and back, a light passes over the current phase and the skill chips of this request, and a spark runs along the top edge and back along the bottom one. When the turn ends the card goes still again. With the `motion` setting at `subtle` only the glow stays, and `off` keeps the card still.
6
+ - The card is wider. It now grows with the terminal up to 120 columns instead of 80.
7
+ - The bottom edge shows the weekly usage window next to the five-hour one, as the status line does.
8
+ - The installer sets up the Claude desktop app too. The app's Code tab reads the same settings as the terminal but has no editor for the card's options, so the installer now writes the defaults for `motion`, `glyphs` and `band`, keeping any value you already chose. The new `litclaude-ai config` command shows them, and `litclaude-ai config <option> <value>` changes one from a terminal. Uninstall removes only the defaults it wrote that you never changed.
9
+ - The README demo film now opens on a title card and names each step with a numbered two-line caption, and the card pictures show the new width and the weekly window.
10
+
11
+ ## 1.0.24 - 2026-10-12
12
+
13
+ - The LIT card no longer drops to idle while background work is still running. When the main turn ends after starting background subagents or a workflow, the card stays at work and counts the running agents until the last one finishes, and it never reads `DONE` while an agent is still busy. A workflow's agents now count too, and a subagent that finishes in the middle of a turn no longer dims the chips of the request that is still running. An agent that never reports back stops counting after 30 quiet minutes, so it cannot hold the card at work.
14
+ - The README shows the LIT card as it really appears: resting before anything runs, at work with the skill's chip lit, and after the turn, when the chip dims into the recent list.
15
+ - The README walks through automatic handoff with terminal pictures: turning it on with `lit-handoff auto on 65`, checking and turning it off, the reminder to run `/compact`, and the two shell variables that switch it on before Claude Code starts.
16
+ - The install section has a prompt you can paste into a coding agent so it installs LitClaude for you. The agent shows what would change and waits for your yes, runs the doctor, and reminds you to restart Claude Code.
17
+ - The office runtime now pins sharp 0.35.5, and the README Studio cover templates use sharp 0.35.5 and source-map-js 1.2.2.
18
+
3
19
  ## 1.0.23 - 2026-10-12
4
20
 
5
21
  - The LIT card above the prompt now stays on screen for the whole session, so you can still see the status after the work ends. It is a small four-row card in the colours of your status line. The top edge names the workflow and the current phase. Below it are the plan, work and review steps with the progress of the plan or goal. Every LitClaude skill that runs gets its own chip, several at once. A chip is bright while its request is running and dims into a recent list when the turn ends. At the bottom are the context use, the five-hour usage window and the Git branch. The card fits narrow terminals by leaving out the least important parts and never wraps. The `band` setting now defaults to `always`; `auto` brings back the old behaviour, and `off` hides the card.
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
- <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/docs/assets/cover-motion.webp" width="100%" alt="LitFamily motion cover: five armored robots power on one by one, the LitClaude robot wakes with glowing eyes and a lit frame, then LITFAMILY and KEEP THE WORK LIT. light up." /></picture></p>
1
+ <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/assets/cover-motion.webp" width="100%" alt="LitFamily motion cover: five armored robots power on one by one, the LitClaude robot wakes with glowing eyes and a lit frame, then LITFAMILY and KEEP THE WORK LIT. light up." /></picture></p>
2
2
 
3
3
  <h1 align="center">LitClaude</h1>
4
4
  <p align="center"><strong>Keep the work lit.</strong></p>
5
5
  <p align="center">A Claude Code plugin that helps every session plan the work, build it, check it, and leave a note the next session can pick up.</p>
6
6
  <p align="center">
7
- <img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/docs/assets/readme/badge-version.svg" alt="1.0.23" />
8
- <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/docs/assets/readme/badge-license.svg" alt="MIT license" /></a>
7
+ <img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/assets/readme/badge-version.svg" alt="1.0.25" />
8
+ <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/assets/readme/badge-license.svg" alt="MIT license" /></a>
9
9
  </p>
10
10
  <p align="center">
11
- <a href="https://github.com/wjgoarxiv/litclaude#readme">Full guide and skills gallery on GitHub</a> · <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/README_ko-KR.md">한국어</a>
11
+ <a href="https://github.com/wjgoarxiv/litclaude#readme">Full guide and skills gallery on GitHub</a> · <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/README_ko-KR.md">한국어</a>
12
12
  </p>
13
13
 
14
14
  Add `lit` to a Claude Code prompt and Claude pins the goal, writes a failing test first,
@@ -73,7 +73,7 @@ the `/plugin` menu.
73
73
  ## More
74
74
 
75
75
  - [Full guide and skills gallery on GitHub](https://github.com/wjgoarxiv/litclaude#readme)
76
- - [Privacy and network behavior](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/docs/privacy.md)
77
- - [Migration and ownership conflicts](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/docs/migration.md)
78
- - [Release history](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/CHANGELOG.md)
79
- - [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/LICENSE)
76
+ - [Privacy and network behavior](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/privacy.md)
77
+ - [Migration and ownership conflicts](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/migration.md)
78
+ - [Release history](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/CHANGELOG.md)
79
+ - [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/LICENSE)
package/README_ko-KR.md CHANGED
@@ -1,14 +1,14 @@
1
- <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/docs/assets/cover-motion.webp" width="100%" alt="LitFamily 모션 커버: 다섯 로봇 패널이 차례로 켜지고, LitClaude 로봇의 눈과 테두리가 빛난 뒤 LITFAMILY와 KEEP THE WORK LIT. 문구가 밝아지는 영상" /></picture></p>
1
+ <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/assets/cover-motion.webp" width="100%" alt="LitFamily 모션 커버: 다섯 로봇 패널이 차례로 켜지고, LitClaude 로봇의 눈과 테두리가 빛난 뒤 LITFAMILY와 KEEP THE WORK LIT. 문구가 밝아지는 영상" /></picture></p>
2
2
 
3
3
  <h1 align="center">LitClaude</h1>
4
4
  <p align="center"><strong>Keep the work lit.</strong></p>
5
5
  <p align="center">Claude Code에서 세션마다 계획하고, 만들고, 확인한 뒤 다음 세션이 이어받을 기록을 남기게 돕는 플러그인입니다.</p>
6
6
  <p align="center">
7
- <img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/docs/assets/readme/badge-version.svg" alt="1.0.23" />
8
- <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/docs/assets/readme/badge-license.svg" alt="MIT license" /></a>
7
+ <img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/assets/readme/badge-version.svg" alt="1.0.25" />
8
+ <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/assets/readme/badge-license.svg" alt="MIT license" /></a>
9
9
  </p>
10
10
  <p align="center">
11
- <a href="https://github.com/wjgoarxiv/litclaude/blob/main/README_ko-KR.md">전체 안내와 스킬 갤러리는 GitHub에서</a> · <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/README.md">English</a>
11
+ <a href="https://github.com/wjgoarxiv/litclaude/blob/main/README_ko-KR.md">전체 안내와 스킬 갤러리는 GitHub에서</a> · <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/README.md">English</a>
12
12
  </p>
13
13
 
14
14
  Claude Code 프롬프트에 `lit`을 붙이면 Claude가 목표를 고정하고, 실패하는 테스트를 먼저 쓰고,
@@ -71,7 +71,7 @@ LitClaude는 프로젝트 상태를 `.litclaude/`에 두고, 그 안의 `.gitign
71
71
  ## 더 보기
72
72
 
73
73
  - [전체 안내와 스킬 갤러리 (GitHub)](https://github.com/wjgoarxiv/litclaude/blob/main/README_ko-KR.md)
74
- - [개인정보와 네트워크 동작](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/docs/privacy.md)
75
- - [이전과 소유권 충돌](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/docs/migration.md)
76
- - [변경 이력](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/CHANGELOG.md)
77
- - [MIT 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.23/LICENSE)
74
+ - [개인정보와 네트워크 동작](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/privacy.md)
75
+ - [이전과 소유권 충돌](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/migration.md)
76
+ - [변경 이력](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/CHANGELOG.md)
77
+ - [MIT 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/LICENSE)
@@ -30,6 +30,7 @@ import {
30
30
  themeForAccent,
31
31
  } from "../plugins/litclaude/lib/hud-accent-palette.mjs";
32
32
  import { readPublicSource } from "../plugins/litclaude/lib/public-source-reader/reader.mjs";
33
+ import { MOD_CHOICES } from "../plugins/litclaude/lib/mod/settings.mjs";
33
34
  import { verifyBundledSkillsIntegrity } from "../plugins/litclaude/lib/bundled-skills-integrity.mjs";
34
35
  import { verifyCanonicalFrontendCorpus } from "../plugins/litclaude/lib/canonical-frontend-corpus.mjs";
35
36
  import { verifyCanonicalRuntimeClosures } from "../plugins/litclaude/lib/canonical-runtime-closures.mjs";
@@ -80,12 +81,16 @@ const prewarmMotionRuntime = () => {
80
81
  };
81
82
  const version = packageJson.version;
82
83
 
83
- const usage = `Usage: litclaude-ai [--dry-run] <install|doctor|path|run|update|uninstall|litgoal|wikify|workflow-check|start-work|start-work-next|public-read|motion-runtime> [...args]
84
+ const usage = `Usage: litclaude-ai [--dry-run] <install|doctor|config|path|run|update|uninstall|litgoal|wikify|workflow-check|start-work|start-work-next|public-read|motion-runtime> [...args]
84
85
  litclaude-ai --version
85
86
 
86
87
  Commands:
87
88
  install Register the packaged LitClaude plugin in Claude Code.
88
89
  doctor Validate the installed LitClaude plugin path.
90
+ config [<option> <value>]
91
+ Show or change the LIT card options (motion, glyphs, band) in
92
+ pluginConfigs, the same values /config edits. Works for the
93
+ desktop app too, which shows no editor for them.
89
94
  path Print the installed Claude plugin path.
90
95
  run -- ... Run Claude Code after the global plugin install.
91
96
  litgoal Manage litgoal runtime state and evidence.
@@ -720,6 +725,82 @@ const registerMarketplace = (marketplacePath, home = claudeHome()) => {
720
725
  writeKnownMarketplaces(knownMarketplaces, home);
721
726
  };
722
727
 
728
+ // The LIT card's options live in settings.json `pluginConfigs`, which the terminal and the desktop
729
+ // app both read; the desktop app has no editor for them, so install writes each default the user
730
+ // has not set and records which ones it wrote. Uninstall removes only those still at that value.
731
+ const modOptionDefaults = () => Object.fromEntries(Object.keys(MOD_CHOICES).map((key) => [key, MOD_CHOICES[key][0]]));
732
+ const modOptionsOf = (settings) => ({ ...modOptionDefaults(), ...(settings.pluginConfigs?.[pluginKey]?.options ?? {}) });
733
+ const modOptionsLine = (settings) => {
734
+ const options = modOptionsOf(settings);
735
+ return Object.keys(MOD_CHOICES).map((key) => `${key} ${options[key]}`).join(" · ");
736
+ };
737
+
738
+ const writeModOptions = (settings, options) => {
739
+ settings.pluginConfigs = settings.pluginConfigs ?? {};
740
+ const entry = settings.pluginConfigs[pluginKey] ?? {};
741
+ settings.pluginConfigs[pluginKey] = { ...entry, options: { ...(entry.options ?? {}), ...options } };
742
+ };
743
+
744
+ const installModOptions = (home = claudeHome()) => {
745
+ const settings = readClaudeSettings(home);
746
+ const present = settings.pluginConfigs?.[pluginKey]?.options ?? {};
747
+ const existingLitClaude = settings[litClaudeSettingsKey] ?? {};
748
+ const written = { ...(existingLitClaude.modOptionsWritten ?? {}) };
749
+ const missing = Object.fromEntries(Object.entries(modOptionDefaults()).filter(([key]) => present[key] === undefined));
750
+ if (Object.keys(missing).length === 0) return;
751
+ writeModOptions(settings, missing);
752
+ settings[litClaudeSettingsKey] = { ...existingLitClaude, modOptionsWritten: { ...written, ...missing } };
753
+ writeClaudeSettings(settings, home);
754
+ };
755
+
756
+ const uninstallModOptions = (home = claudeHome()) => {
757
+ if (!existsSync(claudeSettingsPath(home))) return;
758
+ const settings = readClaudeSettings(home);
759
+ const litClaude = settings[litClaudeSettingsKey];
760
+ if (!litClaude?.modOptionsWritten) return;
761
+ const entry = settings.pluginConfigs?.[pluginKey];
762
+ if (entry?.options) {
763
+ const options = { ...entry.options };
764
+ for (const [key, value] of Object.entries(litClaude.modOptionsWritten)) if (options[key] === value) delete options[key];
765
+ const rest = { ...entry };
766
+ if (Object.keys(options).length > 0) rest.options = options;
767
+ else delete rest.options;
768
+ if (Object.keys(rest).length > 0) settings.pluginConfigs[pluginKey] = rest;
769
+ else delete settings.pluginConfigs[pluginKey];
770
+ if (Object.keys(settings.pluginConfigs).length === 0) delete settings.pluginConfigs;
771
+ }
772
+ const remainingLitClaude = { ...litClaude };
773
+ delete remainingLitClaude.modOptionsWritten;
774
+ if (Object.keys(remainingLitClaude).length > 0) settings[litClaudeSettingsKey] = remainingLitClaude;
775
+ else delete settings[litClaudeSettingsKey];
776
+ writeClaudeSettings(settings, home);
777
+ };
778
+
779
+ // `config` prints the options; `config <option> <value>` sets one. A value the user sets is theirs,
780
+ // so it leaves the install record and uninstall keeps it.
781
+ const configModOptions = (rest, home = claudeHome()) => {
782
+ if (rest.length === 0) {
783
+ const options = modOptionsOf(readClaudeSettings(home));
784
+ for (const key of Object.keys(MOD_CHOICES)) process.stdout.write(`${key.padEnd(7)} ${options[key].padEnd(7)} (${MOD_CHOICES[key].join(", ")})\n`);
785
+ process.stdout.write("Change one with: litclaude-ai config <option> <value>. New Claude Code sessions, terminal or desktop app, pick it up.\n");
786
+ return;
787
+ }
788
+ const [key, raw] = rest;
789
+ if (!Object.hasOwn(MOD_CHOICES, key)) fail(`Unknown option: ${key}. Options: ${Object.keys(MOD_CHOICES).join(", ")}`, 64);
790
+ const value = String(raw ?? "").trim().toLowerCase();
791
+ if (!MOD_CHOICES[key].includes(value) || rest.length > 2) fail(`${key} takes one of: ${MOD_CHOICES[key].join(", ")}`, 64);
792
+ const settings = readClaudeSettings(home);
793
+ writeModOptions(settings, { [key]: value });
794
+ const litClaude = settings[litClaudeSettingsKey];
795
+ if (litClaude?.modOptionsWritten && Object.hasOwn(litClaude.modOptionsWritten, key)) {
796
+ const written = { ...litClaude.modOptionsWritten };
797
+ delete written[key];
798
+ settings[litClaudeSettingsKey] = { ...litClaude, modOptionsWritten: written };
799
+ }
800
+ writeClaudeSettings(settings, home);
801
+ process.stdout.write(`MOD_OPTIONS: ${modOptionsLine(settings)}\n`);
802
+ };
803
+
723
804
  const ownsHudStatusLine = (settings, ownership) =>
724
805
  settings.statusLine?.type === "command" && settings.statusLine.command === ownership.statusLineCommand &&
725
806
  Object.keys(settings.statusLine).every((key) => key === "type" || key === "command");
@@ -1025,6 +1106,7 @@ const registerClaudePlugin = (installRoot, marketplacePath, home = claudeHome(),
1025
1106
  writeInstalledPlugins(registry, home);
1026
1107
  registerMarketplace(marketplacePath, home);
1027
1108
  installHudStatusLine(installRoot, home, hudAccent);
1109
+ installModOptions(home);
1028
1110
  recordPermissionConfiguration(permissionMode, home);
1029
1111
  };
1030
1112
 
@@ -1384,6 +1466,8 @@ const install = async ({ dryRun, rest }) => {
1384
1466
  process.stdout.write(`Plugin path: ${intendedPluginPath(home)}\n`);
1385
1467
  process.stdout.write(hudManaged ? `HUD: LitClaude statusLine installed (${hudAccent})\n` : "HUD: user statusLine setting preserved\n");
1386
1468
  process.stdout.write(`Global Claude settings permissions.allow/deny: ${permissionMode} — ${permissionProfile(permissionMode).description}\n`);
1469
+ process.stdout.write(`MOD_OPTIONS: ${modOptionsLine(readClaudeSettings(home))} (change: litclaude-ai config <option> <value>)\n`);
1470
+ process.stdout.write("DESKTOP_APP: ready — the Claude desktop app's Code tab reads these same settings; open a new session there\n");
1387
1471
  process.stdout.write(`${prewarmMotionRuntime()}\n`);
1388
1472
  if (wantsIconFont) await runIconFontInstall();
1389
1473
  process.stdout.write("Launch with: claude\n");
@@ -1505,6 +1589,7 @@ const doctor = ({ dryRun }) => {
1505
1589
  process.stdout.write(`${jevStatusLine(process.env)}\n`);
1506
1590
  writeAutoHandoffDoctorLines();
1507
1591
  process.stdout.write(`${iconFontDoctorLine()}\n`);
1592
+ process.stdout.write(`MOD_OPTIONS: ${modOptionsLine(readClaudeSettings())}\n`);
1508
1593
 
1509
1594
  // lit-typographic-motion: the five readiness probes, every run. Read-only and never fatal; a
1510
1595
  // missing pre-warm names the command that fixes it.
@@ -1635,6 +1720,7 @@ const uninstall = ({ dryRun }) => {
1635
1720
  uninstallHudStatusLine();
1636
1721
  uninstallOutputStyle();
1637
1722
  uninstallPermissionRules();
1723
+ uninstallModOptions();
1638
1724
  rmSync(claudePluginRoot(), { recursive: true, force: true });
1639
1725
  removeEmptyDirectory(dirname(claudePluginRoot()));
1640
1726
  removeEmptyDirectory(pluginCacheRoot);
@@ -1730,6 +1816,9 @@ const main = async () => {
1730
1816
  case "doctor":
1731
1817
  doctor(parsed);
1732
1818
  break;
1819
+ case "config":
1820
+ configModOptions(parsed.rest);
1821
+ break;
1733
1822
  case "path":
1734
1823
  printPath(parsed);
1735
1824
  break;
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.23"><title>release: 1.0.23</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.23</text></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.25"><title>release: 1.0.25</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.25</text></g></svg>
package/docs/hooks.md CHANGED
@@ -355,7 +355,7 @@ status line read as one family:
355
355
  ╭─ [🔥 LIT] · lit-loop ─────────────────────────── WORK ● ─╮
356
356
  │ ● PLAN ━━ [WORK] ━━ ○ REVIEW goal ██░░░ 2/5 │
357
357
  │ skills [⚡ lit-plan] [⚡ litresearch] [↯ lit-docx] │
358
- ╰─ ctx 42% ▰▰▱▱▱ · 5h 31% · git main ──────────────────────╯
358
+ ╰─ ctx 42% ▰▰▱▱▱ · 5h 31% · 1w 12% · git main ─────────────╯
359
359
  ```
360
360
 
361
361
  The brackets stand for the ends of filled pills: rounded caps from the icon font with `nerd`
@@ -365,10 +365,11 @@ requests are muted grey pills.
365
365
 
366
366
  - The top edge names the discipline the prompt hook ignited (the last one, dimmed, while the
367
367
  session is idle; `ready` before any) and, on the right, the current phase: `WORK ●`, `idle`,
368
- or `DONE ✓` during the finish.
368
+ or `DONE ✓` during the finish. While a subagent is still running the card stays at work
369
+ (`LIVE ●` when no phase is active) and never reads `DONE`, even after the main turn ended.
369
370
  - The phase row lights `PLAN ━━ WORK ━━ REVIEW` from the start-work plan, the litgoal goal and
370
371
  the discipline (the active phase as a pill, done phases `●`, the rest `○`), and on its right shows a pink-to-cyan progress bar with the checked tasks or
371
- passed criteria (`goal ✓ 5/5` once a goal is met), the number of live subagents, the autoloop
372
+ passed criteria (`goal ✓ 5/5` once a goal is met), the number of live subagents (`● 2 lanes`), the autoloop
372
373
  count with the time left in its 30-minute budget, and `ctx 76% → 80%` when the context is
373
374
  within five points of the auto-handoff percent.
374
375
  - The chips row shows each LitFamily skill that fired as a pill: a Skill tool call naming a LitClaude
@@ -379,7 +380,7 @@ requests are muted grey pills.
379
380
  With no chip yet, the row shows the next task while work runs, or how to start (`lit <task>`)
380
381
  while idle.
381
382
  - The bottom edge carries the live figures from the status line: context fill, the five-hour
382
- rate window and the Git branch (read from `HEAD`, across a linked worktree).
383
+ and weekly rate windows and the Git branch (read from `HEAD`, across a linked worktree).
383
384
  - A notice takes the chips row for a few seconds, with a toast, when a gate fires: lit-plan not
384
385
  saved, interface probe pending or failed, motion gate, context near the auto-handoff percent,
385
386
  auto-handoff fired. The completion moment takes it when the last task is checked, the work
@@ -389,9 +390,16 @@ requests are muted grey pills.
389
390
  border amber and puts `⚠ <lane> quiet for 12m` on the bottom edge; a failed interface probe
390
391
  turns it red.
391
392
 
392
- The card is the terminal's width up to 80 columns and every row is fitted to it, with Hangul and
393
+ Subagents and teammates come from Claude Code's agent list. A workflow script's agents never
394
+ appear there, so the mod also records every agent at `agent.spawn` and counts it until that
395
+ agent's own `turn.complete` arrives. A subagent's turn ending does not end the main turn: the
396
+ chips of the current request stay bright until the main turn completes. An agent the list shows
397
+ as ended stops counting at once, and one quiet for 30 minutes (no tool call, no status change)
398
+ stops counting too, so a crashed or lost agent cannot hold the card at work.
399
+
400
+ The card is the terminal's width up to 120 columns and every row is fitted to it, with Hangul and
393
401
  emoji counted as two cells. Nothing wraps: on a narrow terminal the Git branch goes first, then
394
- the rate window, the lanes, the autoloop count and the long phase labels (`P ━ W ━ R`).
402
+ the weekly and five-hour windows, the lanes, the autoloop count and the long phase labels (`P ━ W ━ R`).
395
403
 
396
404
  The spinner gains ` · <discipline> · n/m` after its ellipsis. `/lit-evidence` opens a pane with
397
405
  three tabs (keys `1`, `2`, `3`): the goal's criteria with their last evidence, blockers and the
@@ -400,17 +408,26 @@ newest litgoal ledger entries; the lanes; and the gates.
400
408
  Motion: entrances pop in 60 ms apart with a slight overshoot (160 ms each), fills and colour
401
409
  changes ease in and out (300 ms and 140 ms), notices slide in over 220 ms and leave over 150 ms,
402
410
  a chip's arrival light runs 400 ms (a plain fade under `subtle`, nothing under `off`), and the
403
- finish lasts at most 1.2 s. The band's `Client` element animates on Claude Code's frame
404
- clock only while something moves and stops its timer as soon as everything settles. In Claude
411
+ finish lasts at most 1.2 s. While LitClaude is at work the card keeps moving so you can tell it
412
+ is active at a glance: the LIT pill breathes from pink to flame orange every 2.4 s, a light sweeps
413
+ across the active phase and this request's skill chips every 1.8 s, and a comet runs along the top rule and back along the
414
+ bottom one every 2.6 s. Under `subtle` only the breathing stays; under `off` nothing moves. An
415
+ idle card holds still. The band's `Client` element animates on Claude Code's frame clock, every
416
+ 33 ms while a transition plays and every 66 ms during work, and stops its timer once the card is
417
+ idle and settled. In Claude
405
418
  Code 2.1.292 a terminal row whose only change is a non-ASCII glyph is not repainted, so every
406
419
  moving part changes ASCII text or colour: bar cells are coloured spaces, embers are ASCII sparks,
407
420
  a chip's light moves over its ASCII name, and a glyph swaps only together with its words.
408
421
 
409
- Options, in `/config` under LitClaude (stored in `pluginConfigs`):
422
+ Options, in `/config` under LitClaude (stored in `pluginConfigs` in `~/.claude/settings.json`,
423
+ which the terminal and the desktop app's Code tab both read). The installer writes each default
424
+ you have not set, so the desktop app, which has no editor for them, starts with all three;
425
+ `litclaude-ai config` prints them and `litclaude-ai config <option> <value>` changes one.
426
+ Uninstall removes only the defaults it wrote that are still unchanged.
410
427
 
411
428
  | Option | Values | Default |
412
429
  | --- | --- | --- |
413
- | `motion` | `full`, `subtle` (no overshoot, shimmer or embers, shorter), `off` (final frames only) | `full` |
430
+ | `motion` | `full`, `subtle` (no overshoot, shimmer, embers, sweep or comet, shorter; the LIT pill still breathes during work), `off` (final frames only) | `full` |
414
431
  | `glyphs` | `auto` (Nerd glyphs when the icon font is installed), `nerd`, or `basic` | `auto` |
415
432
  | `band` | `always` (the whole session, idle included), `auto` (only while work is active or a skill of this request is lit), `off` | `always` |
416
433
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litfamily/litclaude",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "Claude Code plugin: add lit to a prompt and Claude plans, tests, checks and reviews the work, then leaves a handoff for the next session.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "litclaude",
3
3
  "description": "Claude Code-native workflow plugin with a bounded-authority start-work lifecycle.",
4
- "version": "1.0.23",
4
+ "version": "1.0.25",
5
5
  "author": {
6
6
  "name": "LitClaude contributors"
7
7
  },
@@ -18,7 +18,7 @@
18
18
  "motion": {
19
19
  "type": "string",
20
20
  "title": "LIT band motion",
21
- "description": "How the band above the prompt moves: full, subtle (no overshoot, shimmer or embers) or off.",
21
+ "description": "How the band above the prompt moves: full (during work the LIT pill breathes, a light sweeps the phase and a comet runs the border), subtle (only the breathing) or off.",
22
22
  "default": "full"
23
23
  },
24
24
  "glyphs": {
@@ -9,7 +9,7 @@ import {
9
9
  } from "../lib/wikify-knowledge.mjs";
10
10
 
11
11
  const protocolVersion = "2024-11-05";
12
- const serverVersion = "1.0.23";
12
+ const serverVersion = "1.0.25";
13
13
 
14
14
  const publicSourceReadTool = {
15
15
  name: "public_source_read",
@@ -1,12 +1,13 @@
1
1
  // The LIT band's surface module: the `Client` element lit-mod.mjs draws above the prompt. It runs on
2
2
  // the drawing thread with no mods API, keeps the band's timeline in its local state, and animates on
3
- // the surface's own frame clock only while something moves; once the timeline settles it stops its
4
- // timer and the band stays still until the hooks module hands it a new model.
3
+ // the surface's own frame clock: fast while a transition plays, slower while the ambient loop says
4
+ // LitClaude is at work, and not at all once the card is idle and settled, when it stays still until
5
+ // the hooks module hands it a new model.
5
6
 
6
7
  import { composeBand } from "../lib/mod/frame.mjs";
7
8
  import { TIMINGS, motionProfile } from "../lib/mod/motion.mjs";
8
9
  import { renderBand } from "../lib/mod/render.mjs";
9
- import { advanceTimeline, createTimeline, sampleTimeline, timelineBusy } from "../lib/mod/timeline.mjs";
10
+ import { advanceTimeline, ambientActive, createTimeline, sampleTimeline, timelineBusy } from "../lib/mod/timeline.mjs";
10
11
 
11
12
  // The timeline lives in one holder object that the frame timer and every draw share. The timer
12
13
  // never reads `surface.state` back, so a draw that folded in a new model is never overwritten by a
@@ -17,7 +18,7 @@ export default function LitBand(props, surface) {
17
18
  const empty = () => surface.elements.Box({ children: [] });
18
19
  if (!model) return empty();
19
20
 
20
- const holder = surface.state?.live ?? { now: 0, model: null, timeline: null, stop: null };
21
+ const holder = surface.state?.live ?? { now: 0, model: null, timeline: null, stop: null, interval: null };
21
22
  if (holder.timeline === null) {
22
23
  holder.timeline = createTimeline(model, 0, profile);
23
24
  holder.model = model;
@@ -26,19 +27,25 @@ export default function LitBand(props, surface) {
26
27
  holder.model = model;
27
28
  }
28
29
 
29
- if (holder.stop === null && timelineBusy(holder.timeline, holder.now, profile)) {
30
- holder.stop = surface.every(TIMINGS.frame, () => {
31
- holder.now += TIMINGS.frame;
32
- if (!timelineBusy(holder.timeline, holder.now, profile)) {
33
- holder.stop?.();
34
- holder.stop = null;
35
- }
30
+ const interval = () => (timelineBusy(holder.timeline, holder.now, profile) ? TIMINGS.frame
31
+ : ambientActive(holder.timeline, profile) ? TIMINGS.ambientFrame : null);
32
+ const schedule = () => {
33
+ const wanted = interval();
34
+ if (wanted === holder.interval) return;
35
+ holder.stop?.();
36
+ holder.stop = null;
37
+ holder.interval = wanted;
38
+ if (wanted === null) return;
39
+ holder.stop = surface.every(wanted, () => {
40
+ holder.now += wanted;
41
+ schedule();
36
42
  surface.setState({ live: holder, now: holder.now });
37
43
  });
38
- }
44
+ };
45
+ schedule();
39
46
  if (surface.state?.live !== holder) surface.setState({ live: holder, now: holder.now });
40
47
 
41
- if (!model.visible && !timelineBusy(holder.timeline, holder.now, profile)) return empty();
48
+ if (!model.visible && interval() === null) return empty();
42
49
  const frame = composeBand(model, sampleTimeline(holder.timeline, holder.now, profile), { columns: props.columns, glyphs: props.glyphs, accent: props.accent });
43
50
  return renderBand(surface.elements, frame);
44
51
  }
@@ -22,7 +22,11 @@ import {
22
22
  buildEvidenceModel,
23
23
  detectCompletion,
24
24
  diffNotices,
25
+ mergeSpawned,
25
26
  noteAgentActivity,
27
+ noteSpawn,
28
+ noteSpawnActivity,
29
+ noteSpawnEnded,
26
30
  spinnerSuffix,
27
31
  trackAgents,
28
32
  } from "../lib/mod/view.mjs";
@@ -44,6 +48,7 @@ const createLive = (options) => ({
44
48
  snapshot: null,
45
49
  model: null,
46
50
  agents: [],
51
+ spawned: {},
47
52
  usage: null,
48
53
  activity: EMPTY_ACTIVITY,
49
54
  statusById: {},
@@ -150,9 +155,10 @@ async function refresh($, live) {
150
155
  const [cwd, sessionId] = await Promise.all([$.session.cwd(), $.session.id()]);
151
156
  const fresh = await readLitSnapshot(stateIo($), { cwd, sessionId, env: live.env });
152
157
  live.git = await readGitBranch(stateIo($), cwd).catch(() => live.git);
153
- live.agents = await $.agent.list().catch(() => []);
158
+ const listed = await $.agent.list().catch(() => []);
154
159
  live.usage = await $.session.usage().catch(() => live.usage);
155
160
  const now = await $.clock.now();
161
+ ({ agents: live.agents, spawned: live.spawned } = mergeSpawned(listed, live.spawned, now));
156
162
  live.activity = trackAgents(live.activity, live.agents, live.statusById, now);
157
163
  live.statusById = Object.fromEntries(live.agents.map((agent) => [agent.id, agent.status]));
158
164
  const before = live.snapshot;
@@ -217,6 +223,12 @@ export const register = (on, options) => {
217
223
  });
218
224
 
219
225
  on("turn.complete", async ($, e, next) => {
226
+ // A subagent's or teammate's turn ends its run, not the main turn the card follows.
227
+ if (typeof e.agentId === "string") {
228
+ live.spawned = noteSpawnEnded(live.spawned, e.agentId);
229
+ refreshSoon($, live);
230
+ return next(e);
231
+ }
220
232
  live.isWorking = false;
221
233
  // The request is over: its chips dim to the recent list until a skill fires again.
222
234
  live.settledAt = await $.clock.now();
@@ -232,12 +244,28 @@ export const register = (on, options) => {
232
244
 
233
245
  on("tool.call", async ($, e, next) => {
234
246
  if (e.tool === "Skill") await noteFiring($, live, e.skill);
247
+ if (typeof e.agentId === "string") live.spawned = noteSpawnActivity(live.spawned, e.agentId, await $.clock.now());
235
248
  const result = await next(e);
236
- if (typeof e.agentId === "string") live.activity = noteAgentActivity(live.activity, e.agentId, await $.clock.now());
249
+ if (typeof e.agentId === "string") {
250
+ const now = await $.clock.now();
251
+ live.activity = noteAgentActivity(live.activity, e.agentId, now);
252
+ live.spawned = noteSpawnActivity(live.spawned, e.agentId, now);
253
+ }
237
254
  refreshSoon($, live);
238
255
  return result;
239
256
  }).catch(($, e, next) => next(e));
240
257
 
258
+ // Every subagent, teammate and workflow agent starts here. A workflow's never shows in
259
+ // `$.agent.list()`, so the card keeps its own record until the agent's turn.complete.
260
+ on("agent.spawn", async ($, e, next) => {
261
+ const result = await next(e);
262
+ if (typeof result?.agentId === "string") {
263
+ live.spawned = noteSpawn(live.spawned, { id: result.agentId, description: e.description, type: e.subagentType, at: await $.clock.now() });
264
+ refreshSoon($, live);
265
+ }
266
+ return result;
267
+ }).catch(($, e, next) => next(e));
268
+
241
269
  // A typed `/litclaude:<name>` and the Skill tool both expand the skill's prompt here; the text
242
270
  // passes through untouched.
243
271
  on("skill.prompt", async ($, e, next) => {
@@ -11,8 +11,8 @@ export const canonicalSkillResourceManifest = Object.freeze(
11
11
  ["lib/durable-plan.mjs", "33fc7c2ba6305602c73650c8f29e6141e93b8ac32a444f772807e11bf8c22e44"],
12
12
  ["lib/immutable-expected-file-map.mjs", "4061486bc8eda8a53e56b7b346a1e400e7c858c0a682c83362fea460b46dfd4b"],
13
13
  ["lib/lit-mark.mjs", "cb57921a3da0ff983c74e9426dcd3b2379db090198743139e379a059c128021e"],
14
- ["lib/office-runtime-lock/package-lock.json", "4b9552dba69bf2803cecdf71996d26de0497004c00d1e1f5753a5b299f85d8ba"],
15
- ["lib/office-runtime-lock/package.json", "edf7fee2ba78d6ad42bf5761678b2a2210adb7282108864360f83c46adb59708"],
14
+ ["lib/office-runtime-lock/package-lock.json", "16d19b420b410035e9ca6886133368444a2b438121919857b3e8915be280c6a6"],
15
+ ["lib/office-runtime-lock/package.json", "5e4b0c7ee7bd5ff88c888f7f46152c605caf128cc4e7bfd16423571210925394"],
16
16
  ["lib/office-runtime-lock/requirements.lock", "7c63ce0de1ae9a912d1e546e9c01dd99a705d4a50435f36040d351cc3e6e6014"],
17
17
  ["lib/office-runtime.mjs", "24d108a4ae7aa98dd0d6f2fa66686d7c9f020ecf42fe641241be2869075bbea3"],
18
18
  ["lib/office_data.py", "0f4c2449622b6d59630e60508594d181279052c41918df4370e11a68df1ee239"],
@@ -848,11 +848,11 @@ export const canonicalSkillResourceManifest = Object.freeze(
848
848
  ["skills/readme-studio/templates/cover-source.json", "182051737e4cda9002360316bb2c01b500ecc20c27953a2d332d908f044f5cd7"],
849
849
  ["skills/readme-studio/templates/hyperframes-cover/index.html", "edaee52e01cc7d90524b43e3da80b4bc6514fef1d17b4317cbf15ed5e36a5c67"],
850
850
  ["skills/readme-studio/templates/hyperframes-cover/index.motion.json", "bef879ce5abe7810d73cc733ab635884ec807eccd7bc5e1c9bc9900049187249"],
851
- ["skills/readme-studio/templates/hyperframes-cover/package-lock.json", "facce3fcbba7b9b5cd5b368b78192b035663a7c957f7802f97f037497dc193be"],
851
+ ["skills/readme-studio/templates/hyperframes-cover/package-lock.json", "5adacd09e4608c8c3917ca0245052e4d3abae3a566ebe07a2fdad593c67cb91c"],
852
852
  ["skills/readme-studio/templates/hyperframes-cover/package.json", "0993c6f04892f5a318cc99030de653b7c68d3ea6789c69b30b6a2ae80e4120f0"],
853
853
  ["skills/readme-studio/templates/readme-cover-section.md", "1dc4c536f65d3437f12e09a76170a8dc76557225587c7453631a06604a21573d"],
854
854
  ["skills/readme-studio/templates/readme-facts.json", "fb95b8aece0e8591726d3a36e25cc66712b0c6e57835c848db0f07b745e73205"],
855
- ["skills/readme-studio/templates/remotion-cover/package-lock.json", "45cae4a5acee8bc98ef449a2ae91c4eae775afe7fc94769b86017bedb9301511"],
855
+ ["skills/readme-studio/templates/remotion-cover/package-lock.json", "422af74482c6c36e5bfaa86c2fe6a78e7ff54d27d3119f9c462685333a449857"],
856
856
  ["skills/readme-studio/templates/remotion-cover/package.json", "7921a415cf96343b46c9a2a3bea9160ecc807710fb6a34ba227704eff7d83887"],
857
857
  ["skills/readme-studio/templates/remotion-cover/src/index.tsx", "81cf3edd996a98ddca46465d4bc84ca0203ecd3bdce1736bf1dc3d9f2bc15af9"],
858
858
  ["skills/readme-studio/templates/typography/package-lock.json", "1d6dc1ae0d06d0a3a5e1b2b45bea1d40be7497f1f2fb856eefd33cc3649af5bd"],