@pencil-agent/nano-pencil 1.3.0 โ†’ 1.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.
@@ -24,6 +24,11 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
24
24
  },
25
25
  { name: "apikey", description: "Update API key for current provider" },
26
26
  { name: "mcp", description: "Manage MCP servers (list, enable, disable)" },
27
+ { name: "soul", description: "Show AI personality and stats (Soul)" },
28
+ {
29
+ name: "memory",
30
+ description: "Show project memory and knowledge (NanoMem)",
31
+ },
27
32
  { name: "export", description: "Export session to HTML file" },
28
33
  { name: "share", description: "Share session as a secret GitHub gist" },
29
34
  { name: "copy", description: "Copy last agent message to clipboard" },
@@ -25,6 +25,7 @@ export {
25
25
  export { LoginDialogComponent } from "./login-dialog.js";
26
26
  export { ModelSelectorComponent } from "./model-selector.js";
27
27
  export { OAuthSelectorComponent } from "./oauth-selector.js";
28
+ export { PencilLoader } from "./pencil-loader.js";
28
29
  export { ProviderSelectorComponent } from "./provider-selector.js";
29
30
  export {
30
31
  type ModelsCallbacks,
@@ -0,0 +1,139 @@
1
+ /**
2
+ * [INPUT]: NanoMem engine
3
+ * [OUTPUT]: Formatted display of memory stats
4
+ * [POS]: Interactive mode component for /memory command
5
+ */
6
+
7
+ // Use any type for NanoMemEngine since it's a local package
8
+ interface NanoMemEngine {
9
+ getStats(): {
10
+ knowledge: number;
11
+ lessons: number;
12
+ preferences: number;
13
+ facets: number;
14
+ work: number;
15
+ episodes: number;
16
+ totalSessions: number;
17
+ };
18
+ }
19
+
20
+ interface DisplayOptions {
21
+ compact?: boolean;
22
+ }
23
+
24
+ /**
25
+ * Format NanoMem stats for display
26
+ */
27
+ export function formatMemoryStats(
28
+ memory: any,
29
+ options: DisplayOptions = {},
30
+ ): string {
31
+ if (options.compact) {
32
+ return formatCompactMemory(memory);
33
+ }
34
+
35
+ return formatFullMemory(memory);
36
+ }
37
+
38
+ /**
39
+ * Format compact memory stats (single line)
40
+ */
41
+ function formatCompactMemory(memory: NanoMemEngine): string {
42
+ const stats = memory.getStats();
43
+
44
+ const totalMemories =
45
+ stats.knowledge +
46
+ stats.lessons +
47
+ stats.preferences +
48
+ stats.facets +
49
+ stats.episodes;
50
+
51
+ return `๐Ÿ“š Memory: ${totalMemories} ้กน | Knowledge: ${stats.knowledge} | Lessons: ${stats.lessons} | Episodes: ${stats.episodes}`;
52
+ }
53
+
54
+ /**
55
+ * Format full memory stats (detailed view)
56
+ */
57
+ function formatFullMemory(memory: NanoMemEngine): string {
58
+ const lines: string[] = [];
59
+
60
+ lines.push("โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—");
61
+ lines.push("โ•‘ ๐Ÿ“š Project Memory - NanoMem โ•‘");
62
+ lines.push("โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ");
63
+ lines.push("โ•‘");
64
+
65
+ const stats = memory.getStats();
66
+
67
+ // Memory Types
68
+ const memoryTypes = [
69
+ { type: "knowledge", name: "็Ÿฅ่ฏ†", emoji: "๐Ÿ“–", count: stats.knowledge },
70
+ { type: "lessons", name: "็ป้ชŒๆ•™่ฎญ", emoji: "๐Ÿ’ก", count: stats.lessons },
71
+ {
72
+ type: "preferences",
73
+ name: "็”จๆˆทๅๅฅฝ",
74
+ emoji: "โค๏ธ",
75
+ count: stats.preferences,
76
+ },
77
+ { type: "facets", name: "ๆจกๅผ/ๅ›ฐๅขƒ", emoji: "๐Ÿงฉ", count: stats.facets },
78
+ { type: "work", name: "ๅทฅไฝœๆ‘˜่ฆ", emoji: "๐Ÿ“‹", count: stats.work },
79
+ { type: "episodes", name: "ไผš่ฏ่ฎฐๅฝ•", emoji: "๐Ÿ“", count: stats.episodes },
80
+ ];
81
+
82
+ lines.push("โ•‘ ๐Ÿ“Š Memory Types");
83
+ lines.push("โ•‘ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
84
+
85
+ for (const mem of memoryTypes) {
86
+ const bar = createBar(Math.min(mem.count / 100, 1), 15);
87
+ lines.push(
88
+ `โ•‘ ${mem.emoji} ${mem.name.padEnd(12)} ${bar} ${mem.count.toString().padStart(5)} ้กน`,
89
+ );
90
+ }
91
+
92
+ lines.push("โ•‘");
93
+ lines.push("โ•‘ ๐Ÿ” Recent Knowledge (Top 5)");
94
+ lines.push("โ•‘ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
95
+
96
+ // Note: This would require loading actual entries
97
+ // For now, show stats only
98
+ lines.push(`โ•‘ (ๅ…ฑ ${stats.knowledge} ๆก้กน็›ฎ็Ÿฅ่ฏ†)`);
99
+ lines.push(`โ•‘ ๆœ€่ฟ‘ๆ›ดๆ–ฐ: ${stats.totalSessions > 0 ? "ๆœฌๆฌกไผš่ฏ" : "ๆ— "}`);
100
+
101
+ lines.push("โ•‘");
102
+ lines.push("โ•‘ ๐Ÿ’ก Lessons Learned");
103
+ lines.push("โ•‘ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
104
+ lines.push(`โ•‘ (ๅ…ฑ ${stats.lessons} ๆก็ป้ชŒๆ•™่ฎญ)`);
105
+ lines.push(` โš ๏ธ ไปŽ้”™่ฏฏไธญๅญฆไน ๏ผŒ้ฟๅ…้‡ๅค็Šฏ้”™`);
106
+
107
+ lines.push("โ•‘");
108
+ lines.push("โ•‘ ๐Ÿงฉ Patterns & Struggles");
109
+ lines.push("โ•‘ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
110
+ lines.push(`โ•‘ ๆจกๅผ: ${stats.facets} ๆก`);
111
+ lines.push(` ๅ›ฐๅขƒ: ๅทฒ่ฏ†ๅˆซ็š„่กŒไธบๆจกๅผ`);
112
+
113
+ lines.push("โ•‘");
114
+ lines.push("โ•‘ ๐Ÿ“ Session History");
115
+ lines.push("โ•‘ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
116
+ lines.push(`โ•‘ ไผš่ฏ่ฎฐๅฝ•: ${stats.episodes} ๆก`);
117
+ lines.push(` ๆ€ปไผš่ฏๆ•ฐ: ${stats.totalSessions}`);
118
+
119
+ lines.push("โ•‘");
120
+ lines.push("โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•");
121
+
122
+ return lines.join("\n");
123
+ }
124
+
125
+ /**
126
+ * Create a visual bar for counts
127
+ */
128
+ function createBar(filled: number, width: number): string {
129
+ const intFilled = Math.floor(filled);
130
+ const partial = Math.floor((filled - intFilled) * 8);
131
+
132
+ let bar = "โ–ˆ".repeat(intFilled);
133
+ if (partial > 0) {
134
+ bar += "โ–“".repeat(1); // Partial fill
135
+ }
136
+ bar += "โ–‘".repeat(width - intFilled - (partial > 0 ? 1 : 0));
137
+
138
+ return bar;
139
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * PencilLoader - Custom loader with โœŽ breathing animation
3
+ * Replaces the default spinner with a pencil icon that pulses
4
+ */
5
+
6
+ import { Container, Spacer, Text, type TUI } from "@mariozechner/pi-tui";
7
+ import type { Theme } from "../theme/theme.js";
8
+
9
+ export class PencilLoader extends Container {
10
+ private tui: TUI;
11
+ private theme: Theme;
12
+ private message: string;
13
+ private interval: NodeJS.Timeout | undefined;
14
+ private currentFrame = 0;
15
+ private textComponent: Text;
16
+ private messageComponent: Text;
17
+ private isStopped = false;
18
+
19
+ // Breathing animation frames - opacity levels for โœŽ
20
+ private readonly frames = [
21
+ "โ–‘", // very dim
22
+ "โ–’", // dim
23
+ "โ–“", // medium
24
+ "โ–ˆ", // bright
25
+ "โ–“", // medium
26
+ "โ–’", // dim
27
+ ];
28
+
29
+ constructor(tui: TUI, theme: Theme, message: string) {
30
+ super();
31
+ this.tui = tui;
32
+ this.theme = theme;
33
+ this.message = message;
34
+
35
+ this.textComponent = new Text("", 0, 0);
36
+ this.messageComponent = new Text("", 0, 0);
37
+
38
+ this.addChild(new Spacer(1));
39
+ this.addChild(this.textComponent);
40
+ this.addChild(this.messageComponent);
41
+ this.addChild(new Spacer(1));
42
+
43
+ this.startAnimation();
44
+ }
45
+
46
+ private startAnimation(): void {
47
+ const updateFrame = () => {
48
+ if (this.isStopped) return;
49
+
50
+ const frameChar = this.frames[this.currentFrame];
51
+ const pencil = this.theme.fg("accent", `โœŽ${frameChar}`);
52
+
53
+ this.textComponent.setText(`${pencil} ${this.message}`);
54
+ this.tui.requestRender();
55
+
56
+ this.currentFrame = (this.currentFrame + 1) % this.frames.length;
57
+ };
58
+
59
+ // Initial render
60
+ updateFrame();
61
+
62
+ // Update every 200ms for smooth breathing effect
63
+ this.interval = setInterval(updateFrame, 200);
64
+ }
65
+
66
+ setMessage(message: string): void {
67
+ this.message = message;
68
+ const frameChar = this.frames[this.currentFrame];
69
+ const pencil = this.theme.fg("accent", `โœŽ${frameChar}`);
70
+ this.messageComponent.setText(`${pencil} ${this.message}`);
71
+ this.tui.requestRender();
72
+ }
73
+
74
+ stop(): void {
75
+ this.isStopped = true;
76
+ if (this.interval) {
77
+ clearInterval(this.interval);
78
+ this.interval = undefined;
79
+ }
80
+ }
81
+
82
+ dispose(): void {
83
+ this.stop();
84
+ }
85
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * [INPUT]: Soul profile and memory manager
3
+ * [OUTPUT]: Formatted display of Soul stats and personality
4
+ * [POS]: Interactive mode component for /soul command
5
+ */
6
+
7
+ import type { SoulManager } from "nanosoul";
8
+
9
+ interface DisplayOptions {
10
+ compact?: boolean;
11
+ }
12
+
13
+ /**
14
+ * Format Soul stats for display
15
+ */
16
+ export function formatSoulStats(
17
+ soul: SoulManager,
18
+ options: DisplayOptions = {},
19
+ ): string {
20
+ const profile = soul.getProfile();
21
+ const stats = soul.getStats();
22
+
23
+ if (options.compact) {
24
+ return formatCompactSoul(profile, stats);
25
+ }
26
+
27
+ return formatFullSoul(profile, stats);
28
+ }
29
+
30
+ /**
31
+ * Format compact Soul stats (single line)
32
+ */
33
+ function formatCompactSoul(profile: any, stats: any): string {
34
+ const personality = profile.personality;
35
+ const expertise = stats.expertise.slice(0, 3);
36
+
37
+ const topTraits = Object.entries(personality)
38
+ .filter(
39
+ ([_, value]) => typeof value === "number" && (value > 0.6 || value < 0.4),
40
+ )
41
+ .map(([key, value]) => {
42
+ const label = getTraitLabel(key);
43
+ const numValue = typeof value === "number" ? value : 0.5;
44
+ const status = numValue > 0.6 ? "โ†‘" : numValue < 0.4 ? "โ†“" : "โ†’";
45
+ return `${label}${status} ${(numValue * 100).toFixed(0)}%`;
46
+ })
47
+ .slice(0, 3)
48
+ .join(", ");
49
+
50
+ const topExpertise = expertise
51
+ .map((e: any) => `${e.domain}(${(e.confidence * 100).toFixed(0)}%)`)
52
+ .join(", ");
53
+
54
+ return `๐Ÿง  Soul: ${topTraits} | Expertise: ${topExpertise} | Interactions: ${stats.stats.totalInteractions}`;
55
+ }
56
+
57
+ /**
58
+ * Format full Soul stats (detailed view)
59
+ */
60
+ function formatFullSoul(profile: any, stats: any): string {
61
+ const lines: string[] = [];
62
+
63
+ lines.push("โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—");
64
+ lines.push("โ•‘ ๐Ÿง  AI Soul - Personality & Stats โ•‘");
65
+ lines.push("โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ");
66
+ lines.push("โ•‘");
67
+
68
+ // Personality Section
69
+ lines.push("โ•‘ ๐Ÿ“Š Personality Traits");
70
+ lines.push("โ•‘ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
71
+
72
+ const personality = profile.personality;
73
+ const traits = [
74
+ { key: "openness", label: "ๅผ€ๆ”พๆ€ง", emoji: "๐ŸŽจ" },
75
+ { key: "conscientiousness", label: "ๅฐฝ่ดฃๆ€ง", emoji: "๐Ÿ“‹" },
76
+ { key: "codeVerbosity", label: "ไปฃ็ ๅ†—้•ฟ", emoji: "๐Ÿ“" },
77
+ { key: "abstractionLevel", label: "ๆŠฝ่ฑกๅฑ‚็บง", emoji: "๐Ÿ—๏ธ" },
78
+ { key: "safetyMargin", label: "ๅฎ‰ๅ…จ่พน้™…", emoji: "๐Ÿ›ก๏ธ" },
79
+ { key: "explorationDrive", label: "ๆŽข็ดขๆฌฒๆœ›", emoji: "๐Ÿ”" },
80
+ ];
81
+
82
+ for (const trait of traits) {
83
+ const value = personality[trait.key];
84
+ const bar = createBar(value, 10);
85
+ const percent = (value * 100).toFixed(0).padStart(3);
86
+ lines.push(`โ•‘ ${trait.emoji} ${trait.label.padEnd(12)} ${bar} ${percent}%`);
87
+ }
88
+
89
+ lines.push("โ•‘");
90
+ lines.push("โ•‘ ๐ŸŽฏ Top Expertise Areas");
91
+
92
+ const expertise = stats.expertise.slice(0, 5);
93
+ if (expertise.length === 0) {
94
+ lines.push("โ•‘ (ๆš‚ๆ— ไธ“้•ฟๆ•ฐๆฎ๏ผŒ็ปง็ปญไฝฟ็”จไปฅ็งฏ็ดฏ)");
95
+ } else {
96
+ for (const exp of expertise) {
97
+ const confidence = (exp.confidence * 100).toFixed(0).padStart(3);
98
+ const examples = exp.examples.toString().padStart(3);
99
+ lines.push(
100
+ `โ•‘ โ€ข ${exp.domain.padEnd(20)} ไฟกๅฟƒ: ${confidence}% ๆˆๅŠŸ: ${examples} ๆฌก`,
101
+ );
102
+ }
103
+ }
104
+
105
+ lines.push("โ•‘");
106
+ lines.push("โ•‘ ๐Ÿ’ญ Current Mood");
107
+
108
+ const emotional = profile.emotionalState;
109
+ const mood = [
110
+ { label: "ไฟกๅฟƒ", value: emotional.confidence, emoji: "๐Ÿ˜Š" },
111
+ { label: "ๅฅฝๅฅ‡ๅฟƒ", value: emotional.curiosity, emoji: "๐Ÿค”" },
112
+ { label: "ๆŒซ่ดฅๆ„Ÿ", value: emotional.frustration, emoji: "๐Ÿ˜ค" },
113
+ { label: "ๅฟƒๆต", value: emotional.flow, emoji: "โœจ" },
114
+ ];
115
+
116
+ for (const m of mood) {
117
+ const bar = createBar(m.value, 8);
118
+ const percent = (m.value * 100).toFixed(0).padStart(3);
119
+ lines.push(`โ•‘ ${m.emoji} ${m.label.padEnd(8)} ${bar} ${percent}%`);
120
+ }
121
+
122
+ lines.push("โ•‘");
123
+ lines.push("โ•‘ ๐Ÿ“ˆ Development Stats");
124
+ lines.push("โ•‘ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
125
+ lines.push(`โ•‘ ๆ€ปไบคไบ’ๆฌกๆ•ฐ: ${stats.stats.totalInteractions}`);
126
+ lines.push(`โ•‘ ๆˆๅŠŸ็އ: ${(stats.stats.successRate * 100).toFixed(1)}%`);
127
+ lines.push(`โ•‘ Soul ็‰ˆๆœฌ: ${profile.version}`);
128
+ lines.push(
129
+ `โ•‘ Soul ๅนด้พ„: ${Math.floor((Date.now() - profile.createdAt.getTime()) / (1000 * 60 * 60 * 24))} ๅคฉ`,
130
+ );
131
+ lines.push(`โ•‘ ๆœ€ๅŽ่ฟ›ๅŒ–: ${formatTimeAgo(profile.lastEvolved)}`);
132
+
133
+ lines.push("โ•‘");
134
+ lines.push("โ•‘ ๐Ÿง˜ User Relationship");
135
+ lines.push("โ•‘ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
136
+ const rel = profile.userRelationship;
137
+ lines.push(`โ•‘ ไบคไบ’ๆฌกๆ•ฐ: ${rel.interactionCount}`);
138
+ lines.push(`โ•‘ ๆปกๆ„ๅบฆ: ${(rel.satisfactionScore * 100).toFixed(0)}%`);
139
+ lines.push(` ๆฒŸ้€š้ฃŽๆ ผ: ${rel.communicationStyle}`);
140
+
141
+ if (rel.knownPreferences.length > 0) {
142
+ lines.push(`โ•‘ ๅทฒ็Ÿฅๅๅฅฝ: ${rel.knownPreferences.slice(0, 3).join(", ")}`);
143
+ }
144
+
145
+ lines.push("โ•‘");
146
+ lines.push("โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•");
147
+
148
+ return lines.join("\n");
149
+ }
150
+
151
+ /**
152
+ * Create a visual bar for values
153
+ */
154
+ function createBar(value: number, width: number): string {
155
+ const filled = Math.round(value * width);
156
+ const empty = width - filled;
157
+ return "โ–ˆ".repeat(filled) + "โ–‘".repeat(empty);
158
+ }
159
+
160
+ /**
161
+ * Get trait label in Chinese
162
+ */
163
+ function getTraitLabel(key: string): string {
164
+ const labels: Record<string, string> = {
165
+ openness: "ๅผ€ๆ”พๆ€ง",
166
+ conscientiousness: "ๅฐฝ่ดฃๆ€ง",
167
+ extraversion: "ๅค–ๅ‘ๆ€ง",
168
+ agreeableness: "ๅฎœไบบๆ€ง",
169
+ neuroticism: "็ฅž็ป่ดจ",
170
+ codeVerbosity: "ไปฃ็ ๅ†—้•ฟ",
171
+ abstractionLevel: "ๆŠฝ่ฑกๅฑ‚็บง",
172
+ safetyMargin: "ๅฎ‰ๅ…จ่พน้™…",
173
+ explorationDrive: "ๆŽข็ดข",
174
+ };
175
+ return labels[key] || key;
176
+ }
177
+
178
+ /**
179
+ * Format time ago in Chinese
180
+ */
181
+ function formatTimeAgo(date: Date): string {
182
+ const now = Date.now();
183
+ const diff = now - date.getTime();
184
+
185
+ const minutes = Math.floor(diff / (1000 * 60));
186
+ const hours = Math.floor(diff / (1000 * 60 * 60));
187
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
188
+
189
+ if (minutes < 1) return "ๅˆšๅˆš";
190
+ if (minutes < 60) return `${minutes} ๅˆ†้’Ÿๅ‰`;
191
+ if (hours < 24) return `${hours} ๅฐๆ—ถๅ‰`;
192
+ return `${days} ๅคฉๅ‰`;
193
+ }
@@ -95,6 +95,7 @@ import { promptForApiKey } from "./components/apikey-input.js";
95
95
  import { BashExecutionComponent } from "./components/bash-execution.js";
96
96
  import { BorderedLoader } from "./components/bordered-loader.js";
97
97
  import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
98
+ import { PencilLoader } from "./components/pencil-loader.js";
98
99
  import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
99
100
  import { CustomEditor } from "./components/custom-editor.js";
100
101
  import { CustomMessageComponent } from "./components/custom-message.js";
@@ -193,7 +194,7 @@ export class InteractiveMode {
193
194
  private version: string;
194
195
  private isInitialized = false;
195
196
  private onInputCallback?: (text: string) => void;
196
- private loadingAnimation: Loader | undefined = undefined;
197
+ private loadingAnimation: Component | undefined = undefined;
197
198
  private pendingWorkingMessage: string | undefined = undefined;
198
199
  private readonly defaultWorkingMessage = "Working...";
199
200
 
@@ -233,11 +234,11 @@ export class InteractiveMode {
233
234
  private pendingBashComponents: BashExecutionComponent[] = [];
234
235
 
235
236
  // Auto-compaction state
236
- private autoCompactionLoader: Loader | undefined = undefined;
237
+ private autoCompactionLoader: Component | undefined = undefined;
237
238
  private autoCompactionEscapeHandler?: () => void;
238
239
 
239
240
  // Auto-retry state
240
- private retryLoader: Loader | undefined = undefined;
241
+ private retryLoader: Component | undefined = undefined;
241
242
  private retryEscapeHandler?: () => void;
242
243
 
243
244
  // Messages queued while compaction is running
@@ -1153,7 +1154,7 @@ export class InteractiveMode {
1153
1154
  waitForIdle: () => this.session.agent.waitForIdle(),
1154
1155
  newSession: async (options) => {
1155
1156
  if (this.loadingAnimation) {
1156
- this.loadingAnimation.stop();
1157
+ (this.loadingAnimation as PencilLoader).stop();
1157
1158
  this.loadingAnimation = undefined;
1158
1159
  }
1159
1160
  this.statusContainer.clear();
@@ -1439,7 +1440,7 @@ export class InteractiveMode {
1439
1440
  this.defaultEditor.onExtensionShortcut = undefined;
1440
1441
  this.updateTerminalTitle();
1441
1442
  if (this.loadingAnimation) {
1442
- this.loadingAnimation.setMessage(
1443
+ (this.loadingAnimation as PencilLoader).setMessage(
1443
1444
  `${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`,
1444
1445
  );
1445
1446
  }
@@ -1606,9 +1607,9 @@ export class InteractiveMode {
1606
1607
  setWorkingMessage: (message) => {
1607
1608
  if (this.loadingAnimation) {
1608
1609
  if (message) {
1609
- this.loadingAnimation.setMessage(message);
1610
+ (this.loadingAnimation as PencilLoader).setMessage(message);
1610
1611
  } else {
1611
- this.loadingAnimation.setMessage(
1612
+ (this.loadingAnimation as PencilLoader).setMessage(
1612
1613
  `${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`,
1613
1614
  );
1614
1615
  }
@@ -2237,6 +2238,16 @@ export class InteractiveMode {
2237
2238
  await this.handleReloadCommand();
2238
2239
  return;
2239
2240
  }
2241
+ if (text === "/soul") {
2242
+ this.handleSoulCommand();
2243
+ this.editor.setText("");
2244
+ return;
2245
+ }
2246
+ if (text === "/memory") {
2247
+ this.handleMemoryCommand();
2248
+ this.editor.setText("");
2249
+ return;
2250
+ }
2240
2251
  if (text === "/debug") {
2241
2252
  this.handleDebugCommand();
2242
2253
  this.editor.setText("");
@@ -2349,24 +2360,25 @@ export class InteractiveMode {
2349
2360
  this.retryEscapeHandler = undefined;
2350
2361
  }
2351
2362
  if (this.retryLoader) {
2352
- this.retryLoader.stop();
2363
+ (this.retryLoader as PencilLoader).stop();
2353
2364
  this.retryLoader = undefined;
2354
2365
  }
2355
2366
  if (this.loadingAnimation) {
2356
- this.loadingAnimation.stop();
2367
+ (this.loadingAnimation as PencilLoader).stop();
2357
2368
  }
2358
2369
  this.statusContainer.clear();
2359
- this.loadingAnimation = new Loader(
2370
+ this.loadingAnimation = new PencilLoader(
2360
2371
  this.ui,
2361
- (spinner) => theme.fg("accent", spinner),
2362
- (text) => theme.fg("muted", text),
2372
+ theme,
2363
2373
  this.defaultWorkingMessage,
2364
2374
  );
2365
2375
  this.statusContainer.addChild(this.loadingAnimation);
2366
2376
  // Apply any pending working message queued before loader existed
2367
2377
  if (this.pendingWorkingMessage !== undefined) {
2368
2378
  if (this.pendingWorkingMessage) {
2369
- this.loadingAnimation.setMessage(this.pendingWorkingMessage);
2379
+ (this.loadingAnimation as PencilLoader).setMessage(
2380
+ this.pendingWorkingMessage,
2381
+ );
2370
2382
  }
2371
2383
  this.pendingWorkingMessage = undefined;
2372
2384
  }
@@ -2512,7 +2524,7 @@ export class InteractiveMode {
2512
2524
 
2513
2525
  case "agent_end":
2514
2526
  if (this.loadingAnimation) {
2515
- this.loadingAnimation.stop();
2527
+ (this.loadingAnimation as PencilLoader).stop();
2516
2528
  this.loadingAnimation = undefined;
2517
2529
  this.statusContainer.clear();
2518
2530
  }
@@ -2539,10 +2551,9 @@ export class InteractiveMode {
2539
2551
  this.statusContainer.clear();
2540
2552
  const reasonText =
2541
2553
  event.reason === "overflow" ? "Context overflow detected, " : "";
2542
- this.autoCompactionLoader = new Loader(
2554
+ this.autoCompactionLoader = new PencilLoader(
2543
2555
  this.ui,
2544
- (spinner) => theme.fg("accent", spinner),
2545
- (text) => theme.fg("muted", text),
2556
+ theme,
2546
2557
  `${reasonText}Auto-compacting... (${appKey(this.keybindings, "interrupt")} to cancel)`,
2547
2558
  );
2548
2559
  this.statusContainer.addChild(this.autoCompactionLoader);
@@ -2558,7 +2569,7 @@ export class InteractiveMode {
2558
2569
  }
2559
2570
  // Stop loader
2560
2571
  if (this.autoCompactionLoader) {
2561
- this.autoCompactionLoader.stop();
2572
+ (this.autoCompactionLoader as PencilLoader).stop();
2562
2573
  this.autoCompactionLoader = undefined;
2563
2574
  this.statusContainer.clear();
2564
2575
  }
@@ -2598,10 +2609,9 @@ export class InteractiveMode {
2598
2609
  // Show retry indicator
2599
2610
  this.statusContainer.clear();
2600
2611
  const delaySeconds = Math.round(event.delayMs / 1000);
2601
- this.retryLoader = new Loader(
2612
+ this.retryLoader = new PencilLoader(
2602
2613
  this.ui,
2603
- (spinner) => theme.fg("warning", spinner),
2604
- (text) => theme.fg("muted", text),
2614
+ theme,
2605
2615
  `Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s... (${appKey(this.keybindings, "interrupt")} to cancel)`,
2606
2616
  );
2607
2617
  this.statusContainer.addChild(this.retryLoader);
@@ -2617,7 +2627,7 @@ export class InteractiveMode {
2617
2627
  }
2618
2628
  // Stop loader
2619
2629
  if (this.retryLoader) {
2620
- this.retryLoader.stop();
2630
+ (this.retryLoader as PencilLoader).stop();
2621
2631
  this.retryLoader = undefined;
2622
2632
  this.statusContainer.clear();
2623
2633
  }
@@ -4033,7 +4043,7 @@ export class InteractiveMode {
4033
4043
  }
4034
4044
 
4035
4045
  // Set up escape handler and loader if summarizing
4036
- let summaryLoader: Loader | undefined;
4046
+ let summaryLoader: Component | undefined;
4037
4047
  const originalOnEscape = this.defaultEditor.onEscape;
4038
4048
 
4039
4049
  if (wantsSummary) {
@@ -4041,10 +4051,9 @@ export class InteractiveMode {
4041
4051
  this.session.abortBranchSummary();
4042
4052
  };
4043
4053
  this.chatContainer.addChild(new Spacer(1));
4044
- summaryLoader = new Loader(
4054
+ summaryLoader = new PencilLoader(
4045
4055
  this.ui,
4046
- (spinner) => theme.fg("accent", spinner),
4047
- (text) => theme.fg("muted", text),
4056
+ theme,
4048
4057
  `Summarizing branch... (${appKey(this.keybindings, "interrupt")} to cancel)`,
4049
4058
  );
4050
4059
  this.statusContainer.addChild(summaryLoader);
@@ -4081,7 +4090,7 @@ export class InteractiveMode {
4081
4090
  );
4082
4091
  } finally {
4083
4092
  if (summaryLoader) {
4084
- summaryLoader.stop();
4093
+ (summaryLoader as PencilLoader).stop();
4085
4094
  this.statusContainer.clear();
4086
4095
  }
4087
4096
  this.defaultEditor.onEscape = originalOnEscape;
@@ -4146,7 +4155,7 @@ export class InteractiveMode {
4146
4155
  private async handleResumeSession(sessionPath: string): Promise<void> {
4147
4156
  // Stop loading animation
4148
4157
  if (this.loadingAnimation) {
4149
- this.loadingAnimation.stop();
4158
+ (this.loadingAnimation as PencilLoader).stop();
4150
4159
  this.loadingAnimation = undefined;
4151
4160
  }
4152
4161
  this.statusContainer.clear();
@@ -4794,7 +4803,7 @@ export class InteractiveMode {
4794
4803
  private async handleClearCommand(): Promise<void> {
4795
4804
  // Stop loading animation
4796
4805
  if (this.loadingAnimation) {
4797
- this.loadingAnimation.stop();
4806
+ (this.loadingAnimation as PencilLoader).stop();
4798
4807
  this.loadingAnimation = undefined;
4799
4808
  }
4800
4809
  this.statusContainer.clear();
@@ -5011,7 +5020,7 @@ export class InteractiveMode {
5011
5020
  ): Promise<CompactionResult | undefined> {
5012
5021
  // Stop loading animation
5013
5022
  if (this.loadingAnimation) {
5014
- this.loadingAnimation.stop();
5023
+ (this.loadingAnimation as PencilLoader).stop();
5015
5024
  this.loadingAnimation = undefined;
5016
5025
  }
5017
5026
  this.statusContainer.clear();
@@ -5028,12 +5037,7 @@ export class InteractiveMode {
5028
5037
  const label = isAuto
5029
5038
  ? `Auto-compacting context... ${cancelHint}`
5030
5039
  : `Compacting context... ${cancelHint}`;
5031
- const compactingLoader = new Loader(
5032
- this.ui,
5033
- (spinner) => theme.fg("accent", spinner),
5034
- (text) => theme.fg("muted", text),
5035
- label,
5036
- );
5040
+ const compactingLoader = new PencilLoader(this.ui, theme, label);
5037
5041
  this.statusContainer.addChild(compactingLoader);
5038
5042
  this.ui.requestRender();
5039
5043
 
@@ -5065,7 +5069,7 @@ export class InteractiveMode {
5065
5069
  this.showError(`Compaction failed: ${message}`);
5066
5070
  }
5067
5071
  } finally {
5068
- compactingLoader.stop();
5072
+ (compactingLoader as PencilLoader).stop();
5069
5073
  this.statusContainer.clear();
5070
5074
  this.defaultEditor.onEscape = originalOnEscape;
5071
5075
  }
@@ -5075,7 +5079,7 @@ export class InteractiveMode {
5075
5079
 
5076
5080
  stop(): void {
5077
5081
  if (this.loadingAnimation) {
5078
- this.loadingAnimation.stop();
5082
+ (this.loadingAnimation as PencilLoader).stop();
5079
5083
  this.loadingAnimation = undefined;
5080
5084
  }
5081
5085
  this.clearExtensionTerminalInputListeners();
@@ -5089,4 +5093,56 @@ export class InteractiveMode {
5089
5093
  this.isInitialized = false;
5090
5094
  }
5091
5095
  }
5096
+
5097
+ private handleSoulCommand(): void {
5098
+ const soulManager = (this.session as any)._soulManager;
5099
+ if (!soulManager) {
5100
+ this.chatContainer.addChild(new Spacer(1));
5101
+ this.chatContainer.addChild(
5102
+ new Text(theme.fg("warning", "โš ๏ธ Soul ๆœชๅฏ็”จ"), 1, 0),
5103
+ );
5104
+ this.chatContainer.addChild(
5105
+ new Text(
5106
+ theme.fg(
5107
+ "dim",
5108
+ "Soul (AI ๆ€งๆ ผ็ณป็ปŸ) ๆœชๅฏ็”จใ€‚่ฏท็กฎไฟไฝฟ็”จ NanoPencil 1.3.0 ๆˆ–ๆ›ด้ซ˜็‰ˆๆœฌใ€‚",
5109
+ ),
5110
+ 1,
5111
+ 0,
5112
+ ),
5113
+ );
5114
+ this.ui.requestRender();
5115
+ return;
5116
+ }
5117
+
5118
+ const { formatSoulStats } = require("./components/soul-stats.js");
5119
+ const stats = formatSoulStats(soulManager, { compact: false });
5120
+
5121
+ this.chatContainer.addChild(new Spacer(1));
5122
+ this.chatContainer.addChild(new Text(stats, 1, 0));
5123
+ this.ui.requestRender();
5124
+ }
5125
+
5126
+ private handleMemoryCommand(): void {
5127
+ const lines: string[] = [];
5128
+ lines.push(theme.fg("accent", "๐Ÿ“š Project Memory - NanoMem"));
5129
+ lines.push("");
5130
+ lines.push(theme.fg("dim", "ๅญ˜ๅ‚จไฝ็ฝฎ: ~/.nanopencil/agent/memory/"));
5131
+ lines.push(theme.fg("dim", " - knowledge.json (้กน็›ฎ็Ÿฅ่ฏ†)"));
5132
+ lines.push(theme.fg("dim", " - lessons.json (็ป้ชŒๆ•™่ฎญ)"));
5133
+ lines.push(theme.fg("dim", " - preferences.json (็”จๆˆทๅๅฅฝ)"));
5134
+ lines.push(theme.fg("dim", " - patterns.json (่กŒไธบๆจกๅผ)"));
5135
+ lines.push(theme.fg("dim", " - facets.json (ๆจกๅผ/ๅ›ฐๅขƒ)"));
5136
+ lines.push("");
5137
+ lines.push(
5138
+ theme.fg("dim", "๐Ÿ’ก ๆ็คบ: NanoMem ่‡ชๅŠจไปŽๅฏน่ฏไธญๆๅ–ๅ’Œ่ฎฐๅฟ†้กน็›ฎ็Ÿฅ่ฏ†"),
5139
+ );
5140
+ lines.push(theme.fg("dim", " - ่ฎฐไฝ API ็ซฏ็‚นใ€้…็ฝฎ้€‰้กน"));
5141
+ lines.push(theme.fg("dim", " - ๅญฆไน ้”™่ฏฏๆจกๅผๅ’Œ่งฃๅ†ณๆ–นๆกˆ"));
5142
+ lines.push(theme.fg("dim", " - ่ฏ†ๅˆซ็”จๆˆทๅๅฅฝๅ’Œ็ผ–็ ้ฃŽๆ ผ"));
5143
+
5144
+ this.chatContainer.addChild(new Spacer(1));
5145
+ this.chatContainer.addChild(new Text(lines.join("\n"), 1, 0));
5146
+ this.ui.requestRender();
5147
+ }
5092
5148
  }
@@ -1,81 +1,81 @@
1
- {
2
- "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
- "name": "warm",
4
- "vars": {
5
- "warmBrown": "#c4a574",
6
- "warmLight": "#d4a574",
7
- "warmBorder": "#a08060",
8
- "blue": "#5f87ff",
9
- "green": "#b5bd68",
10
- "red": "#cc6666",
11
- "yellow": "#d4a84b",
12
- "gray": "#808080",
13
- "dimGray": "#666666",
14
- "darkGray": "#505050",
15
- "accent": "warmBrown",
16
- "selectedBg": "#3d362a",
17
- "userMsgBg": "#2e2a24",
18
- "toolPendingBg": "#2a2820",
19
- "toolSuccessBg": "#2a3024",
20
- "toolErrorBg": "#3c2828",
21
- "customMsgBg": "#2d2838"
22
- },
23
- "colors": {
24
- "accent": "accent",
25
- "border": "warmBorder",
26
- "borderAccent": "warmLight",
27
- "borderMuted": "darkGray",
28
- "success": "green",
29
- "error": "red",
30
- "warning": "yellow",
31
- "muted": "gray",
32
- "dim": "dimGray",
33
- "text": "",
34
- "thinkingText": "gray",
35
- "selectedBg": "selectedBg",
36
- "userMessageBg": "userMsgBg",
37
- "userMessageText": "",
38
- "customMessageBg": "customMsgBg",
39
- "customMessageText": "",
40
- "customMessageLabel": "#b8956b",
41
- "toolPendingBg": "toolPendingBg",
42
- "toolSuccessBg": "toolSuccessBg",
43
- "toolErrorBg": "toolErrorBg",
44
- "toolTitle": "",
45
- "toolOutput": "gray",
46
- "mdHeading": "warmLight",
47
- "mdLink": "#81a2be",
48
- "mdLinkUrl": "dimGray",
49
- "mdCode": "accent",
50
- "mdCodeBlock": "green",
51
- "mdCodeBlockBorder": "gray",
52
- "mdQuote": "gray",
53
- "mdQuoteBorder": "gray",
54
- "mdHr": "gray",
55
- "mdListBullet": "accent",
56
- "toolDiffAdded": "green",
57
- "toolDiffRemoved": "red",
58
- "toolDiffContext": "gray",
59
- "syntaxComment": "#6A9955",
60
- "syntaxKeyword": "#569CD6",
61
- "syntaxFunction": "#DCDCAA",
62
- "syntaxVariable": "#9CDCFE",
63
- "syntaxString": "#CE9178",
64
- "syntaxNumber": "#B5CEA8",
65
- "syntaxType": "#4EC9B0",
66
- "syntaxOperator": "#D4D4D4",
67
- "syntaxPunctuation": "#D4D4D4",
68
- "thinkingOff": "darkGray",
69
- "thinkingMinimal": "#6e6e6e",
70
- "thinkingLow": "#5f87af",
71
- "thinkingMedium": "warmBrown",
72
- "thinkingHigh": "#b294bb",
73
- "thinkingXhigh": "#d183e8",
74
- "bashMode": "green"
75
- },
76
- "export": {
77
- "pageBg": "#1a1814",
78
- "cardBg": "#242018",
79
- "infoBg": "#3c3728"
80
- }
81
- }
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "warm",
4
+ "vars": {
5
+ "warmBrown": "#c4a574",
6
+ "warmLight": "#d4a574",
7
+ "warmBorder": "#a08060",
8
+ "blue": "#5f87ff",
9
+ "green": "#b5bd68",
10
+ "red": "#cc6666",
11
+ "yellow": "#d4a84b",
12
+ "gray": "#808080",
13
+ "dimGray": "#666666",
14
+ "darkGray": "#505050",
15
+ "accent": "warmBrown",
16
+ "selectedBg": "#3d362a",
17
+ "userMsgBg": "#2e2a24",
18
+ "toolPendingBg": "#2a2820",
19
+ "toolSuccessBg": "#2a3024",
20
+ "toolErrorBg": "#3c2828",
21
+ "customMsgBg": "#2d2838"
22
+ },
23
+ "colors": {
24
+ "accent": "accent",
25
+ "border": "warmBorder",
26
+ "borderAccent": "warmLight",
27
+ "borderMuted": "darkGray",
28
+ "success": "green",
29
+ "error": "red",
30
+ "warning": "yellow",
31
+ "muted": "gray",
32
+ "dim": "dimGray",
33
+ "text": "",
34
+ "thinkingText": "gray",
35
+ "selectedBg": "selectedBg",
36
+ "userMessageBg": "userMsgBg",
37
+ "userMessageText": "warmLight",
38
+ "customMessageBg": "customMsgBg",
39
+ "customMessageText": "",
40
+ "customMessageLabel": "#b8956b",
41
+ "toolPendingBg": "toolPendingBg",
42
+ "toolSuccessBg": "toolSuccessBg",
43
+ "toolErrorBg": "toolErrorBg",
44
+ "toolTitle": "",
45
+ "toolOutput": "gray",
46
+ "mdHeading": "warmLight",
47
+ "mdLink": "#81a2be",
48
+ "mdLinkUrl": "dimGray",
49
+ "mdCode": "accent",
50
+ "mdCodeBlock": "green",
51
+ "mdCodeBlockBorder": "gray",
52
+ "mdQuote": "gray",
53
+ "mdQuoteBorder": "gray",
54
+ "mdHr": "gray",
55
+ "mdListBullet": "accent",
56
+ "toolDiffAdded": "green",
57
+ "toolDiffRemoved": "red",
58
+ "toolDiffContext": "gray",
59
+ "syntaxComment": "#6A9955",
60
+ "syntaxKeyword": "#569CD6",
61
+ "syntaxFunction": "#DCDCAA",
62
+ "syntaxVariable": "#9CDCFE",
63
+ "syntaxString": "#CE9178",
64
+ "syntaxNumber": "#B5CEA8",
65
+ "syntaxType": "#4EC9B0",
66
+ "syntaxOperator": "#D4D4D4",
67
+ "syntaxPunctuation": "#D4D4D4",
68
+ "thinkingOff": "darkGray",
69
+ "thinkingMinimal": "#6e6e6e",
70
+ "thinkingLow": "#5f87af",
71
+ "thinkingMedium": "warmBrown",
72
+ "thinkingHigh": "#b294bb",
73
+ "thinkingXhigh": "#d183e8",
74
+ "bashMode": "green"
75
+ },
76
+ "export": {
77
+ "pageBg": "#1a1814",
78
+ "cardBg": "#242018",
79
+ "infoBg": "#3c3728"
80
+ }
81
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pencil-agent/nano-pencil",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "CLI writing agent with read, bash, edit, write tools and session management. Based on pi; supports DashScope Coding Plan. Soul enabled by default for AI personality evolution.",
5
5
  "type": "module",
6
6
  "bin": {