@slats/claude-assets-sync 0.3.0 → 0.3.1

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 (139) hide show
  1. package/README.md +9 -9
  2. package/dist/claude-hashes.json +7 -7
  3. package/dist/commands/runCli/runCli.mjs +7 -2
  4. package/dist/commands/runCli/type.d.ts +1 -0
  5. package/dist/commands/runCli/utils/renderOrFallback.d.ts +6 -0
  6. package/dist/commands/runCli/utils/renderOrFallback.mjs +12 -0
  7. package/dist/commands/runCli/utils/renderPlain.d.ts +11 -0
  8. package/dist/commands/runCli/utils/renderPlain.mjs +89 -0
  9. package/dist/commands/runCli/utils/resolveScopeFlag.d.ts +9 -1
  10. package/dist/commands/runCli/utils/resolveScopeFlag.mjs +5 -11
  11. package/dist/commands/runCli/utils/toConsumerPackages.d.ts +9 -0
  12. package/dist/commands/runCli/utils/toConsumerPackages.mjs +26 -0
  13. package/dist/core/index.d.ts +2 -2
  14. package/dist/core/injectDocs/index.d.ts +3 -2
  15. package/dist/core/injectDocs/type.d.ts +0 -19
  16. package/dist/core/scope/index.d.ts +1 -1
  17. package/dist/core/scope/scope.d.ts +0 -1
  18. package/dist/core/scope/scope.mjs +1 -4
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.mjs +2 -2
  21. package/dist/ui/InjectApp/InjectApp.d.ts +2 -0
  22. package/dist/ui/InjectApp/InjectApp.mjs +82 -0
  23. package/dist/ui/InjectApp/index.d.ts +2 -0
  24. package/dist/ui/InjectApp/utils/eventSelectors.d.ts +5 -0
  25. package/dist/ui/InjectApp/utils/eventSelectors.mjs +24 -0
  26. package/dist/ui/InjectApp/utils/phaseReducer.d.ts +2 -0
  27. package/dist/ui/InjectApp/utils/phaseReducer.mjs +130 -0
  28. package/dist/ui/InjectApp/utils/renderInjectApp.d.ts +2 -0
  29. package/dist/ui/InjectApp/utils/renderInjectApp.mjs +19 -0
  30. package/dist/ui/InjectApp/utils/type.d.ts +5 -0
  31. package/dist/ui/components/ActionRow.d.ts +7 -0
  32. package/dist/ui/components/ActionRow.mjs +45 -0
  33. package/dist/ui/components/Banner.d.ts +7 -0
  34. package/dist/ui/components/Banner.mjs +9 -0
  35. package/dist/ui/components/ConfirmForce.d.ts +8 -0
  36. package/dist/ui/components/ConfirmForce.mjs +35 -0
  37. package/dist/ui/components/ErrorPanel.d.ts +6 -0
  38. package/dist/ui/components/ErrorPanel.mjs +14 -0
  39. package/dist/ui/components/Footer.d.ts +8 -0
  40. package/dist/ui/components/Footer.mjs +27 -0
  41. package/dist/ui/components/PlanTable.d.ts +8 -0
  42. package/dist/ui/components/PlanTable.mjs +15 -0
  43. package/dist/ui/components/ProgressBar.d.ts +10 -0
  44. package/dist/ui/components/ProgressBar.mjs +28 -0
  45. package/dist/ui/components/ScopePicker.d.ts +7 -0
  46. package/dist/ui/components/ScopePicker.mjs +26 -0
  47. package/dist/ui/components/Spinner.d.ts +8 -0
  48. package/dist/ui/components/Spinner.mjs +10 -0
  49. package/dist/ui/components/StatusBadge.d.ts +8 -0
  50. package/dist/ui/components/StepTracker.d.ts +9 -0
  51. package/dist/ui/components/StepTracker.mjs +43 -0
  52. package/dist/ui/components/Summary.d.ts +9 -0
  53. package/dist/ui/components/Summary.mjs +30 -0
  54. package/dist/ui/components/TargetCard.d.ts +11 -0
  55. package/dist/ui/components/TargetCard.mjs +29 -0
  56. package/dist/ui/hooks/useApplyStep.d.ts +12 -0
  57. package/dist/ui/hooks/useApplyStep.mjs +30 -0
  58. package/dist/ui/hooks/useExitApp.d.ts +8 -0
  59. package/dist/ui/hooks/useExitApp.mjs +19 -0
  60. package/dist/ui/hooks/useForceConfirmStep.d.ts +9 -0
  61. package/dist/ui/hooks/useForceConfirmStep.mjs +24 -0
  62. package/dist/ui/hooks/useInjectSession.d.ts +10 -0
  63. package/dist/ui/hooks/useInjectSession.mjs +63 -0
  64. package/dist/ui/hooks/useInterval.d.ts +1 -0
  65. package/dist/ui/hooks/usePhase.d.ts +2 -0
  66. package/dist/ui/hooks/usePhase.mjs +9 -0
  67. package/dist/ui/hooks/usePlanStep.d.ts +13 -0
  68. package/dist/ui/hooks/usePlanStep.mjs +94 -0
  69. package/dist/ui/hooks/useResolveStep.d.ts +18 -0
  70. package/dist/ui/hooks/useResolveStep.mjs +21 -0
  71. package/dist/ui/hooks/useTerminalWidth.d.ts +1 -0
  72. package/dist/ui/index.d.ts +2 -0
  73. package/dist/ui/index.mjs +16 -0
  74. package/dist/ui/theme/colors.d.ts +12 -0
  75. package/dist/ui/theme/colors.mjs +9 -0
  76. package/dist/ui/theme/icons.d.ts +29 -0
  77. package/dist/ui/theme/icons.mjs +17 -0
  78. package/dist/ui/theme/layout.d.ts +20 -0
  79. package/dist/ui/theme/layout.mjs +9 -0
  80. package/dist/ui/types/event.d.ts +45 -0
  81. package/dist/ui/types/index.d.ts +4 -0
  82. package/dist/ui/types/phase.d.ts +44 -0
  83. package/dist/ui/types/render.d.ts +6 -0
  84. package/dist/ui/types/target.d.ts +25 -0
  85. package/dist/utils/version.d.ts +1 -1
  86. package/dist/utils/version.mjs +1 -1
  87. package/docs/claude/skills/claude-docs-asset-wiring/SKILL.md +1 -1
  88. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/claude-md-template.md +4 -12
  89. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/gotchas.md +17 -14
  90. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/package-json-patches.md +18 -13
  91. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/reference-files.md +4 -4
  92. package/docs/consumer-integration.md +9 -8
  93. package/package.json +12 -7
  94. package/scripts/dev-ui-fixtures.ts +288 -0
  95. package/scripts/dev-ui.tsx +289 -0
  96. package/dist/commands/runCli/runCli.cjs +0 -53
  97. package/dist/commands/runCli/utils/classifyTarget.cjs +0 -48
  98. package/dist/commands/runCli/utils/injectOne.cjs +0 -47
  99. package/dist/commands/runCli/utils/injectOne.d.ts +0 -3
  100. package/dist/commands/runCli/utils/injectOne.mjs +0 -45
  101. package/dist/commands/runCli/utils/resolvePackage.cjs +0 -77
  102. package/dist/commands/runCli/utils/resolveScopeAlias.cjs +0 -69
  103. package/dist/commands/runCli/utils/resolveScopeFlag.cjs +0 -28
  104. package/dist/commands/runCli/utils/resolveTargets.cjs +0 -40
  105. package/dist/commands/runCli/utils/runInject.cjs +0 -52
  106. package/dist/commands/runCli/utils/runInject.d.ts +0 -3
  107. package/dist/commands/runCli/utils/runInject.mjs +0 -50
  108. package/dist/core/buildPlan/buildPlan.cjs +0 -42
  109. package/dist/core/buildPlan/utils/toPosix.cjs +0 -9
  110. package/dist/core/buildPlan/utils/walkFiles.cjs +0 -25
  111. package/dist/core/hash/hash.cjs +0 -30
  112. package/dist/core/hashManifest/hashManifest.cjs +0 -27
  113. package/dist/core/injectDocs/injectDocs.cjs +0 -43
  114. package/dist/core/injectDocs/injectDocs.d.ts +0 -2
  115. package/dist/core/injectDocs/injectDocs.mjs +0 -41
  116. package/dist/core/injectDocs/utils/applyAction.cjs +0 -21
  117. package/dist/core/injectDocs/utils/emitCiForceList.cjs +0 -10
  118. package/dist/core/injectDocs/utils/emitCiForceList.d.ts +0 -2
  119. package/dist/core/injectDocs/utils/emitCiForceList.mjs +0 -8
  120. package/dist/core/injectDocs/utils/printPlan.cjs +0 -20
  121. package/dist/core/injectDocs/utils/printPlan.d.ts +0 -2
  122. package/dist/core/injectDocs/utils/printPlan.mjs +0 -18
  123. package/dist/core/injectDocs/utils/summarize.cjs +0 -27
  124. package/dist/core/scope/scope.cjs +0 -46
  125. package/dist/core/scope/utils/isDirectory.cjs +0 -14
  126. package/dist/index.cjs +0 -20
  127. package/dist/prompts/confirmForce.cjs +0 -27
  128. package/dist/prompts/confirmForce.d.ts +0 -1
  129. package/dist/prompts/confirmForce.mjs +0 -25
  130. package/dist/prompts/index.d.ts +0 -2
  131. package/dist/prompts/selectScope.cjs +0 -30
  132. package/dist/prompts/selectScope.d.ts +0 -2
  133. package/dist/prompts/selectScope.mjs +0 -28
  134. package/dist/utils/asyncPool.cjs +0 -26
  135. package/dist/utils/heartbeat.cjs +0 -25
  136. package/dist/utils/heartbeat.d.ts +0 -16
  137. package/dist/utils/heartbeat.mjs +0 -23
  138. package/dist/utils/logger.cjs +0 -74
  139. package/dist/utils/version.cjs +0 -5
