pelulu-cli 1.0.0 → 1.0.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 (49) hide show
  1. package/DailyNotesApp/app/build.gradle.kts +57 -0
  2. package/DailyNotesApp/app/src/main/java/com/dailynotes/app/MainActivity.kt +48 -0
  3. package/DailyNotesApp/app/src/main/java/com/dailynotes/app/Note.kt +13 -0
  4. package/DailyNotesApp/app/src/main/java/com/dailynotes/app/NoteDatabase.kt +9 -0
  5. package/DailyNotesApp/build.gradle.kts +5 -0
  6. package/DailyNotesApp/settings.gradle.kts +17 -0
  7. package/README.md +69 -82
  8. package/config.example.json +2 -1
  9. package/package.json +6 -3
  10. package/preview.jpg +0 -0
  11. package/src/core/auto-format.js +61 -12
  12. package/src/core/config.js +1 -1
  13. package/src/core/confirm.js +2 -2
  14. package/src/core/diff.js +4 -4
  15. package/src/core/doctor.js +2 -2
  16. package/src/core/error-handler.js +1 -1
  17. package/src/core/file-tracker.js +4 -4
  18. package/src/core/formatter.js +19 -19
  19. package/src/core/logger.js +19 -3
  20. package/src/core/spinner.js +2 -2
  21. package/src/core/stats.js +2 -2
  22. package/src/core/thinking.js +8 -8
  23. package/src/core/tool-help.js +2 -2
  24. package/src/core/tool-registry.js +6 -10
  25. package/src/core/update-checker.js +125 -0
  26. package/src/core/wizard.js +5 -5
  27. package/src/core/workspace.js +4 -4
  28. package/src/index.js +82 -37
  29. package/src/mcp/activation.js +3 -3
  30. package/src/mcp/message-sender.js +1 -1
  31. package/src/mcp/mqtt-client.js +4 -4
  32. package/src/repl-commands.js +2 -1
  33. package/src/repl.js +26 -6
  34. package/src/tools/config.js +1 -1
  35. package/src/tools/diff.js +6 -0
  36. package/src/tools/file.js +3 -3
  37. package/src/tools/git.js +1 -1
  38. package/src/tools/network.js +1 -1
  39. package/src/tools/project.js +2 -2
  40. package/src/tools/search.js +3 -3
  41. package/src/tools/shell.js +2 -2
  42. package/src/tools/template.js +1 -1
  43. package/src/tools/watch.js +1 -1
  44. package/src/tui/completable-input.js +127 -0
  45. package/src/tui/ink-app.js +324 -0
  46. package/src/tui/ink-components.js +157 -0
  47. package/src/tui/ink-entry.js +86 -0
  48. package/src/tui/renderer.js +137 -19
  49. package/src/tui/status-bar.js +7 -7
