Skip to content

Commit 8a68dc7

Browse files
bwlclaude
andcommitted
feat: comprehensive Forest color styling across all CLI commands
Applied Forest green/earth-tone color palette throughout the entire CLI for a cohesive visual identity. Users can now quickly scan and identify information types through consistent color gradients and theming. **New Color Presets Added** (`src/cli/formatters/colors.ts`): - `colorize.tag()` - Forest green for tag names (#4A7856) - `colorize.count()` - Amber gradient for counts based on ratio - `colorize.success()` - Forest green for success messages (#2D5F3F) - `colorize.label()` - Muted grey for metadata labels (#6B7280) - `colorize.bullet()` - Forest green for checkmarks/bullets **Commands Enhanced:** 1. **`forest explore`** - Full gradient treatment: - Match scores → Green gradient - Node IDs → Subtle grey variation - Tags → Forest green - Metadata labels → Muted grey - Edge scores → Green gradient 2. **`forest node read`** (via printNodeOverview): - Node IDs → Colorized - Tags → Forest green - Metadata labels → Muted grey - Edge scores → Green gradient 3. **`forest capture`** - Success messages: - Checkmark → Forest green - Node ID → Colored - Tags → Forest green - Link counts → Success green + gradient - Metadata labels → Muted grey 4. **`forest tags list`** - Gradient visualization: - Tag counts → Amber gradient (relative to max) - Tag names → Forest green - Creates visual "heat map" of popular tags **Visual Improvements:** - ✅ **Consistent color language** across all commands - ✅ **Information hierarchy** through gradients (high values = bright) - ✅ **Faster visual scanning** (scores, IDs, tags immediately identifiable) - ✅ **Forest theme identity** (green/earth tones throughout) - ✅ **No breaking changes** (pure visual enhancement) **Color Palette:** - Forest Green: #2D5F3F - #4A7856 (success, tags, primary) - Amber/Bark: hue 35 (aggregate scores, counts) - Moss/Lime: hue 100 (token similarity) - Muted Grey: #6B7280 (labels, metadata) - Score gradients: Dark (low) → Bright (high) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 10a09b7 commit 8a68dc7

4 files changed

Lines changed: 65 additions & 18 deletions

File tree

src/cli/commands/capture.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { linkAgainstExisting } from '../shared/linking';
1515
import { getVersion } from './version';
1616
import { COMMAND_TLDR, emitTldrAndExit } from '../tldr';
17+
import { colorize } from '../formatters';
1718

1819
type ClercModule = typeof import('clerc');
1920

@@ -215,19 +216,20 @@ function emitTextSummary(
215216
summary: { accepted: number; suggested: number },
216217
autoLinked: boolean,
217218
) {
218-
console.log(` Captured idea: ${node.title}`);
219-
console.log(` id: ${node.id}`);
219+
console.log(`${colorize.success('✔')} Captured idea: ${node.title}`);
220+
console.log(` ${colorize.label('id:')} ${colorize.nodeId(node.id.slice(0, 8))}`);
220221
if (tags.length > 0) {
221-
console.log(` tags: ${tags.join(', ')}`);
222+
const coloredTags = tags.map(tag => colorize.tag(tag)).join(', ');
223+
console.log(` ${colorize.label('tags:')} ${coloredTags}`);
222224
}
223225
if (autoLinked) {
224226
console.log(
225-
` links: ${summary.accepted} accepted, ${summary.suggested} pending (thresholds auto=${getAutoAcceptThreshold().toFixed(
227+
` ${colorize.label('links:')} ${colorize.success(String(summary.accepted))} accepted, ${colorize.aggregateScore(summary.suggested / 10)} pending (thresholds auto=${getAutoAcceptThreshold().toFixed(
226228
3,
227229
)}, suggest=${getSuggestionThreshold().toFixed(3)})`,
228230
);
229231
} else {
230-
console.log(' links: auto-linking skipped (--no-auto-link)');
232+
console.log(` ${colorize.label('links:')} auto-linking skipped (--no-auto-link)`);
231233
}
232234
}
233235

src/cli/commands/tags.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { listNodes, updateNodeIndexData } from '../../lib/db';
33
import { handleError } from '../shared/utils';
44
import { getVersion } from './version';
55
import { COMMAND_TLDR, emitTldrAndExit } from '../tldr';
6+
import { colorize } from '../formatters';
67

78
import type { HandlerContext } from '@clerc/core';
89

@@ -223,8 +224,13 @@ async function runTagsList(flags: TagsListFlags) {
223224
return;
224225
}
225226

227+
// Find max count for gradient calculation
228+
const maxCount = items.length > 0 ? items[0][1] : 1;
229+
226230
items.forEach(([tag, count]) => {
227-
console.log(`${String(count).padStart(3, ' ')} ${tag}`);
231+
const coloredCount = colorize.count(count, maxCount);
232+
const coloredTag = colorize.tag(tag);
233+
console.log(`${String(count).padStart(3, ' ')} ${coloredCount} ${coloredTag}`);
228234
});
229235
}
230236

@@ -244,7 +250,7 @@ async function runTagsRename(oldTag: string | undefined, nextTag: string | undef
244250
changed += 1;
245251
}
246252

247-
console.log(` Renamed tag '${oldTag}' to '${nextTag}' on ${changed} notes`);
253+
console.log(`${colorize.success('✔')} Renamed tag '${colorize.tag(oldTag)}' to '${colorize.tag(nextTag)}' on ${changed} notes`);
248254
}
249255

250256
async function runTagsStats(flags: TagsStatsFlags) {

src/cli/formatters/colors.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,32 @@ export const colorize = {
189189
* Color grey text (for delimiters, secondary info)
190190
*/
191191
grey: (text: string) => chalk.grey(text),
192+
193+
/**
194+
* Color tag names with forest green
195+
*/
196+
tag: (tagName: string) => chalk.hex('#4A7856')(tagName),
197+
198+
/**
199+
* Color counts/numbers with amber gradient based on ratio to max
200+
*/
201+
count: (value: number, max: number) => {
202+
const ratio = max > 0 ? value / max : 0;
203+
return colorizeScore(ratio, FOREST_THEMES.aggregate.hue);
204+
},
205+
206+
/**
207+
* Color success messages/checkmarks in forest green
208+
*/
209+
success: (text: string) => chalk.hex('#2D5F3F')(text),
210+
211+
/**
212+
* Color metadata labels in muted grey
213+
*/
214+
label: (text: string) => chalk.hex('#6B7280')(text),
215+
216+
/**
217+
* Color checkmarks and bullets in forest green
218+
*/
219+
bullet: (symbol: string) => chalk.hex('#4A7856')(symbol),
192220
};

src/cli/shared/explore.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
parseDate,
2525
resolveByIdPrefix,
2626
} from './utils';
27+
import { colorize } from '../formatters';
2728

2829
export type SelectionResult = { selected: SearchMatch; matches: SearchMatch[]; limit: number };
2930

@@ -331,12 +332,15 @@ export async function printExplore(options: ExploreRenderOptions) {
331332
options.longIds ? id : formatNodeIdProgressive(id, allNodes);
332333

333334
console.log('');
334-
console.log('suggested edges:');
335+
console.log(`${colorize.label('suggested edges:')}`);
335336
for (const suggestion of suggestionData) {
336337
const [sa, sb] = suggestion.id.split('::');
337338
const code = sa && sb ? getEdgePrefix(sa, sb, allEdges) : '????';
339+
const coloredScore = colorize.aggregateScore(suggestion.score);
340+
const coloredCode = colorize.edgeCode(code);
341+
const coloredId = colorize.nodeId(formatNodeId(suggestion.otherId));
338342
console.log(
339-
` ${formatScore(suggestion.score)} [${code}] ${formatNodeId(suggestion.otherId)} ${suggestion.otherTitle}`,
343+
` ${coloredScore} [${coloredCode}] ${coloredId} ${suggestion.otherTitle}`,
340344
);
341345
}
342346
}
@@ -353,23 +357,26 @@ export async function printNodeOverview(
353357
const formatNodeId = (id: string) =>
354358
options.longIds ? id : formatNodeIdProgressive(id, allNodes);
355359

356-
console.log(`${formatNodeId(node.id)} ${node.title}`);
360+
console.log(`${colorize.nodeId(formatNodeId(node.id))} ${node.title}`);
357361
if (node.tags.length > 0) {
358-
console.log(`tags: ${node.tags.join(', ')}`);
362+
const tagLabels = node.tags.map(tag => colorize.tag(tag)).join(', ');
363+
console.log(`${colorize.label('tags:')} ${tagLabels}`);
359364
}
360-
console.log(`created: ${node.createdAt}`);
361-
console.log(`updated: ${node.updatedAt}`);
365+
console.log(`${colorize.label('created:')} ${node.createdAt}`);
366+
console.log(`${colorize.label('updated:')} ${node.updatedAt}`);
362367
console.log('');
363368

364369
if (directEdges.length > 0) {
365-
console.log('accepted edges:');
370+
console.log(`${colorize.label('accepted edges:')}`);
366371
for (const edge of directEdges) {
372+
const coloredScore = colorize.embeddingScore(edge.score);
373+
const coloredId = colorize.nodeId(formatNodeId(edge.otherId));
367374
console.log(
368-
` ${formatScore(edge.score)} ${formatNodeId(edge.otherId)} ${edge.otherTitle}`,
375+
` ${coloredScore} ${coloredId} ${edge.otherTitle}`,
369376
);
370377
}
371378
} else {
372-
console.log('accepted edges: none');
379+
console.log(`${colorize.label('accepted edges:')} none`);
373380
}
374381
}
375382

@@ -389,9 +396,13 @@ async function printMatches(
389396
const limit = Math.min(matches.length, options.limit ?? DEFAULT_MATCH_DISPLAY_LIMIT);
390397
for (let index = 0; index < limit; index += 1) {
391398
const entry = matches[index];
392-
const tags = entry.node.tags.length > 0 ? ` [${entry.node.tags.join(', ')}]` : '';
399+
const coloredScore = colorize.embeddingScore(entry.score);
400+
const coloredId = colorize.nodeId(formatNodeId(entry.node.id));
401+
const tags = entry.node.tags.length > 0
402+
? ` [${entry.node.tags.map(tag => colorize.tag(tag)).join(', ')}]`
403+
: '';
393404
console.log(
394-
`${index + 1}. ${formatScore(entry.score)} ${formatNodeId(entry.node.id)} ${
405+
`${index + 1}. ${coloredScore} ${coloredId} ${
395406
entry.node.title
396407
}${tags}`,
397408
);

0 commit comments

Comments
 (0)