momo-ai 1.0.69 → 1.0.70

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "momo-ai",
3
- "version": "1.0.69",
3
+ "version": "1.0.70",
4
4
  "description": "Rachel Momo ( OpenSpec )",
5
5
  "main": "src/momo.js",
6
6
  "bin": {
@@ -0,0 +1,297 @@
1
+ ---
2
+ name: mo-dev-vue2css
3
+ description: Use when Vue SFC has large style block (>100 lines), styles unstyled after split, or "Cannot find module" errors on CSS imports
4
+ ---
5
+
6
+ # Vue CSS Extraction
7
+
8
+ Extract CSS from Vue SFC `<style>` blocks to separate files. Core principle: **file extension must match template class usage**.
9
+
10
+ ## When to Use
11
+
12
+ - Large `<style>` block (>100 lines)
13
+ - Styles appear unstyled after extraction
14
+ - Build error: "Cannot find module"
15
+ - Need to share styles across components
16
+
17
+ ## Quick Workflow (Automated)
18
+
19
+ ```bash
20
+ # Run from skill directory or provide full path
21
+ cd .claude/skills/mo-dev-vue2css
22
+
23
+ # Extract CSS automatically
24
+ ./scripts/extract-css.sh /path/to/Component.vue
25
+
26
+ # Verify
27
+ cd /path/to/web && npm run build
28
+ ```
29
+
30
+ **Script handles:**
31
+ - Detects `class` vs `:class="$style"` usage
32
+ - Chooses correct extension (`.css` or `.module.css`)
33
+ - Extracts CSS content
34
+ - Updates Vue file with import
35
+ - Preserves `scoped`/`module` attributes
36
+
37
+ ## Manual Workflow (Fallback)
38
+
39
+ Use when script fails (multiple `<style>` blocks, mixed usage):
40
+
41
+ 1. **Detect usage**: `./scripts/detect-class-usage.sh Component.vue`
42
+ - Output: `plain-class`, `style-module`, `mixed`, or `no-class`
43
+ 2. **Extract manually**: Copy CSS, create file with correct extension
44
+ 3. **Update import**: `<style scoped src="./Component.css"></style>`
45
+ 4. **Verify**: `npm run build` + browser test
46
+
47
+ ## Quick Reference
48
+
49
+ | Template Uses | File Extension | Import |
50
+ |---------------|----------------|--------|
51
+ | `class="..."` | `.css` | `<style scoped src="./X.css">` |
52
+ | `:class="$style.x"` | `.module.css` | `<style module src="./X.module.css">` |
53
+ | Multiple blocks | `.scoped.css` + `.global.css` | Two imports (manual) |
54
+
55
+ ## Common Mistakes
56
+
57
+ ### Mistake 1: Wrong File Extension
58
+
59
+ **Problem**: Used `.module.css` when template uses plain classes
60
+
61
+ ```vue
62
+ <!-- Template uses plain class -->
63
+ <div class="overview-page">...</div>
64
+
65
+ <!-- WRONG: .module.css triggers CSS Modules -->
66
+ <style scoped src="./Overview.module.css"></style>
67
+
68
+ <!-- CORRECT: Use .css -->
69
+ <style scoped src="./Overview.css"></style>
70
+ ```
71
+
72
+ **Symptom**: Build passes but page looks unstyled
73
+
74
+ ### Mistake 2: Missing Scoping Attribute
75
+
76
+ **Problem**: Forgot to preserve `scoped` or `module` attribute
77
+
78
+ ```vue
79
+ <!-- WRONG: Lost scoped attribute -->
80
+ <style src="./Component.css"></style>
81
+
82
+ <!-- CORRECT: Preserve scoped -->
83
+ <style scoped src="./Component.css"></style>
84
+ ```
85
+
86
+ **Symptom**: Styles leak to other components
87
+
88
+ ### Mistake 3: Incorrect Import Path
89
+
90
+ **Problem**: Wrong relative path to CSS file
91
+
92
+ ```vue
93
+ <!-- WRONG: Missing ./ prefix -->
94
+ <style scoped src="Component.css"></style>
95
+
96
+ <!-- CORRECT: Relative path -->
97
+ <style scoped src="./Component.css"></style>
98
+ ```
99
+
100
+ **Symptom**: Build fails with "Cannot find module"
101
+
102
+ ### Mistake 4: Skipping Verification
103
+
104
+ **Problem**: Assumed extraction worked without testing
105
+
106
+ **Fix**: ALWAYS run build and browser test after extraction
107
+
108
+ ## Debugging Checklist
109
+
110
+ If page looks unstyled after extraction:
111
+
112
+ 1. ✅ Check file extension: `.css` or `.module.css`?
113
+ 2. ✅ Check template: plain `class` or `:class="$style"`?
114
+ 3. ✅ Check import: `<style scoped src="./xxx.css"></style>`?
115
+ 4. ✅ Check scoping: `scoped` or `module` attribute present?
116
+ 5. ✅ Check build: `npm run build` passes?
117
+ 6. ✅ Check browser: styles visible in DevTools?
118
+
119
+ ## Examples
120
+
121
+ ### Example 1: Simple Scoped Extraction
122
+
123
+ **Before**:
124
+ ```vue
125
+ <template>
126
+ <div class="card">
127
+ <h2 class="title">Title</h2>
128
+ </div>
129
+ </template>
130
+
131
+ <style scoped>
132
+ .card {
133
+ border: 1px solid #ddd;
134
+ padding: 16px;
135
+ }
136
+
137
+ .title {
138
+ font-size: 18px;
139
+ font-weight: bold;
140
+ }
141
+ </style>
142
+ ```
143
+
144
+ **After**:
145
+
146
+ `Component.vue`:
147
+ ```vue
148
+ <template>
149
+ <div class="card">
150
+ <h2 class="title">Title</h2>
151
+ </div>
152
+ </template>
153
+
154
+ <style scoped src="./Component.css"></style>
155
+ ```
156
+
157
+ `Component.css`:
158
+ ```css
159
+ .card {
160
+ border: 1px solid #ddd;
161
+ padding: 16px;
162
+ }
163
+
164
+ .title {
165
+ font-size: 18px;
166
+ font-weight: bold;
167
+ }
168
+ ```
169
+
170
+ ### Example 2: CSS Modules Extraction
171
+
172
+ **Before**:
173
+ ```vue
174
+ <template>
175
+ <div :class="$style.container">
176
+ <span :class="$style.label">Label</span>
177
+ </div>
178
+ </template>
179
+
180
+ <style module>
181
+ .container {
182
+ display: flex;
183
+ }
184
+
185
+ .label {
186
+ color: blue;
187
+ }
188
+ </style>
189
+ ```
190
+
191
+ **After**:
192
+
193
+ `Component.vue`:
194
+ ```vue
195
+ <template>
196
+ <div :class="$style.container">
197
+ <span :class="$style.label">Label</span>
198
+ </div>
199
+ </template>
200
+
201
+ <style module src="./Component.module.css"></style>
202
+ ```
203
+
204
+ `Component.module.css`:
205
+ ```css
206
+ .container {
207
+ display: flex;
208
+ }
209
+
210
+ .label {
211
+ color: blue;
212
+ }
213
+ ```
214
+
215
+ ### Example 3: Multiple Style Blocks
216
+
217
+ **Before**:
218
+ ```vue
219
+ <template>
220
+ <div class="local">
221
+ <div class="global">...</div>
222
+ </div>
223
+ </template>
224
+
225
+ <style scoped>
226
+ .local {
227
+ padding: 10px;
228
+ }
229
+ </style>
230
+
231
+ <style>
232
+ .global {
233
+ margin: 0;
234
+ }
235
+ </style>
236
+ ```
237
+
238
+ **After**:
239
+
240
+ `Component.vue`:
241
+ ```vue
242
+ <template>
243
+ <div class="local">
244
+ <div class="global">...</div>
245
+ </div>
246
+ </template>
247
+
248
+ <style scoped src="./Component.scoped.css"></style>
249
+ <style src="./Component.global.css"></style>
250
+ ```
251
+
252
+ `Component.scoped.css`:
253
+ ```css
254
+ .local {
255
+ padding: 10px;
256
+ }
257
+ ```
258
+
259
+ `Component.global.css`:
260
+ ```css
261
+ .global {
262
+ margin: 0;
263
+ }
264
+ ```
265
+
266
+ ## Integration with ft-webos
267
+
268
+ When working in ft-webos project:
269
+
270
+ 1. **Check CLAUDE.md first**: Review project-specific CSS conventions
271
+ 2. **Follow existing patterns**: Check how other components handle CSS
272
+ 3. **Verify theme compatibility**: Ensure extracted CSS works with all themes
273
+ 4. **Test in all themes**: macOS, Windows, Linux themes
274
+ 5. **Update memory**: Document any project-specific patterns learned
275
+
276
+ ## Workflow Summary
277
+
278
+ ```
279
+ 1. Read Vue SFC
280
+ 2. Analyze template class usage (plain vs $style)
281
+ 3. Determine file extension (.css vs .module.css)
282
+ 4. Extract CSS content to new file
283
+ 5. Update SFC with external import
284
+ 6. Preserve scoping attributes (scoped/module)
285
+ 7. Run npm run build
286
+ 8. Test in browser
287
+ 9. Verify styles render correctly
288
+ ```
289
+
290
+ ## Success Criteria
291
+
292
+ - ✅ Build passes with no errors
293
+ - ✅ Styles render correctly in browser
294
+ - ✅ No visual regressions
295
+ - ✅ File extension matches template usage
296
+ - ✅ Scoping preserved (no style leaks)
297
+ - ✅ Import path correct
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ $# -ne 1 ]; then
5
+ echo "Usage: $0 <component.vue>" >&2
6
+ exit 1
7
+ fi
8
+
9
+ FILE="$1"
10
+ if [ ! -f "$FILE" ]; then
11
+ echo "Error: file not found: $FILE" >&2
12
+ exit 1
13
+ fi
14
+
15
+ PLAIN_COUNT=$(grep -Eo '(^|[^:])class="[^"]*"' "$FILE" 2>/dev/null | wc -l | tr -d ' ' || true)
16
+ STYLE_COUNT=$(grep -Eo ':class="\$style\.[^"]*"' "$FILE" 2>/dev/null | wc -l | tr -d ' ' || true)
17
+
18
+ if [ "$PLAIN_COUNT" -gt 0 ] && [ "$STYLE_COUNT" -eq 0 ]; then
19
+ echo "plain-class"
20
+ exit 0
21
+ fi
22
+
23
+ if [ "$PLAIN_COUNT" -eq 0 ] && [ "$STYLE_COUNT" -gt 0 ]; then
24
+ echo "style-module"
25
+ exit 0
26
+ fi
27
+
28
+ if [ "$PLAIN_COUNT" -gt 0 ] && [ "$STYLE_COUNT" -gt 0 ]; then
29
+ echo "mixed"
30
+ exit 0
31
+ fi
32
+
33
+ echo "no-class"
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ $# -ne 1 ]; then
5
+ echo "Usage: $0 <component.vue>" >&2
6
+ exit 1
7
+ fi
8
+
9
+ FILE="$1"
10
+ if [ ! -f "$FILE" ]; then
11
+ echo "Error: file not found: $FILE" >&2
12
+ exit 1
13
+ fi
14
+
15
+ DIR="$(dirname "$FILE")"
16
+ BASE="$(basename "$FILE" .vue)"
17
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ DETECT_SCRIPT="$SCRIPT_DIR/detect-class-usage.sh"
19
+ UPDATE_SCRIPT="$SCRIPT_DIR/update-import.sh"
20
+
21
+ USAGE="$($DETECT_SCRIPT "$FILE")"
22
+ case "$USAGE" in
23
+ plain-class) CSS_EXT=".css" ;;
24
+ style-module) CSS_EXT=".module.css" ;;
25
+ mixed)
26
+ echo "Error: mixed class and \$style usage. Handle manually." >&2
27
+ exit 1
28
+ ;;
29
+ no-class) CSS_EXT=".css" ;;
30
+ *)
31
+ echo "Error: unknown class usage result: $USAGE" >&2
32
+ exit 1
33
+ ;;
34
+ esac
35
+
36
+ STYLE_COUNT=$(grep -c '<style' "$FILE" || true)
37
+ if [ "$STYLE_COUNT" -eq 0 ]; then
38
+ echo "No <style> block found in $FILE"
39
+ exit 0
40
+ fi
41
+
42
+ if [ "$STYLE_COUNT" -gt 1 ]; then
43
+ echo "Error: multiple <style> blocks detected. Handle manually." >&2
44
+ exit 1
45
+ fi
46
+
47
+ STYLE_OPEN_LINE=$(grep -n '<style[^>]*>' "$FILE" | head -n1 | cut -d: -f1)
48
+ STYLE_CLOSE_LINE=$(grep -n '</style>' "$FILE" | head -n1 | cut -d: -f1)
49
+
50
+ if [ -z "$STYLE_OPEN_LINE" ] || [ -z "$STYLE_CLOSE_LINE" ] || [ "$STYLE_CLOSE_LINE" -le "$STYLE_OPEN_LINE" ]; then
51
+ echo "Error: invalid <style> block in $FILE" >&2
52
+ exit 1
53
+ fi
54
+
55
+ STYLE_TAG=$(sed -n "${STYLE_OPEN_LINE}p" "$FILE")
56
+ CSS_FILE="$DIR/$BASE$CSS_EXT"
57
+ CSS_FILE_NAME="$BASE$CSS_EXT"
58
+
59
+ sed -n "$((STYLE_OPEN_LINE + 1)),$((STYLE_CLOSE_LINE - 1))p" "$FILE" > "$CSS_FILE"
60
+
61
+ if [[ "$STYLE_TAG" == *"scoped"* ]]; then
62
+ MODE="scoped"
63
+ elif [[ "$STYLE_TAG" == *"module"* ]]; then
64
+ MODE="module"
65
+ else
66
+ MODE="plain"
67
+ fi
68
+
69
+ "$UPDATE_SCRIPT" "$FILE" "$CSS_FILE_NAME" "$MODE"
70
+
71
+ echo "Extracted: $CSS_FILE"
72
+ echo "Updated: $FILE"
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ $# -lt 2 ] || [ $# -gt 3 ]; then
5
+ echo "Usage: $0 <component.vue> <css-file-name> [scoped|module|plain]" >&2
6
+ exit 1
7
+ fi
8
+
9
+ FILE="$1"
10
+ CSS_FILE_NAME="$2"
11
+ MODE="${3:-scoped}"
12
+
13
+ if [ ! -f "$FILE" ]; then
14
+ echo "Error: file not found: $FILE" >&2
15
+ exit 1
16
+ fi
17
+
18
+ STYLE_COUNT=$(grep -c '<style' "$FILE" || true)
19
+ if [ "$STYLE_COUNT" -eq 0 ]; then
20
+ echo "Error: no <style> block found in $FILE" >&2
21
+ exit 1
22
+ fi
23
+
24
+ if [ "$STYLE_COUNT" -gt 1 ]; then
25
+ echo "Error: multiple <style> blocks detected. Handle manually." >&2
26
+ exit 1
27
+ fi
28
+
29
+ STYLE_OPEN_LINE=$(grep -n '<style[^>]*>' "$FILE" | head -n1 | cut -d: -f1)
30
+ STYLE_CLOSE_LINE=$(grep -n '</style>' "$FILE" | head -n1 | cut -d: -f1)
31
+
32
+ if [ -z "$STYLE_OPEN_LINE" ] || [ -z "$STYLE_CLOSE_LINE" ] || [ "$STYLE_CLOSE_LINE" -le "$STYLE_OPEN_LINE" ]; then
33
+ echo "Error: invalid <style> block in $FILE" >&2
34
+ exit 1
35
+ fi
36
+
37
+ case "$MODE" in
38
+ scoped) NEW_STYLE="<style scoped src=\"./$CSS_FILE_NAME\"></style>" ;;
39
+ module) NEW_STYLE="<style module src=\"./$CSS_FILE_NAME\"></style>" ;;
40
+ plain) NEW_STYLE="<style src=\"./$CSS_FILE_NAME\"></style>" ;;
41
+ *)
42
+ echo "Error: invalid mode '$MODE' (use scoped|module|plain)" >&2
43
+ exit 1
44
+ ;;
45
+ esac
46
+
47
+ awk -v start="$STYLE_OPEN_LINE" -v end="$STYLE_CLOSE_LINE" -v repl="$NEW_STYLE" '
48
+ NR < start { print; next }
49
+ NR == start { print repl; next }
50
+ NR > start && NR <= end { next }
51
+ { print }
52
+ ' "$FILE" > "$FILE.tmp"
53
+ mv "$FILE.tmp" "$FILE"
54
+
55
+ echo "Updated import in: $FILE"
@@ -49,49 +49,85 @@ const _isObsidianInstalled = async () => {
49
49
  };