@@ -0,0 +1,57 @@
1
+ plugins {
2
+ id("com.android.application")
3
+ id("org.jetbrains.kotlin.android")
4
+ }
5
+
6
+ android {
7
+ namespace = "com.dailynotes.app"
8
+ compileSdk = 34
9
+
10
+ defaultConfig {
11
+ applicationId = "com.dailynotes.app"
12
+ minSdk = 24
13
+ targetSdk = 34
14
+ versionCode = 1
15
+ versionName = "1.0"
16
+
17
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18
+ }
19
+
20
+ buildTypes {
21
+ release {
22
+ isMinifyEnabled = false
23
+ proguardFiles(
24
+ getDefaultProguardFile("proguard-android-optimize.txt"),
25
+ "proguard-rules.pro"
26
+ )
27
+ }
28
+ }
29
+ compileOptions {
30
+ sourceCompatibility = JavaVersion.VERSION_1_8
31
+ targetCompatibility = JavaVersion.VERSION_1_8
32
+ }
33
+ kotlinOptions {
34
+ jvmTarget = "1.8"
35
+ }
36
+ }
37
+
38
+ dependencies {
39
+ implementation("androidx.core:core-ktx:1.12.0")
40
+ implementation("androidx.appcompat:appcompat:1.6.1")
41
+ implementation("com.google.android.material:material:1.11.0")
42
+ implementation("androidx.constraintlayout:constraintlayout:2.1.4")
43
+
44
+ // Room Database
45
+ val roomVersion = "2.6.1"
46
+ implementation("androidx.room:room-runtime:$roomVersion")
47
+ implementation("androidx.room:room-ktx:$roomVersion")
48
+ ksp("androidx.room:room-compiler:$roomVersion")
49
+
50
+ // Lifecycle
51
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
52
+ implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
53
+
54
+ testImplementation("junit:junit:4.13.2")
55
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
56
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
57
+ }
@@ -0,0 +1,48 @@
1
+ package com.dailynotes.app
2
+
3
+ import android.os.Bundle
4
+ import androidx.appcompat.app.AppCompatActivity
5
+ import androidx.lifecycle.ViewModelProvider
6
+ import androidx.recyclerview.widget.LinearLayoutManager
7
+ import androidx.recyclerview.widget.RecyclerView
8
+ import com.google.android.material.floatingactionbutton.FloatingActionButton
9
+
10
+ class MainActivity : AppCompatActivity() {
11
+
12
+ private lateinit var viewModel: NoteViewModel
13
+ private lateinit var adapter: NoteAdapter
14
+ private lateinit var recyclerView: RecyclerView
15
+
16
+ override fun onCreate(savedInstanceState: Bundle?) {
17
+ super.onCreate(savedInstanceState)
18
+ setContentView(R.layout.activity_main)
19
+
20
+ viewModel = ViewModelProvider(this)[NoteViewModel::class.java]
21
+
22
+ recyclerView = findViewById(R.id.recyclerView)
23
+ adapter = NoteAdapter { note ->
24
+ // Handle note click - could open edit dialog
25
+ }
26
+
27
+ recyclerView.adapter = adapter
28
+ recyclerView.layoutManager = LinearLayoutManager(this)
29
+
30
+ val fabAdd: FloatingActionButton = findViewById(R.id.fabAdd)
31
+ fabAdd.setOnClickListener {
32
+ // Open add note dialog
33
+ showAddNoteDialog()
34
+ }
35
+
36
+ viewModel.allNotes.observe(this) { notes ->
37
+ adapter.submitList(notes)
38
+ }
39
+ }
40
+
41
+ private fun showAddNoteDialog() {
42
+ val dialog = AddNoteDialog { title, content ->
43
+ val note = Note(title = title, content = content)
44
+ viewModel.insert(note)
45
+ }
46
+ dialog.show(supportFragmentManager, "AddNoteDialog")
47
+ }
48
+ }
@@ -0,0 +1,13 @@
1
+ package com.dailynotes.app
2
+
3
+ import androidx.room.Entity
4
+ import androidx.room.PrimaryKey
5
+
6
+ @Entity(tableName = "notes")
7
+ data class Note(
8
+ @PrimaryKey(autoGenerate = true)
9
+ val id: Int = 0,
10
+ val title: String,
11
+ val content: String,
12
+ val createdAt: Long = System.currentTimeMillis()
13
+ )
@@ -0,0 +1,9 @@
1
+ package com.dailynotes.app
2
+
3
+ import androidx.room.Database
4
+ import androidx.room.RoomDatabase
5
+
6
+ @Database(entities = [Note::class], version = 1)
7
+ abstract class NoteDatabase : RoomDatabase() {
8
+ abstract fun noteDao(): NoteDao
9
+ }
@@ -0,0 +1,5 @@
1
+ // Top-level build file where you can add configuration options common to all sub-projects/modules.
2
+ plugins {
3
+ id("com.android.application") version "8.2.0" apply false
4
+ id("org.jetbrains.kotlin.android") version "1.9.20" apply false
5
+ }
@@ -0,0 +1,17 @@
1
+ pluginManagement {
2
+ repositories {
3
+ google()
4
+ mavenCentral()
5
+ gradlePluginPortal()
6
+ }
7
+ }
8
+ dependencyResolutionManagement {
9
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10
+ repositories {
11
+ google()
12
+ mavenCentral()
13
+ }
14
+ }
15
+
16
+ rootProject.name = "DailyNotesApp"
17
+ include(":app")
package/README.md CHANGED
@@ -1,15 +1,19 @@
1
- # Pelulu CLI 🐾
1
+ # Pelulu CLI
2
2
 
