@make-u-free/migi 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/migi.js CHANGED
@@ -79,6 +79,29 @@ function sepWithLabel(label) {
79
79
  }
80
80
 
81
81
  // ---- チャット入力(Enter送信 / Shift+Enter改行)----
82
+ // 全角文字(日本語・絵文字など)は端末上で2カラム幅を占める
83
+ // string.length はコードポイント数なので、カーソル位置計算に使うと日本語入力でズレる
84
+ function displayWidth(str) {
85
+ let w = 0
86
+ for (const ch of str) {
87
+ const cp = ch.codePointAt(0)
88
+ const wide =
89
+ (cp >= 0x1100 && cp <= 0x115F) || // Hangul Jamo
90
+ (cp >= 0x2E80 && cp <= 0x303F) || // CJK Radicals
91
+ (cp >= 0x3040 && cp <= 0x33FF) || // Hiragana〜CJK Compat
92
+ (cp >= 0x3400 && cp <= 0x9FFF) || // CJK Unified
93
+ (cp >= 0xAC00 && cp <= 0xD7FF) || // Hangul Syllables
94
+ (cp >= 0xF900 && cp <= 0xFAFF) || // CJK Compat Ideographs
95
+ (cp >= 0xFE10 && cp <= 0xFE1F) || // Vertical Forms
96
+ (cp >= 0xFE30 && cp <= 0xFE6F) || // CJK Compat Forms
97
+ (cp >= 0xFF01 && cp <= 0xFF60) || // Fullwidth ASCII
98
+ (cp >= 0xFFE0 && cp <= 0xFFE6) || // Fullwidth Signs
99
+ (cp >= 0x1F300 && cp <= 0x1FAFF) // Emoji
100
+ w += wide ? 2 : 1
101
+ }
102
+ return w
103
+ }
104
+
82
105
  async function readChatInput() {
83
106
  return new Promise((resolve) => {
84
107
  const PFIRST = ' > '
@@ -132,9 +155,9 @@ async function readChatInput() {
132
155
  if (linesFromBottom > 0) buf += `\x1b[${linesFromBottom}A`
133
156
  buf += '\r'
134
157
 
135
- // ⑤ カーソルを入力内容の末尾へ
158
+ // ⑤ カーソルを入力内容の末尾へ(全角文字は2カラム幅なので displayWidth を使う)
136
159
  const prefix = curLine === 0 ? PFIRST : PCONT
137
- buf += `\x1b[${prefix.length + lines[curLine].length + 1}G`
160
+ buf += `\x1b[${prefix.length + displayWidth(lines[curLine]) + 1}G`
138
161
 
139
162
  cursorLine = curLine
140
163
  process.stdout.write(buf)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@make-u-free/migi",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Your AI right-hand agent. Works anywhere, with any LLM API.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,6 +12,7 @@
12
12
  "dependencies": {
13
13
  "adm-zip": "^0.5.16",
14
14
  "chalk": "^5.3.0",
15
+ "diff": "^8.0.4",
15
16
  "dotenv": "^16.4.0",
16
17
  "glob": "^11.0.0",
17
18
  "openai": "^4.0.0",
package/src/tools.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from 'fs'
2
2
  import { execSync } from 'child_process'
3
3
  import { dirname, extname } from 'path'
4
+ import { diffLines } from 'diff'
4
5
  import { request } from 'https'
5
6
  import { glob } from 'glob'
6
7
  import xlsxPkg from 'xlsx'
@@ -126,6 +127,42 @@ export const teamsToolSchema = {
126
127
  }
127
128
  }
128
129
 
130
+ // ---- diff 表示 ----
131
+
132
+ import chalk from 'chalk'
133
+
134
+ function showDiff(path, oldContent, newContent) {
135
+ const MAX_LINES = 50 // 長すぎる diff は省略
136
+
137
+ if (oldContent === null) {
138
+ console.log(chalk.green(` + ${path} (新規作成)`))
139
+ return
140
+ }
141
+
142
+ if (oldContent === newContent) {
143
+ console.log(chalk.dim(` = ${path} (変更なし)`))
144
+ return
145
+ }
146
+
147
+ const parts = diffLines(oldContent, newContent)
148
+ let shown = 0
149
+ let truncated = false
150
+
151
+ for (const part of parts) {
152
+ if (!part.added && !part.removed) continue
153
+ const lines = part.value.replace(/\n$/, '').split('\n')
154
+ for (const line of lines) {
155
+ if (shown >= MAX_LINES) { truncated = true; break }
156
+ if (part.added) console.log(chalk.green(` + ${line}`))
157
+ if (part.removed) console.log(chalk.red(` - ${line}`))
158
+ shown++
159
+ }
160
+ if (truncated) break
161
+ }
162
+
163
+ if (truncated) console.log(chalk.dim(` … (省略)`))
164
+ }
165
+
129
166
  // ---- ツール実行 ----
130
167
 
131
168
  export async function executeTool(name, args, opts = {}) {
@@ -209,14 +246,18 @@ export async function executeTool(name, args, opts = {}) {
209
246
  case 'write_file': {
210
247
  const dir = dirname(args.path)
211
248
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
249
+ const oldContent = existsSync(args.path) ? readFileSync(args.path, 'utf-8') : null
212
250
  writeFileSync(args.path, args.content, 'utf-8')
251
+ showDiff(args.path, oldContent, args.content)
213
252
  return `完了: ${args.path} に書き込みました`
214
253
  }
215
254
 
216
255
  case 'append_file': {
217
256
  const dir = dirname(args.path)
218
257
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
258
+ const base = existsSync(args.path) ? readFileSync(args.path, 'utf-8') : ''
219
259
  appendFileSync(args.path, args.content, 'utf-8')
260
+ showDiff(args.path, base, base + args.content)
220
261
  return `完了: ${args.path} に追記しました`
221
262
  }
222
263