50
50
 
51
51
  /**
52
- * 检查 Obsidian 是否正在运行
52
+ * 检查指定 vault 是否正在运行
53
+ * @param {string} vaultPath vault 路径
53
54
  * @returns {Promise<boolean>}
54
55
  */
55
- const _isObsidianRunning = async () => {
56
+ const _isVaultRunning = async (vaultPath) => {
56
57
  const platform = os.platform();
57
-
58
+ let configPath;
59
+
60
+ if (platform === 'darwin') {
61
+ configPath = path.join(os.homedir(), 'Library/Application Support/obsidian/obsidian.json');
62
+ } else if (platform === 'win32') {
63
+ configPath = path.join(process.env.APPDATA || '', 'obsidian', 'obsidian.json');
64
+ } else {
65
+ const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
66
+ configPath = path.join(xdgConfig, 'obsidian', 'obsidian.json');
67
+ }
68
+
58
69
  try {
59
- if (platform === 'darwin') {
60
- const result = execSync('pgrep -f "Obsidian.app"', { stdio: 'pipe' });
61
- return result.toString().trim().length > 0;
62
- } else if (platform === 'win32') {
63
- const result = execSync('tasklist /FI "IMAGENAME eq Obsidian.exe"', { stdio: 'pipe' });
64
- return result.toString().includes('Obsidian.exe');
65
- } else {
66
- const result = execSync('pgrep -f obsidian', { stdio: 'pipe' });
67
- return result.toString().trim().length > 0;
70
+ if (!fs.existsSync(configPath)) {
71
+ return false;
68
72
  }
73
+
74
+ const content = await fsAsync.readFile(configPath, 'utf8');
75
+ const config = JSON.parse(content);
76
+
77
+ // 检查是否有 vault 的 open 状态为 true 且路径匹配
78
+ const openVault = Object.values(config.vaults || {}).find(
79
+ vault => vault.path === vaultPath && vault.open === true
80
+ );
81
+
82
+ return !!openVault;
69
83
  } catch {
70
84
  return false;
71
85
  }
72
86
  };
73
87
 
74
88
  /**
75
- * 关闭 Obsidian 应用
89
+ * 关闭指定 vault 的 Obsidian 窗口
90
+ * 通过更新配置文件将 vault 的 open 状态设为 false
91
+ * @param {string} vaultPath vault 路径
76
92
  * @returns {Promise<void>}
77
93
  */
78
- const _closeObsidian = async () => {
94
+ const _closeVault = async (vaultPath) => {
79
95
  const platform = os.platform();
80
-
96
+ let configPath;
97
+
98
+ if (platform === 'darwin') {
99
+ configPath = path.join(os.homedir(), 'Library/Application Support/obsidian/obsidian.json');
100
+ } else if (platform === 'win32') {
101
+ configPath = path.join(process.env.APPDATA || '', 'obsidian', 'obsidian.json');
102
+ } else {
103
+ const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
104
+ configPath = path.join(xdgConfig, 'obsidian', 'obsidian.json');
105
+ }
106
+
81
107
  try {
82
- if (platform === 'darwin') {
83
- execSync('killall Obsidian', { stdio: 'ignore' });
84
- // 等待进程完全关闭
85
- await new Promise(resolve => setTimeout(resolve, 1000));
86
- } else if (platform === 'win32') {
87
- execSync('taskkill /F /IM Obsidian.exe', { stdio: 'ignore' });
88
- await new Promise(resolve => setTimeout(resolve, 1000));
89
- } else {
90
- execSync('killall obsidian', { stdio: 'ignore' });
91
- await new Promise(resolve => setTimeout(resolve, 1000));
108
+ if (!fs.existsSync(configPath)) {
109
+ return;
92
110
  }
93
- } catch {
94
- // 如果关闭失败也不报错
111
+
112
+ const content = await fsAsync.readFile(configPath, 'utf8');
113
+ const config = JSON.parse(content);
114
+
115
+ // 找到对应的 vault 并设置 open 为 false
116
+ let updated = false;
117
+ for (const [id, vault] of Object.entries(config.vaults || {})) {
118
+ if (vault.path === vaultPath && vault.open === true) {
119
+ config.vaults[id].open = false;
120
+ updated = true;
121
+ }
122
+ }
123
+
124
+ if (updated) {
125
+ await fsAsync.writeFile(configPath, JSON.stringify(config), 'utf8');
126
+ // 等待配置生效
127
+ await new Promise(resolve => setTimeout(resolve, 500));
128
+ }
129
+ } catch (error) {
130
+ Ec.warn(`⚠ 关闭 vault 失败: ${error.message}`);
95
131
  }
96
132
  };
97
133
 
@@ -451,11 +487,11 @@ module.exports = async (options) => {
451
487
 
452
488
  Ec.info('✓ Obsidian 已安装');
453
489
 
454
- // 6. 检查 Obsidian 是否正在运行
455
- const isRunning = await _isObsidianRunning();
456
- if (isRunning) {
457
- Ec.waiting('检测到 Obsidian 正在运行,正在重启以加载新 vault...');
458
- await _closeObsidian();
490
+ // 6. 检查当前 vault 是否正在运行
491
+ const isVaultRunning = await _isVaultRunning(targetDir);
492
+ if (isVaultRunning) {
493
+ Ec.waiting('检测到该 vault 正在运行,正在关闭以重新加载...');
494
+ await _closeVault(targetDir);
459
495
  }
460
496
 
461
497
  // 7. 注册 vault 到 Obsidian 配置