3
- > A CLI coding agent powered by [XiaoZhi](https://xiaozhi.me) — yes, the tiny Chinese AI model that lives in ESP32 chips. We gave it 15 tools and a terminal. What could go wrong?
3
+ > A CLI coding agent powered by [XiaoZhi](https://xiaozhi.me) — the tiny Chinese AI model that lives in ESP32 chips. We gave it 15 tools and a terminal. What could go wrong?
4
+
5
+ ![Pelulu CLI Preview](preview.jpg)
4
6
 
5
7
  ```
6
- ╭────────────────────────────────────────────────╮
7
- │ 🐾 Pelulu CLI │
8
- ├────────────────────────────────────────────────┤
9
- │ 📁 my-project │
10
- 🔧 15 tools · 67 actions · 15 MCP slots used │
11
- │ 🟢 Connected to XiaoZhi │
12
- ╰────────────────────────────────────────────────╯
8
+ /\_/\
9
+ ( o.o ) P E L U L U - C L I
10
+ > ^ < v.1.0.1 | powered by XiaoZhi
11
+ /| |\
12
+ (_| |_) the tiny coding companion
13
+
14
+ ╭────────────────────────────────────────────────────────────╮
15
+ │ pelulu-cli v.1.0.1 | ONLINE | 594c02b2 | Provider: Xiaozhi.me │
16
+ ╰────────────────────────────────────────────────────────────╯
13
17
  ```
14
18
 
15
19
  ## What is this?
@@ -21,7 +25,8 @@ Pelulu CLI is a terminal-based AI coding agent that connects to XiaoZhi (a small
21
25
  ## Features
22
26
 
23
27
  - **15 Consolidated Tools** — 67 actions packed into 15 MCP slots (because XiaoZhi has a 32-tool limit and we're not wasteful)
24
- - **Rich TUI** — Box drawing, colors, emoji. Your terminal will look like a hacker movie from 2003
28
+ - **Rich TUI** — Box drawing, colors, scrollable chat. Your terminal will look like a hacker movie from 2003
29
+ - **Scrollable Chat** — Shift+Up/Down or PageUp/PageDown to scroll through message history
25
30
  - **Auto-detect Projects** — `cd my-project && pelulu` and it figures out the rest
26
31
  - **Safety Sandbox** — Won't let the AI `rm -rf /` (we tested... extensively)
27
32
  - **Destructive Op Confirmation** — Asks before deleting your life's work
@@ -29,7 +34,8 @@ Pelulu CLI is a terminal-based AI coding agent that connects to XiaoZhi (a small
29
34
  - **Session Persistence** — Remembers your device ID so you don't have to re-activate every time
30
35
  - **Plugin System** — Add your own tools (if 67 actions aren't enough for you)
31
36
  - **Auto-reconnect** — MQTT disconnects happen. We handle them. Mostly.
32
-
37
+ - **Auto-format** — Formats code after edits (prettier/black if available, basic cleanup otherwise)
38
+ - **Mandatory Updates** — Blocks CLI when a new version is available (keeps everyone on the latest)
33
39
 
34
40
  ## Quick Start
35
41
 
@@ -58,7 +64,7 @@ First run: you'll get an activation code. Go to [xiaozhi.me](https://xiaozhi.me)
58
64
 
59
65
  | Tool | Actions | What it does |
60
66
  |------|---------|-------------|
61
- | `file` | read, write, edit, list, delete, mkdir, copy, move, exists | File operations (9 actions, 1 MCP slot — efficiency™) |
67
+ | `file` | read, write, edit, list, delete, mkdir, copy, move, exists | File operations (9 actions, 1 MCP slot) |
62
68
  | `shell` | exec, bg, ps, kill | Run commands (with safety rails) |
63
69
  | `git` | init, clone, status, diff, log, add, commit, push, pull, branch | Full git workflow |
64
70
  | `search` | grep, find, web | Search files and the internet |
@@ -102,86 +108,72 @@ pelulu --wizard # Re-run setup wizard
102
108
  ### Shortcuts (Natural Language)
103
109
 
104
110
  ```
105
- read index.js file read
106
- run npm test shell exec
107
- git status git status
108
- build project build
109
- search TODO search grep
111
+ read index.js -> file read
112
+ run npm test -> shell exec
113
+ git status -> git status
114
+ build -> project build
115
+ search TODO -> search grep
116
+ ```
117
+
118
+ ### Scrolling
119
+
120
+ ```
121
+ Shift+Up / Shift+Down # Scroll by 5 messages
122
+ PageUp / PageDown # Scroll by 20 messages
110
123
  ```
111
124
 
112
- ## Fun Facts 🎉
125
+ ## Fun Facts
113
126
 
114
127
  - **XiaoZhi's main job** is being a voice assistant for ESP32 microcontrollers. We basically gave a thermostat the ability to `git push --force`
115
128
  - **The 32-tool limit** exists because XiaoZhi was designed to control smart home devices, not write your React app. We consolidated 67 actions into 15 tools just to fit
116
- - **It responds in Mandarin** sometimes. We consider this a feature, not a bug. 非常好!
129
+ - **It responds in Mandarin** sometimes. We consider this a feature, not a bug.
117
130
  - **The MQTT protocol** was chosen because that's what IoT devices use. Your coding agent runs on the same protocol as your smart fridge
118
131
  - **XiaoZhi costs approximately $0** to run. Your coding agent is cheaper than a cup of coffee. The quality is... also about that
119
132
  - **We tested `rm -rf /`** protection extensively. The AI suggested it exactly once. It was blocked. The AI seemed disappointed
120
- - **The model runs on a Tenclass server** somewhere in China. Your code is being reviewed by an AI that was trained to turn on lights and play music
121
- - **15 MCP slots used** out of 32. That's 17 slots free for when someone inevitably asks us to add a "make coffee" tool
122
133
 
123
134
  ## Architecture
124
135
 
125
136
  ```
126
- ┌──────────────┐ MQTT ┌──────────────┐ MCP ┌──────────────┐
127
- XiaoZhi AI ←──────────→ Pelulu CLI ←──────────→ 15 Tools
128
- (tiny LLM) (router) (67 actions)
129
- └──────────────┘ └──────────────┘ └──────────────┘
130
-
131
- ┌──────────────┐
132
- Rich TUI
133
- └──────────────┘
137
+ +--------------+ MQTT +--------------+ MCP +--------------+
138
+ | XiaoZhi AI | <----------> | Pelulu CLI | <----------> | 15 Tools |
139
+ | (tiny LLM) | | (router) | | (67 actions) |
140
+ +--------------+ +--------------+ +--------------+
141
+ |
142
+ +--------------+
143
+ | Ink TUI |
144
+ +--------------+
134
145
  ```
135
146
 
136
147
  ```
137
148
  src/
138
- ├── index.js Entry point
139
- ├── repl.js ← CLI interface
140
- ├── repl-commands.js Command handlers
141
- ├── core/
142
- ├── config.js Config loader
143
- ├── event-bus.js Pub/sub
144
- ├── logger.js Logging
145
- ├── tool-registry.js Tool registry
146
- ├── sandbox.js Safety layer
147
- ├── session.js Session state
148
- ├── system-prompt.js Prompt builder
149
- ├── context.js Context injection
150
- ├── confirm.js Confirmation system
151
- ├── diff.js Diff display
152
- │ ├── spinner.js ← Progress indicators
153
- ├── workspace.js Workspace detection
154
- ├── stats.js Usage analytics
155
- ├── conversation.js Conv export/import
156
- │ ├── wizard.js ← Config wizard
157
- ├── formatter.js Rich output formatting
158
- │ ├── tool-help.js ← Tool examples
159
- ├── intent.js Natural language parser
160
- │ ├── file-tracker.js ← File change tracking
161
- │ ├── auto-format.js Code auto-format
162
- │ ├── error-handler.js Smart errors
163
- │ ├── thinking.js AI state indicator
164
- │ ├── model-info.js Model info
165
- │ ├── keybindings.js Keyboard shortcuts
166
- │ ├── completion.js ← Command completion
167
- │ └── retry.js ← Retry logic
168
- ├── mcp/
169
- │ ├── mqtt-client.js ← MQTT connection
170
- │ ├── mcp-handler.js ← MCP protocol
171
- │ ├── activation.js ← Device activation
172
- │ ├── message-sender.js ← Message queue
173
- │ └── wss-endpoint.js ← WSS endpoint
174
- ├── tools/
175
- │ ├── file.js, shell.js, git.js, search.js
176
- │ ├── project.js, process.js, network.js
177
- │ ├── env.js, ai.js, snippet.js
178
- │ ├── template.js, history.js, config.js
179
- │ ├── diff.js, watch.js
180
- ├── plugins/
181
- │ └── manager.js ← Plugin loader
182
- └── tui/
183
- ├── renderer.js ← Rich TUI (chalk)
184
- └── status-bar.js ← Status bar
149
+ +-- index.js <- Entry point
150
+ +-- core/
151
+ | +-- config.js <- Config loader
152
+ | +-- event-bus.js <- Pub/sub
153
+ | +-- logger.js <- Logging (Ink-aware)
154
+ | +-- tool-registry.js <- Tool registry
155
+ | +-- sandbox.js <- Safety layer
156
+ | +-- session.js <- Session state
157
+ | +-- system-prompt.js <- Prompt builder
158
+ | +-- auto-format.js <- Code auto-format
159
+ | +-- intent.js <- Natural language parser
160
+ | +-- formatter.js <- Rich output formatting
161
+ | +-- thinking.js <- AI state indicator
162
+ | +-- completion.js <- Command completion
163
+ +-- mcp/
164
+ | +-- mqtt-client.js <- MQTT connection
165
+ | +-- mcp-handler.js <- MCP protocol
166
+ | +-- activation.js <- Device activation
167
+ +-- tools/
168
+ | +-- file.js, shell.js, git.js, search.js, ...
169
+ +-- plugins/
170
+ | +-- manager.js <- Plugin loader
171
+ +-- tui/
172
+ +-- ink-app.js <- Main Ink React app
173
+ +-- ink-components.js <- UI components
174
+ +-- ink-entry.js <- Ink entry point
175
+ +-- renderer.js <- Banner & helpers
176
+ +-- completable-input.js <- Tab completion input
185
177
  ```
186
178
 
187
179
  ## Extending
@@ -246,11 +238,6 @@ export default {
246
238
  - Complex requests (8+ files) may timeout. The model needs to think. It's doing its best.
247
239
  - The AI occasionally suggests `rm -rf /` as a solution. We block it. The AI learns nothing.
248
240
  - MQTT connections can be unstable. We reconnect. It's fine. Everything is fine.
249
- - The model is small. Like, really small. It runs on chips that cost less than a candy bar.
250
-
251
- ## Why "Nexa"?
252
-
253
- Because it sounds cool and vaguely futuristic. Also "xiaozhi-cli" was already taken (by us, in a previous life). We wanted something that says "this is a serious developer tool" while secretly knowing it's powered by an AI that was trained to control smart light bulbs.
254
241
 
255
242
  ## Contributing
256
243
 
@@ -267,7 +254,7 @@ MIT — do whatever you want with it. If you make money using an AI that was des
267
254
  ---
268
255
 
269
256
  <p align="center">
270
- Built with ❤️ and questionable decisions<br>
257
+ Built with questionable decisions<br>
271
258
  Powered by <a href="https://xiaozhi.me">XiaoZhi</a> — the little AI that could<br>
272
259
  <sub>It's not stupid, it's compact</sub>
273
260
  </p>
@@ -15,7 +15,8 @@
15
15
  },
16
16
  "tools": {
17
17
  "shell_timeout": 30000,
18
- "max_output": 10000
18
+ "max_output": 10000,
19
+ "auto_format": true
19
20
  },
20
21
  "plugins": {
21
22
  "enabled": [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pelulu-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "CLI coding agent powered by XiaoZhi AI — a tiny Chinese LLM that lives in ESP32 chips",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -9,8 +9,8 @@
9
9
  },
10
10
  "scripts": {
11
11
  "start": "node src/index.js",
12
- "start:text": "node src/index.js --text",
13
12
  "start:debug": "node src/index.js --debug",
13
+ "start:fallback": "node src/index.js --no-ink",
14
14
  "tools": "node src/index.js --list-tools",
15
15
  "test": "node src/index.js --test"
16
16
  },
@@ -35,8 +35,11 @@
35
35
  "author": "chaerulchas",
36
36
  "license": "MIT",
37
37
  "dependencies": {
38
+ "chalk": "^5.3.0",
39
+ "ink": "^5.2.1",
40
+ "ink-text-input": "^6.0.0",
38
41
  "mqtt": "^5.3.0",
39
- "chalk": "^5.3.0"
42
+ "react": "^18.3.1"
40
43
  },
41
44
  "engines": {
42
45
  "node": ">=18"
package/preview.jpg ADDED
Binary file
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * AutoFormat — format code after edits
3
3
  * Detects project type and runs appropriate formatter
4
+ * Falls back to basic cleanup if no formatter is available
4
5
  */
5
6
  import { exec } from 'child_process';
7
+ import { readFile, writeFile } from 'fs/promises';
6
8
  import { existsSync } from 'fs';
7
9
  import { join } from 'path';
8
10
  import { getConfig } from './config.js';
@@ -23,25 +25,72 @@ async function detectType(cwd) {
23
25
  return null;
24
26
  }
25
27
 
28
+ /**
29
+ * Basic formatter — no external deps required
30
+ * Trims trailing whitespace, ensures final newline, normalizes blank lines
31
+ */
32
+ async function basicFormat(filePath) {
33
+ try {
34
+ const content = await readFile(filePath, 'utf-8');
35
+ const formatted = content
36
+ .replace(/[ \t]+$/gm, '') // trailing whitespace
37
+ .replace(/\n{3,}/g, '\n\n') // max 2 consecutive blank lines
38
+ .replace(/\r\n/g, '\n') // normalize line endings
39
+ .replace(/\r/g, '\n');
40
+
41
+ // Ensure final newline
42
+ const final = formatted.endsWith('\n') ? formatted : formatted + '\n';
43
+
44
+ if (final !== content) {
45
+ await writeFile(filePath, final, 'utf-8');
46
+ log('file', `formatted: ${filePath}`);
47
+ return true;
48
+ }
49
+ return false;
50
+ } catch (e) {
51
+ debug(`Basic format failed: ${e.message}`);
52
+ return false;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Try external formatter (prettier, black, etc.)
58
+ * Returns true if successful, false if not available or failed
59
+ */
60
+ async function externalFormat(formatter, filePath, cwd) {
61
+ return new Promise((resolve) => {
62
+ exec(`${formatter.cmd} "${filePath}"`, { cwd, timeout: 10000 }, (err, stdout, stderr) => {
63
+ if (err) {
64
+ debug(`External formatter failed: ${err.message}`);
65
+ resolve(false);
66
+ } else {
67
+ log('file', `formatted: ${filePath}`);
68
+ resolve(true);
69
+ }
70
+ });
71
+ });
72
+ }
73
+
26
74
  export async function autoFormat(filePath) {
27
75
  const cfg = getConfig();
28
76
  if (!cfg.tools?.auto_format) return; // disabled by default
29
77
 
30
78
  const cwd = cfg.agent?.workspace || process.cwd();
31
79
  const type = await detectType(cwd);
32
- if (!type) return;
33
-
34
- const formatter = FORMATTERS[type];
35
- if (!formatter) return;
36
80
 
37
81
  const ext = filePath.split('.').pop()?.toLowerCase();
38
- if (!formatter.ext.includes(ext)) return;
82
+ const codeExts = ['js', 'jsx', 'ts', 'tsx', 'json', 'md', 'py', 'rs', 'go', 'css', 'html', 'yaml', 'yml', 'toml'];
83
+ if (!codeExts.includes(ext)) return;
39
84
 
40
- return new Promise((resolve) => {
41
- exec(`${formatter.cmd} "${filePath}"`, { cwd, timeout: 10000 }, (err, stdout, stderr) => {
42
- if (err) debug(`Auto-format failed: ${err.message}`);
43
- else log('file', `🎨 Formatted: ${filePath}`);
44
- resolve(!err);
45
- });
46
- });
85
+ // Try external formatter first
86
+ if (type && FORMATTERS[type]) {
87
+ const formatter = FORMATTERS[type];
88
+ if (formatter.ext.includes(ext)) {
89
+ const ok = await externalFormat(formatter, filePath, cwd);
90
+ if (ok) return;
91
+ }
92
+ }
93
+
94
+ // Fallback to basic format
95
+ await basicFormat(filePath);
47
96
  }
@@ -33,7 +33,7 @@ export async function loadConfig(root) {
33
33
  agent: { name: 'Pelulu CLI', version: '1.0.0', workspace: '~/Pelulu-CLI' },
34
34
  mqtt: { ota_url: 'https://api.tenclass.net/xiaozhi/ota/', keepalive: 240 },
35
35
  mcp: { endpoint_url: '' },
36
- tools: { shell_timeout: 30000, max_output: 10000 },
36
+ tools: { shell_timeout: 30000, max_output: 10000, auto_format: true },
37
37
  plugins: { enabled: [], disabled: [] },
38
38
  };
39
39
 
@@ -28,7 +28,7 @@ export async function askConfirmation(toolName, args, check) {
28
28
  if (!check.destructive) return true;
29
29
 
30
30
  const color = check.level === 'danger' ? COLORS.red : COLORS.yellow;
31
- const icon = check.level === 'danger' ? '🚨' : '⚠️';
31
+ const icon = check.level === 'danger' ? '[!]' : '[WARN]';
32
32
 
33
33
  console.log(`\n${color}${icon} ${check.msg}${COLORS.reset}`);
34
34
  console.log(`${COLORS.dim} Tool: ${toolName}${COLORS.reset}`);
@@ -44,7 +44,7 @@ export async function askConfirmation(toolName, args, check) {
44
44
  rl.question(`${color} Continue? (y/N): ${COLORS.reset}`, (answer) => {
45
45
  rl.close();
46
46
  const ok = answer.trim().toLowerCase() === 'y';
47
- if (!ok) console.log(`${COLORS.red} Cancelled${COLORS.reset}\n`);
47
+ if (!ok) console.log(`${COLORS.red} [ERR] Cancelled${COLORS.reset}\n`);
48
48
  resolve(ok);
49
49
  });
50
50
  });
package/src/core/diff.js CHANGED
@@ -9,7 +9,7 @@ export function showDiff(oldText, newText, filePath) {
9
9
  const newLines = newText.split('\n');
10
10
  const maxLen = Math.max(oldLines.length, newLines.length);
11
11
 
12
- console.log(`\n${COLORS.bold}📝 Changes: ${filePath || ''}${COLORS.reset}\n`);
12
+ console.log(`\n${COLORS.bold}[EDIT] Changes: ${filePath || ''}${COLORS.reset}\n`);
13
13
 
14
14
  let changes = 0;
15
15
  for (let i = 0; i < maxLen; i++) {
@@ -30,7 +30,7 @@ export function showDiff(oldText, newText, filePath) {
30
30
  }
31
31
 
32
32
  export function showPatch(filePath, hunks) {
33
- console.log(`\n${COLORS.bold}📝 Patch: ${filePath}${COLORS.reset}\n`);
33
+ console.log(`\n${COLORS.bold}[EDIT] Patch: ${filePath}${COLORS.reset}\n`);
34
34
  for (const hunk of hunks) {
35
35
  console.log(`${COLORS.cyan}@@ ${hunk.header || ''} @@${COLORS.reset}`);
36
36
  for (const line of hunk.lines) {
@@ -43,8 +43,8 @@ export function showPatch(filePath, hunks) {
43
43
  }
44
44
 
45
45
  export function formatFileChange(action, path, details) {
46
- const icons = { created: '', modified: '📝', deleted: '🗑️', renamed: '📎' };
47
- const icon = icons[action] || '📄';
46
+ const icons = { created: '[NEW]', modified: '[EDIT]', deleted: '[DEL]', renamed: '[REN]' };
47
+ const icon = icons[action] || '[FILE]';
48
48
  const detailStr = details ? ` ${COLORS.dim}(${details})${COLORS.reset}` : '';
49
49
  return `${icon} ${action}: ${path}${detailStr}`;
50
50
  }
@@ -48,10 +48,10 @@ export async function runDoctor() {
48
48
  checks.push({ name: 'Git repo', ok: hasGit, value: hasGit ? 'yes' : 'no' });
49
49
 
50
50
  // Print report
51
- console.log(`\n${COLORS.bold}🩺 Doctor Report:${COLORS.reset}\n`);
51
+ console.log(`\n${COLORS.bold}[DOC] Doctor Report:${COLORS.reset}\n`);
52
52
  let allOk = true;
53
53
  for (const c of checks) {
54
- const icon = c.ok ? `${COLORS.green}✅${COLORS.reset}` : `${COLORS.red}❌${COLORS.reset}`;
54
+ const icon = c.ok ? `${COLORS.green}[OK]${COLORS.reset}` : `${COLORS.red}[ERR]${COLORS.reset}`;
55
55
  console.log(` ${icon} ${c.name.padEnd(18)} ${c.value}`);
56
56
  if (!c.ok) allOk = false;
57
57
  }
@@ -21,7 +21,7 @@ export function handleError(error) {
21
21
  const code = error.code || error.constructor?.name || 'UNKNOWN';
22
22
  const suggestion = ERROR_SUGGESTIONS[code]?.(error) || null;
23
23
 
24
- const lines = [`${COLORS.red} ${error.message}${COLORS.reset}`];
24
+ const lines = [`${COLORS.red}[ERR] ${error.message}${COLORS.reset}`];
25
25
  if (suggestion) lines.push(`${COLORS.yellow}💡 ${suggestion}${COLORS.reset}`);
26
26
 
27
27
  return lines.join('\n');
@@ -32,10 +32,10 @@ export class FileTracker {
32
32
  const modified = changes.filter(c => c.action === 'modified');
33
33
  const deleted = changes.filter(c => c.action === 'deleted');
34
34
 
35
- const lines = [`${COLORS.bold}📁 File Changes:${COLORS.reset}`];
36
- if (created.length) lines.push(` ${COLORS.green} Created:${COLORS.reset} ${created.map(c => c.path).join(', ')}`);
37
- if (modified.length) lines.push(` ${COLORS.yellow}📝 Modified:${COLORS.reset} ${modified.map(c => `${c.path} (${c.count}x)`).join(', ')}`);
38
- if (deleted.length) lines.push(` ${COLORS.red}🗑️ Deleted:${COLORS.reset} ${deleted.map(c => c.path).join(', ')}`);
35
+ const lines = [`${COLORS.bold}[DIR] File Changes:${COLORS.reset}`];
36
+ if (created.length) lines.push(` ${COLORS.green}[NEW] Created:${COLORS.reset} ${created.map(c => c.path).join(', ')}`);
37
+ if (modified.length) lines.push(` ${COLORS.yellow}[EDIT] Modified:${COLORS.reset} ${modified.map(c => `${c.path} (${c.count}x)`).join(', ')}`);
38
+ if (deleted.length) lines.push(` ${COLORS.red}[DEL] Deleted:${COLORS.reset} ${deleted.map(c => c.path).join(', ')}`);
39
39
 
40
40
  return lines.join('\n');
41
41
  }