@@ -0,0 +1,130 @@
1
+ function phaseReducer(phase, event) {
2
+ switch (event.type) {
3
+ case 'scope-needed': {
4
+ if (phase.kind === 'booting' || phase.kind === 'resolving') {
5
+ return {
6
+ kind: 'scope-select',
7
+ targets: phase.kind === 'resolving' ? phase.targets : [],
8
+ pending: event.pending,
9
+ };
10
+ }
11
+ return phase;
12
+ }
13
+ case 'scope-selected': {
14
+ return phase;
15
+ }
16
+ case 'planning-started': {
17
+ const progress = new Map(event.targets.map((t) => [t.name, { packageName: t.name, status: 'pending' }]));
18
+ return {
19
+ kind: 'planning',
20
+ targets: event.targets,
21
+ scope: event.scope,
22
+ progress,
23
+ };
24
+ }
25
+ case 'plan-step': {
26
+ if (phase.kind !== 'planning')
27
+ return phase;
28
+ const next = new Map(phase.progress);
29
+ next.set(event.step.packageName, event.step);
30
+ return { ...phase, progress: next };
31
+ }
32
+ case 'plans-ready': {
33
+ if (phase.kind !== 'planning')
34
+ return phase;
35
+ return {
36
+ kind: 'diff-review',
37
+ plans: event.plans,
38
+ focusedIndex: 0,
39
+ scope: phase.scope,
40
+ };
41
+ }
42
+ case 'focus-target': {
43
+ if (phase.kind !== 'diff-review')
44
+ return phase;
45
+ return { ...phase, focusedIndex: event.index };
46
+ }
47
+ case 'force-confirm-required': {
48
+ if (phase.kind !== 'diff-review' && phase.kind !== 'applying')
49
+ return phase;
50
+ return {
51
+ kind: 'force-confirm',
52
+ plans: phase.kind === 'diff-review' ? phase.plans : phase.plans,
53
+ warnings: event.warnings,
54
+ pending: event.pending,
55
+ scope: phase.scope,
56
+ };
57
+ }
58
+ case 'force-answer': {
59
+ if (phase.kind !== 'force-confirm')
60
+ return phase;
61
+ if (!event.ok) {
62
+ return {
63
+ kind: 'summary',
64
+ reports: [],
65
+ plans: phase.plans,
66
+ exitCode: 2,
67
+ scope: phase.scope,
68
+ dryRun: false,
69
+ };
70
+ }
71
+ return {
72
+ kind: 'applying',
73
+ plans: phase.plans,
74
+ progress: {
75
+ done: 0,
76
+ total: phase.plans.reduce((acc, tp) => acc + tp.plan.actions.length, 0),
77
+ startedAt: Date.now(),
78
+ },
79
+ scope: phase.scope,
80
+ };
81
+ }
82
+ case 'apply-start': {
83
+ if (phase.kind !== 'diff-review' && phase.kind !== 'force-confirm')
84
+ return phase;
85
+ const plans = phase.kind === 'diff-review' ? phase.plans : phase.plans;
86
+ return {
87
+ kind: 'applying',
88
+ plans,
89
+ progress: { done: 0, total: event.total, startedAt: Date.now() },
90
+ scope: phase.scope,
91
+ };
92
+ }
93
+ case 'apply-progress': {
94
+ if (phase.kind !== 'applying')
95
+ return phase;
96
+ return {
97
+ ...phase,
98
+ progress: {
99
+ ...phase.progress,
100
+ done: event.done,
101
+ current: event.current,
102
+ },
103
+ };
104
+ }
105
+ case 'done': {
106
+ const scope = phase.kind === 'applying' || phase.kind === 'diff-review'
107
+ ? phase.scope
108
+ : 'user';
109
+ const plans = phase.kind === 'applying' || phase.kind === 'diff-review'
110
+ ? phase.plans
111
+ : [];
112
+ return {
113
+ kind: 'summary',
114
+ reports: event.reports,
115
+ plans,
116
+ exitCode: event.exitCode,
117
+ scope,
118
+ dryRun: event.dryRun,
119
+ };
120
+ }
121
+ case 'fail': {
122
+ return { kind: 'error', error: event.error };
123
+ }
124
+ default: {
125
+ return phase;
126
+ }
127
+ }
128
+ }
129
+
130
+ export { phaseReducer };
@@ -0,0 +1,2 @@
1
+ import type { RenderInput } from '../../types/index.js';
2
+ export declare function renderInjectApp(input: RenderInput): Promise<number>;
@@ -0,0 +1,19 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { render } from 'ink';
3
+ import { InjectApp } from '../InjectApp.mjs';
4
+
5
+ async function renderInjectApp(input) {
6
+ let exitCode = 0;
7
+ const instance = render(jsx(InjectApp, { ...input, onExit: (code) => {
8
+ exitCode = code;
9
+ } }), { exitOnCtrlC: true });
10
+ try {
11
+ await instance.waitUntilExit();
12
+ return exitCode;
13
+ }
14
+ finally {
15
+ instance.unmount();
16
+ }
17
+ }
18
+
19
+ export { renderInjectApp };
@@ -0,0 +1,5 @@
1
+ import type { RenderInput } from '../../types/index.js';
2
+ export type { Phase, InjectEvent, RenderInput } from '../../types/index.js';
3
+ export interface InjectAppProps extends RenderInput {
4
+ readonly onExit: (code: 0 | 1 | 2) => void;
5
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { Action } from '../../core/buildPlan/index.js';
3
+ interface ActionRowProps {
4
+ readonly action: Action;
5
+ }
6
+ export declare function ActionRow({ action }: ActionRowProps): React.ReactElement;
7
+ export {};
@@ -0,0 +1,45 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Box, Text } from 'ink';
3
+ import { colors } from '../theme/colors.mjs';
4
+ import { icons } from '../theme/icons.mjs';
5
+
6
+ function visualFor(action) {
7
+ switch (action.kind) {
8
+ case 'copy':
9
+ return { icon: icons.plus, color: colors.success };
10
+ case 'skip-uptodate':
11
+ return {
12
+ icon: icons.equals,
13
+ color: colors.muted,
14
+ dim: true,
15
+ note: 'up-to-date',
16
+ };
17
+ case 'warn-diverged':
18
+ return {
19
+ icon: icons.warning,
20
+ color: colors.warn,
21
+ note: 'diverged',
22
+ };
23
+ case 'warn-orphan':
24
+ return {
25
+ icon: icons.question,
26
+ color: colors.warn,
27
+ note: 'orphan',
28
+ };
29
+ case 'delete':
30
+ return {
31
+ icon: icons.minus,
32
+ color: colors.danger,
33
+ note: 'delete',
34
+ };
35
+ default: {
36
+ return { icon: '?', color: colors.muted };
37
+ }
38
+ }
39
+ }
40
+ function ActionRow({ action }) {
41
+ const visual = visualFor(action);
42
+ return (jsxs(Box, { children: [jsxs(Text, { color: visual.color, bold: true, children: [' ', visual.icon, ' '] }), jsx(Text, { color: colors.muted, dimColor: visual.dim, children: action.relPath }), visual.note ? (jsxs(Text, { color: colors.muted, dimColor: true, children: [' ', "(", visual.note, ")"] })) : null] }));
43
+ }
44
+
45
+ export { ActionRow };
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface BannerProps {
3
+ readonly version: string;
4
+ readonly scope?: string;
5
+ }
6
+ export declare function Banner({ version, scope, }: BannerProps): React.ReactElement;
7
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Box, Text } from 'ink';
3
+ import { colors } from '../theme/colors.mjs';
4
+
5
+ function Banner({ version, scope, }) {
6
+ return (jsxs(Box, { borderStyle: "round", borderColor: colors.primary, paddingX: 1, alignSelf: "flex-start", children: [jsx(Text, { bold: true, color: colors.primary, children: "claude-sync" }), jsxs(Text, { color: colors.muted, children: [' ', "v", version] }), scope ? (jsxs(Text, { children: [jsx(Text, { color: colors.muted, children: ' → ' }), jsx(Text, { color: colors.accent, bold: true, children: scope })] })) : null] }));
7
+ }
8
+
9
+ export { Banner };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import type { Warning } from '../types/index.js';
3
+ interface ConfirmForceProps {
4
+ readonly warnings: readonly Warning[];
5
+ readonly onAnswer: (ok: boolean) => void;
6
+ }
7
+ export declare function ConfirmForce({ warnings, onAnswer, }: ConfirmForceProps): React.ReactElement;
8
+ export {};
@@ -0,0 +1,35 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useState } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import SelectInput from 'ink-select-input';
5
+ import { colors } from '../theme/colors.mjs';
6
+ import { icons } from '../theme/icons.mjs';
7
+ import { limits } from '../theme/layout.mjs';
8
+
9
+ function ConfirmForce({ warnings, onAnswer, }) {
10
+ const [expanded, setExpanded] = useState(false);
11
+ const showAll = expanded || warnings.length <= limits.maxWarningsBeforeCollapse;
12
+ const shown = showAll
13
+ ? warnings
14
+ : warnings.slice(0, limits.maxWarningsBeforeCollapse);
15
+ const hidden = warnings.length - shown.length;
16
+ const diverged = warnings.filter((w) => w.kind === 'warn-diverged').length;
17
+ const orphans = warnings.filter((w) => w.kind === 'warn-orphan').length;
18
+ const items = [
19
+ { label: `Yes, overwrite ${warnings.length} path(s)`, value: 'accept' },
20
+ { label: 'No, cancel', value: 'cancel' },
21
+ ];
22
+ if (!showAll) {
23
+ items.push({ label: `Show all (${hidden} hidden)`, value: 'expand' });
24
+ }
25
+ return (jsxs(Box, { flexDirection: "column", marginTop: 1, children: [jsxs(Text, { color: colors.warn, bold: true, children: [icons.warning, " ", warnings.length, " path(s) need --force confirmation"] }), jsx(Box, { marginLeft: 2, children: jsxs(Text, { color: colors.muted, dimColor: true, children: [diverged, " diverged \u00B7 ", orphans, " orphan"] }) }), jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [shown.map((warning) => (jsxs(Box, { children: [jsxs(Text, { color: warning.kind === 'warn-diverged' ? colors.warn : colors.accent, bold: true, children: [warning.kind === 'warn-diverged' ? icons.warning : icons.question, ' '] }), jsxs(Text, { color: colors.muted, dimColor: true, children: ["[", warning.packageName, "]", ' '] }), jsx(Text, { children: warning.relPath })] }, `${warning.packageName}:${warning.relPath}`))), !showAll ? (jsxs(Text, { color: colors.muted, dimColor: true, children: ["\u2026 ", hidden, " more hidden"] })) : null] }), jsx(Box, { marginLeft: 2, marginTop: 1, children: jsx(SelectInput, { items: items, onSelect: (item) => {
26
+ if (item.value === 'accept')
27
+ onAnswer(true);
28
+ else if (item.value === 'cancel')
29
+ onAnswer(false);
30
+ else
31
+ setExpanded(true);
32
+ } }) })] }));
33
+ }
34
+
35
+ export { ConfirmForce };
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface ErrorPanelProps {
3
+ readonly error: Error;
4
+ }
5
+ export declare function ErrorPanel({ error, }: ErrorPanelProps): React.ReactElement;
6
+ export {};
@@ -0,0 +1,14 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Box, Text } from 'ink';
3
+ import { colors } from '../theme/colors.mjs';
4
+ import { icons } from '../theme/icons.mjs';
5
+
6
+ function ErrorPanel({ error, }) {
7
+ return (jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [jsxs(Text, { color: colors.danger, bold: true, children: [icons.cross, " ", error.name ?? 'Error'] }), jsx(Box, { marginLeft: 2, marginTop: 1, children: jsx(Text, { color: colors.danger, children: error.message }) }), error.stack ? (jsx(Box, { marginLeft: 2, marginTop: 1, children: jsx(Text, { color: colors.muted, dimColor: true, children: error.stack
8
+ .split('\n')
9
+ .slice(1, 4)
10
+ .map((l) => l.trim())
11
+ .join('\n') }) })) : null] }));
12
+ }
13
+
14
+ export { ErrorPanel };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import type { Phase } from '../types/index.js';
3
+ interface FooterProps {
4
+ readonly phase: Phase;
5
+ readonly version: string;
6
+ }
7
+ export declare function Footer({ phase, version, }: FooterProps): React.ReactElement;
8
+ export {};
@@ -0,0 +1,27 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Box, Text } from 'ink';
3
+ import { colors } from '../theme/colors.mjs';
4
+
5
+ function hintsFor(kind) {
6
+ switch (kind) {
7
+ case 'scope-select':
8
+ return '↑↓:select · enter:confirm · ctrl-c:cancel';
9
+ case 'diff-review':
10
+ return 'enter:apply · a:accept all warn · esc:cancel';
11
+ case 'force-confirm':
12
+ return 'y:accept · n:cancel';
13
+ case 'applying':
14
+ return 'ctrl-c:abort';
15
+ case 'summary':
16
+ return 'enter:exit';
17
+ case 'error':
18
+ return 'enter:exit';
19
+ default:
20
+ return 'ctrl-c:cancel';
21
+ }
22
+ }
23
+ function Footer({ phase, version, }) {
24
+ return (jsxs(Box, { marginTop: 1, children: [jsx(Text, { color: colors.muted, dimColor: true, children: hintsFor(phase.kind) }), jsxs(Text, { color: colors.muted, dimColor: true, children: [' v', version] }), jsxs(Text, { color: colors.muted, dimColor: true, children: [' phase: ', phase.kind] })] }));
25
+ }
26
+
27
+ export { Footer };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import type { InjectPlan } from '../../core/buildPlan/index.js';
3
+ interface PlanTableProps {
4
+ readonly plan: InjectPlan;
5
+ readonly maxRows?: number;
6
+ }
7
+ export declare function PlanTable({ plan, maxRows, }: PlanTableProps): React.ReactElement;
8
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Box, Text } from 'ink';
3
+ import { colors } from '../theme/colors.mjs';
4
+ import { limits } from '../theme/layout.mjs';
5
+ import { ActionRow } from './ActionRow.mjs';
6
+
7
+ function PlanTable({ plan, maxRows = limits.maxPlanRowsBeforeTruncate, }) {
8
+ const actions = plan.actions;
9
+ const overflow = actions.length > maxRows;
10
+ const shown = overflow ? actions.slice(0, limits.planTruncatedReveal) : actions;
11
+ const hidden = actions.length - shown.length;
12
+ return (jsxs(Box, { flexDirection: "column", children: [shown.map((action) => (jsx(ActionRow, { action: action }, `${action.kind}-${action.relPath}`))), overflow ? (jsx(Box, { marginLeft: 2, children: jsxs(Text, { color: colors.muted, dimColor: true, children: ["\u2026 ", hidden, " more action(s) hidden"] }) })) : null] }));
13
+ }
14
+
15
+ export { PlanTable };
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ interface ProgressBarProps {
3
+ readonly done: number;
4
+ readonly total: number;
5
+ readonly width?: number;
6
+ readonly etaSeconds?: number;
7
+ readonly label?: string;
8
+ }
9
+ export declare function ProgressBar({ done, total, width, etaSeconds, label, }: ProgressBarProps): React.ReactElement;
10
+ export {};
@@ -0,0 +1,28 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Box, Text } from 'ink';
3
+ import { colors } from '../theme/colors.mjs';
4
+ import { icons } from '../theme/icons.mjs';
5
+ import { widths } from '../theme/layout.mjs';
6
+
7
+ function formatEta(seconds) {
8
+ if (seconds === undefined || !Number.isFinite(seconds))
9
+ return null;
10
+ if (seconds < 1)
11
+ return '<1s';
12
+ if (seconds < 60)
13
+ return `${Math.round(seconds)}s`;
14
+ const m = Math.floor(seconds / 60);
15
+ const s = Math.round(seconds % 60);
16
+ return `${m}m${s}s`;
17
+ }
18
+ function ProgressBar({ done, total, width = widths.progressBarDefault, etaSeconds, label, }) {
19
+ const safeTotal = Math.max(total, 1);
20
+ const ratio = Math.min(Math.max(done / safeTotal, 0), 1);
21
+ const filled = Math.round(ratio * width);
22
+ const empty = width - filled;
23
+ const percent = Math.round(ratio * 100);
24
+ const eta = formatEta(etaSeconds);
25
+ return (jsxs(Box, { children: [jsx(Text, { color: colors.success, children: icons.blockFull.repeat(filled) }), jsx(Text, { color: colors.muted, dimColor: true, children: icons.blockEmpty.repeat(empty) }), jsxs(Text, { color: colors.muted, children: [' ', String(percent).padStart(3), "%"] }), jsxs(Text, { color: colors.muted, dimColor: true, children: [' ', "(", done, "/", total, eta ? ` · ETA ${eta}` : '', ")"] }), label ? (jsxs(Text, { color: colors.muted, dimColor: true, children: [' ', label] })) : null] }));
26
+ }
27
+
28
+ export { ProgressBar };
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { Scope } from '../../core/index.js';
3
+ interface ScopePickerProps {
4
+ readonly onSelect: (scope: Scope) => void;
5
+ }
6
+ export declare function ScopePicker({ onSelect, }: ScopePickerProps): React.ReactElement;
7
+ export {};
@@ -0,0 +1,26 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Box, Text } from 'ink';
3
+ import SelectInput from 'ink-select-input';
4
+ import { colors } from '../theme/colors.mjs';
5
+ import { icons } from '../theme/icons.mjs';
6
+
7
+ const ITEMS = [
8
+ {
9
+ label: 'user',
10
+ value: 'user',
11
+ hint: '~/.claude (global)',
12
+ },
13
+ {
14
+ label: 'project',
15
+ value: 'project',
16
+ hint: 'nearest ancestor .claude (or cwd)',
17
+ },
18
+ ];
19
+ function ScopePicker({ onSelect, }) {
20
+ return (jsxs(Box, { flexDirection: "column", marginTop: 1, children: [jsxs(Text, { bold: true, color: colors.primary, children: [icons.triangleRight, " Select the target scope"] }), jsx(Box, { marginLeft: 2, marginTop: 1, children: jsx(SelectInput, { items: ITEMS.map((it) => ({ label: it.label, value: it.value })), onSelect: (item) => onSelect(item.value), itemComponent: ({ isSelected, label }) => {
21
+ const entry = ITEMS.find((it) => it.label === label);
22
+ return (jsxs(Box, { children: [jsxs(Text, { color: isSelected ? colors.accent : colors.muted, bold: true, children: [isSelected ? icons.triangleRight : ' ', ' '] }), jsx(Text, { color: isSelected ? colors.primary : colors.muted, bold: isSelected, children: label.padEnd(8) }), entry ? (jsx(Text, { color: colors.muted, dimColor: true, children: entry.hint })) : null] }));
23
+ } }) })] }));
24
+ }
25
+
26
+ export { ScopePicker };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ interface SpinnerProps {
3
+ readonly label?: string;
4
+ readonly color?: string;
5
+ readonly variant?: 'dots' | 'line' | 'arc';
6
+ }
7
+ export declare function Spinner({ label, color, variant, }: SpinnerProps): React.ReactElement;
8
+ export {};
@@ -0,0 +1,10 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Box, Text } from 'ink';
3
+ import InkSpinner from 'ink-spinner';
4
+ import { colors } from '../theme/colors.mjs';
5
+
6
+ function Spinner({ label, color = colors.primary, variant = 'dots', }) {
7
+ return (jsxs(Box, { children: [jsx(Text, { color: color, children: jsx(InkSpinner, { type: variant }) }), label ? (jsxs(Text, { color: colors.muted, children: [' ', label] })) : null] }));
8
+ }
9
+
10
+ export { Spinner };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export type StatusKind = 'created' | 'updated' | 'skipped' | 'warn' | 'delete' | 'pending' | 'running' | 'done' | 'failed';
3
+ interface StatusBadgeProps {
4
+ readonly kind: StatusKind;
5
+ readonly label?: string;
6
+ }
7
+ export declare function StatusBadge({ kind, label, }: StatusBadgeProps): React.ReactElement;
8
+ export {};
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { Phase } from '../types/index.js';
3
+ interface StepTrackerProps {
4
+ readonly phase: Phase;
5
+ readonly targetCount?: number;
6
+ readonly scope?: string;
7
+ }
8
+ export declare function StepTracker({ phase, targetCount, scope, }: StepTrackerProps): React.ReactElement;
9
+ export {};
@@ -0,0 +1,43 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { colors } from '../theme/colors.mjs';
5
+ import { icons } from '../theme/icons.mjs';
6
+ import { Spinner } from './Spinner.mjs';
7
+
8
+ const STEPS = [
9
+ { key: 'resolve', label: 'resolve', phases: ['booting', 'resolving'] },
10
+ { key: 'scope', label: 'scope', phases: ['scope-select'] },
11
+ { key: 'plan', label: 'plan', phases: ['planning', 'diff-review', 'force-confirm'] },
12
+ { key: 'apply', label: 'apply', phases: ['applying'] },
13
+ { key: 'done', label: 'done', phases: ['summary', 'error'] },
14
+ ];
15
+ function stepState(phaseIndex, stepIndex) {
16
+ if (stepIndex < phaseIndex)
17
+ return 'done';
18
+ if (stepIndex === phaseIndex)
19
+ return 'active';
20
+ return 'pending';
21
+ }
22
+ function phaseToIndex(kind) {
23
+ for (let i = 0; i < STEPS.length; i += 1) {
24
+ const step = STEPS[i];
25
+ if (step.phases.includes(kind))
26
+ return i;
27
+ }
28
+ return 0;
29
+ }
30
+ function StepTracker({ phase, targetCount, scope, }) {
31
+ const phaseIndex = phaseToIndex(phase.kind);
32
+ return (jsxs(Box, { children: [STEPS.map((step, idx) => {
33
+ const state = stepState(phaseIndex, idx);
34
+ const bulletColor = state === 'done'
35
+ ? colors.success
36
+ : state === 'active'
37
+ ? colors.warn
38
+ : colors.muted;
39
+ return (jsxs(React.Fragment, { children: [idx > 0 ? (jsxs(Text, { color: colors.muted, dimColor: true, children: [' ', icons.divider, ' '] })) : null, state === 'active' ? (jsx(Spinner, { color: bulletColor })) : (jsx(Text, { color: bulletColor, children: state === 'done' ? icons.bulletDone : icons.bulletPending })), jsxs(Text, { color: bulletColor, bold: state === 'active', dimColor: state === 'pending', children: [' ', step.label] })] }, step.key));
40
+ }), typeof targetCount === 'number' || scope ? (jsxs(Text, { color: colors.muted, dimColor: true, children: [' ', typeof targetCount === 'number' ? `${targetCount} target${targetCount === 1 ? '' : 's'}` : '', scope ? ` · scope ${scope}` : ''] })) : null] }));
41
+ }
42
+
43
+ export { StepTracker };
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { InjectReport } from '../../core/index.js';
3
+ interface SummaryProps {
4
+ readonly reports: readonly InjectReport[];
5
+ readonly exitCode: 0 | 1 | 2;
6
+ readonly dryRun: boolean;
7
+ }
8
+ export declare function Summary({ reports, exitCode, dryRun, }: SummaryProps): React.ReactElement;
9
+ export {};
@@ -0,0 +1,30 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Box, Text } from 'ink';
3
+ import { colors } from '../theme/colors.mjs';
4
+
5
+ function aggregate(reports) {
6
+ return reports.reduce((acc, report) => ({
7
+ created: acc.created + report.created.length,
8
+ updated: acc.updated + report.updated.length,
9
+ skipped: acc.skipped + report.skipped.length,
10
+ warnings: acc.warnings + report.warnings.length,
11
+ deleted: acc.deleted + report.deleted.length,
12
+ }), { created: 0, updated: 0, skipped: 0, warnings: 0, deleted: 0 });
13
+ }
14
+ function exitColor(code) {
15
+ if (code === 0)
16
+ return colors.success;
17
+ if (code === 2)
18
+ return colors.warn;
19
+ return colors.danger;
20
+ }
21
+ function Row({ label, value, labelColor, valueColor, labelBold, valueBold, }) {
22
+ return (jsxs(Box, { children: [jsx(Box, { width: 10, children: jsx(Text, { color: labelColor, bold: labelBold, children: label }) }), jsx(Text, { color: valueColor ?? colors.muted, bold: valueBold, children: String(value).padStart(3) })] }));
23
+ }
24
+ function Summary({ reports, exitCode, dryRun, }) {
25
+ const totals = aggregate(reports);
26
+ const exitTone = exitColor(exitCode);
27
+ return (jsxs(Box, { flexDirection: "column", borderStyle: "double", borderColor: colors.primary, paddingX: 2, paddingY: 0, marginTop: 1, alignSelf: "flex-start", children: [jsx(Box, { children: jsxs(Text, { bold: true, color: colors.primary, children: ["Summary", dryRun ? ' (dry-run)' : ''] }) }), jsxs(Box, { marginTop: 1, children: [jsxs(Box, { flexDirection: "column", marginRight: 4, children: [jsx(Row, { label: "created", value: totals.created, labelColor: colors.success, labelBold: true }), jsx(Row, { label: "skipped", value: totals.skipped, labelColor: colors.muted }), jsx(Row, { label: "deleted", value: totals.deleted, labelColor: colors.danger })] }), jsxs(Box, { flexDirection: "column", children: [jsx(Row, { label: "updated", value: totals.updated, labelColor: colors.success }), jsx(Row, { label: "warnings", value: totals.warnings, labelColor: colors.warn, labelBold: true }), jsx(Row, { label: "exit", value: exitCode, labelColor: exitTone, labelBold: true, valueColor: exitTone, valueBold: true })] })] })] }));
28
+ }
29
+
30
+ export { Summary };
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import type { InjectPlan } from '../../core/buildPlan/index.js';
3
+ import type { ConsumerPackage } from '../../commands/runCli/type.js';
4
+ interface TargetCardProps {
5
+ readonly target: ConsumerPackage;
6
+ readonly plan?: InjectPlan;
7
+ readonly expanded?: boolean;
8
+ readonly highlighted?: boolean;
9
+ }
10
+ export declare function TargetCard({ target, plan, expanded, highlighted, }: TargetCardProps): React.ReactElement;
11
+ export {};
@@ -0,0 +1,29 @@
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
+ import { Box, Text } from 'ink';
3
+ import { colors } from '../theme/colors.mjs';
4
+ import { icons } from '../theme/icons.mjs';
5
+ import { PlanTable } from './PlanTable.mjs';
6
+
7
+ function countActions(actions) {
8
+ let copy = 0;
9
+ let skip = 0;
10
+ let warn = 0;
11
+ let del = 0;
12
+ for (const action of actions) {
13
+ if (action.kind === 'copy')
14
+ copy += 1;
15
+ else if (action.kind === 'skip-uptodate')
16
+ skip += 1;
17
+ else if (action.kind === 'warn-diverged' || action.kind === 'warn-orphan')
18
+ warn += 1;
19
+ else if (action.kind === 'delete')
20
+ del += 1;
21
+ }
22
+ return { copy, skip, warn, del };
23
+ }
24
+ function TargetCard({ target, plan, expanded = true, highlighted = false, }) {
25
+ const counts = plan ? countActions(plan.actions) : null;
26
+ return (jsxs(Box, { flexDirection: "column", marginTop: 1, children: [jsxs(Box, { children: [jsxs(Text, { color: highlighted ? colors.accent : colors.primary, bold: true, children: [icons.triangleRight, ' '] }), jsx(Text, { bold: true, children: target.name }), jsxs(Text, { color: colors.muted, dimColor: true, children: ["@", target.version] }), counts ? (jsxs(Text, { color: colors.muted, children: [' [', jsxs(Text, { color: colors.success, bold: true, children: [counts.copy, " copy"] }), ' · ', jsxs(Text, { color: colors.muted, dimColor: true, children: [counts.skip, " skip"] }), counts.warn > 0 ? (jsxs(Fragment, { children: [' · ', jsxs(Text, { color: colors.warn, bold: true, children: [counts.warn, " ", icons.warning] })] })) : null, counts.del > 0 ? (jsxs(Fragment, { children: [' · ', jsxs(Text, { color: colors.danger, bold: true, children: [counts.del, " ", icons.minus] })] })) : null, ']'] })) : null] }), expanded && plan ? (jsx(Box, { marginLeft: 2, children: jsx(PlanTable, { plan: plan }) })) : null] }));
27
+ }
28
+
29
+ export { TargetCard };
@@ -0,0 +1,12 @@
1
+ import type { InjectReport } from '../../core/index.js';
2
+ import type { InjectEvent, TargetPlan } from '../types/index.js';
3
+ interface ApplyStepInput {
4
+ readonly plans: readonly TargetPlan[];
5
+ readonly dryRun: boolean;
6
+ readonly dispatch: (event: InjectEvent) => void;
7
+ }
8
+ export declare function applyAllPlans({ plans, dryRun, dispatch, }: ApplyStepInput): Promise<{
9
+ reports: InjectReport[];
10
+ exitCode: 0 | 1 | 2;
11
+ }>;
12
+ export {};