flowmind 1.4.3 → 1.4.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/README_CN.md CHANGED
@@ -319,6 +319,16 @@ Claude:我来使用 FlowMind 的代码审查技能...
319
319
  通过 JSON 输出与 Codex 集成。
320
320
 
321
321
  ```bash
322
+ # 推荐:使用 Codex 包装命令
323
+ flowmind-codex "查询最近1小时的错误日志"
324
+ flowmind-codex skills
325
+ flowmind-codex skill log-audit
326
+ flowmind-codex doctor
327
+
328
+ # 默认使用当前工作区的 .flowmind-codex/
329
+ # 也可以自定义目录
330
+ # FLOWMIND_CODEX_HOME=/your/path flowmind-codex skills
331
+
322
332
  # 获取技能列表 (JSON 格式)
323
333
  flowmind skills --json
324
334
 
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const fs = require('fs-extra');
5
+ const path = require('path');
6
+ const FlowMind = require('../core');
7
+ const { version } = require('../package.json');
8
+
9
+ function printJson(data) {
10
+ process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
11
+ }
12
+
13
+ function fail(message, extra = {}) {
14
+ printJson({
15
+ ok: false,
16
+ error: {
17
+ message,
18
+ ...extra
19
+ }
20
+ });
21
+ process.exitCode = 1;
22
+ }
23
+
24
+ async function createFlowMind() {
25
+ const codexHome = process.env.FLOWMIND_CODEX_HOME || path.join(process.cwd(), '.flowmind-codex');
26
+ process.env.FLOWMIND_HOME = process.env.FLOWMIND_HOME || codexHome;
27
+
28
+ const configPath = path.join(codexHome, 'config.json');
29
+ await fs.ensureDir(codexHome);
30
+
31
+ if (!await fs.pathExists(configPath)) {
32
+ await fs.writeJson(configPath, {
33
+ version: '1.0.0',
34
+ name: 'FlowMind Codex Workspace',
35
+ storagePath: codexHome,
36
+ learning: {
37
+ storagePath: path.join(codexHome, 'learning')
38
+ }
39
+ }, { spaces: 2 });
40
+ }
41
+
42
+ const flowmind = new FlowMind({ configPath });
43
+ await flowmind.init();
44
+ return flowmind;
45
+ }
46
+
47
+ program
48
+ .name('flowmind-codex')
49
+ .description('Codex-friendly JSON wrapper for FlowMind')
50
+ .version(version);
51
+
52
+ program
53
+ .argument('[input...]', 'Process a FlowMind request directly')
54
+ .option('-s, --skill <skill>', 'Use a specific skill')
55
+ .action(async (inputParts, options) => {
56
+ if (!inputParts.length) {
57
+ return;
58
+ }
59
+
60
+ try {
61
+ const flowmind = await createFlowMind();
62
+ const input = inputParts.join(' ');
63
+ const result = await flowmind.process(input, {
64
+ skill: options.skill
65
+ });
66
+
67
+ printJson({
68
+ ok: result.type !== 'error',
69
+ command: 'ask',
70
+ input,
71
+ result
72
+ });
73
+
74
+ if (result.type === 'error') {
75
+ process.exitCode = 1;
76
+ }
77
+ } catch (error) {
78
+ fail(error.message, { command: 'ask' });
79
+ }
80
+ });
81
+
82
+ program
83
+ .command('ask')
84
+ .description('Process a request and emit JSON')
85
+ .argument('<input...>', 'Input to process')
86
+ .option('-s, --skill <skill>', 'Use a specific skill')
87
+ .action(async (inputParts, options) => {
88
+ try {
89
+ const flowmind = await createFlowMind();
90
+ const input = inputParts.join(' ');
91
+ const result = await flowmind.process(input, {
92
+ skill: options.skill
93
+ });
94
+
95
+ printJson({
96
+ ok: result.type !== 'error',
97
+ command: 'ask',
98
+ input,
99
+ result
100
+ });
101
+
102
+ if (result.type === 'error') {
103
+ process.exitCode = 1;
104
+ }
105
+ } catch (error) {
106
+ fail(error.message, { command: 'ask' });
107
+ }
108
+ });
109
+
110
+ program
111
+ .command('skills')
112
+ .description('List skills as JSON')
113
+ .action(async () => {
114
+ try {
115
+ const flowmind = await createFlowMind();
116
+ printJson({
117
+ ok: true,
118
+ command: 'skills',
119
+ skills: flowmind.skills.list()
120
+ });
121
+ } catch (error) {
122
+ fail(error.message, { command: 'skills' });
123
+ }
124
+ });
125
+
126
+ program
127
+ .command('skill')
128
+ .description('Get one skill as JSON')
129
+ .argument('<name>', 'Skill name')
130
+ .action(async (name) => {
131
+ try {
132
+ const flowmind = await createFlowMind();
133
+ const skill = flowmind.skills.get(name);
134
+
135
+ if (!skill) {
136
+ fail(`Skill not found: ${name}`, { command: 'skill', skill: name });
137
+ return;
138
+ }
139
+
140
+ printJson({
141
+ ok: true,
142
+ command: 'skill',
143
+ skill: {
144
+ name: skill.name,
145
+ description: skill.definition?.description,
146
+ category: skill.definition?.category || skill.definition?.metadata?.category,
147
+ version: skill.definition?.version || skill.definition?.metadata?.version,
148
+ author: skill.definition?.author || skill.definition?.metadata?.author,
149
+ triggers: skill.definition?.triggers || [],
150
+ componentDependencies: skill.definition?.componentDependencies || []
151
+ }
152
+ });
153
+ } catch (error) {
154
+ fail(error.message, { command: 'skill', skill: name });
155
+ }
156
+ });
157
+
158
+ program
159
+ .command('doctor')
160
+ .description('Run health checks as JSON')
161
+ .action(async () => {
162
+ try {
163
+ const flowmind = await createFlowMind();
164
+ const result = await flowmind.doctor();
165
+ printJson({
166
+ ok: result.summary?.errors === 0,
167
+ command: 'doctor',
168
+ result
169
+ });
170
+
171
+ if (result.summary?.errors > 0) {
172
+ process.exitCode = 1;
173
+ }
174
+ } catch (error) {
175
+ fail(error.message, { command: 'doctor' });
176
+ }
177
+ });
178
+
179
+ program
180
+ .command('ai-status')
181
+ .description('Show AI status as JSON')
182
+ .action(async () => {
183
+ try {
184
+ const flowmind = await createFlowMind();
185
+ printJson({
186
+ ok: true,
187
+ command: 'ai-status',
188
+ result: flowmind.getAIStatus()
189
+ });
190
+ } catch (error) {
191
+ fail(error.message, { command: 'ai-status' });
192
+ }
193
+ });
194
+
195
+ program.parseAsync(process.argv).catch((error) => {
196
+ fail(error.message);
197
+ });
package/bin/flowmind.js CHANGED
@@ -24,8 +24,10 @@ function restoreTerminal() {
24
24
  process.stdin.setRawMode(false);
25
25
  }
26
26
  } catch (e) { /* ignore */ }
27
- // Show cursor, reset colors, clear scroll region
28
- process.stdout.write('\x1b[?25h\x1b[0m\x1b[r');
27
+ if (process.stdout.isTTY) {
28
+ // Show cursor, reset colors, clear scroll region
29
+ process.stdout.write('\x1b[?25h\x1b[0m\x1b[r');
30
+ }
29
31
  }
30
32
 
31
33
  // Global error handlers to prevent silent CLI crashes
@@ -503,6 +505,7 @@ program
503
505
  .alias('p')
504
506
  .description('Process a request')
505
507
  .argument('[input]', 'Input to process')
508
+ .option('-j, --json', 'Output as JSON (for tool integration)')
506
509
  .option('-s, --skill <skill>', 'Use specific skill')
507
510
  .option('-v, --verbose', 'Verbose output')
508
511
  .action(async (input, options) => {
@@ -514,18 +517,36 @@ program
514
517
  await runInteractiveMode(fm);
515
518
  } else {
516
519
  // Single command mode
517
- const spinner = ora('Processing...').start();
520
+ const spinner = options.json ? null : ora('Processing...').start();
518
521
 
519
522
  const result = await fm.process(input, {
520
523
  skill: options.skill,
521
524
  verbose: options.verbose
522
525
  });
523
526
 
524
- spinner.stop();
525
- displayResult(result);
527
+ if (spinner) {
528
+ spinner.stop();
529
+ }
530
+
531
+ if (options.json) {
532
+ console.log(JSON.stringify(result, null, 2));
533
+ if (result.type === 'error') {
534
+ process.exitCode = 1;
535
+ }
536
+ } else {
537
+ displayResult(result);
538
+ }
526
539
  }
527
540
  } catch (error) {
528
- console.error(chalk.red('Error:'), error.message);
541
+ if (options.json) {
542
+ console.log(JSON.stringify({
543
+ type: 'error',
544
+ message: error.message
545
+ }, null, 2));
546
+ process.exitCode = 1;
547
+ } else {
548
+ console.error(chalk.red('Error:'), error.message);
549
+ }
529
550
  } finally {
530
551
  restoreTerminal();
531
552
  }
@@ -312,7 +312,7 @@ class ConfigManager {
312
312
  * Get home directory
313
313
  */
314
314
  getHomeDir() {
315
- return process.env.HOME || process.env.USERPROFILE || os.homedir();
315
+ return process.env.FLOWMIND_HOME || process.env.HOME || process.env.USERPROFILE || os.homedir();
316
316
  }
317
317
 
318
318
  /**
@@ -26,20 +26,26 @@ const DRAGON_LEVELS = [
26
26
  class HonorEngine {
27
27
  constructor(config) {
28
28
  this.config = config;
29
- this.honorPath = path.join(
30
- config.get('storagePath') || path.join(process.env.HOME || process.env.USERPROFILE, '.flowmind'),
31
- 'honor.json'
32
- );
29
+ this.honorPath = null;
33
30
  this.skillsPath = config.get('skills.path', path.join(__dirname, '..', 'skills'));
34
31
  this.data = null;
35
32
  this.initialized = false;
36
33
  }
37
34
 
35
+ resolveHonorPath() {
36
+ const storagePath = this.config.get('storagePath')
37
+ || process.env.FLOWMIND_HOME
38
+ || path.join(process.env.HOME || process.env.USERPROFILE, '.flowmind');
39
+ return path.join(storagePath, 'honor.json');
40
+ }
41
+
38
42
  /**
39
43
  * Initialize honor engine
40
44
  */
41
45
  async init() {
42
46
  try {
47
+ this.honorPath = this.resolveHonorPath();
48
+
43
49
  if (await fs.pathExists(this.honorPath)) {
44
50
  this.data = await fs.readJson(this.honorPath);
45
51
  } else {
package/core/index.js CHANGED
@@ -116,9 +116,16 @@ class FlowMind {
116
116
 
117
117
  // 4. Select skill (AI-assisted if available)
118
118
  let skill = null;
119
+ if (context.skill) {
120
+ skill = this.skills.get(context.skill);
121
+ if (!skill) {
122
+ return this.formatError(`Skill not found: ${context.skill}`, input);
123
+ }
124
+ }
125
+
119
126
  const candidates = await this.skills.getCandidates(input, context);
120
127
 
121
- if (candidates.length > 0) {
128
+ if (!skill && candidates.length > 0) {
122
129
  // Use AI to select skill if available
123
130
  const aiSelection = await this.ai.selectSkill(input, candidates);
124
131
  if (aiSelection && aiSelection.selectedSkill) {
@@ -32,7 +32,7 @@ class LearningEngine {
32
32
  constructor(config, honorEngine = null) {
33
33
  this.config = config;
34
34
  this.honorEngine = honorEngine;
35
- this.learningPath = config.get('learning.storagePath', '~/.flowmind/learning');
35
+ this.learningPath = null;
36
36
  this.writeQueue = new WriteQueue();
37
37
  this.records = {};
38
38
  this.skillBindings = {};
@@ -44,6 +44,11 @@ class LearningEngine {
44
44
  * Initialize learning engine
45
45
  */
46
46
  async init() {
47
+ this.learningPath = this.config.get(
48
+ 'learning.storagePath',
49
+ path.join(process.env.FLOWMIND_HOME || process.env.HOME || process.env.USERPROFILE || '', '.flowmind', 'learning')
50
+ );
51
+
47
52
  // Ensure directories exist
48
53
  await fs.ensureDir(this.expandPath(this.learningPath));
49
54
  await fs.ensureDir(path.join(this.expandPath(this.learningPath), 'records'));
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "flowmind",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
4
4
  "description": "The AI Agent That Learns How You Work - Stop repeating yourself, FlowMind learns your workflows and applies them automatically.",
5
5
  "main": "core/index.js",
6
6
  "bin": {
7
7
  "flowmind": "./bin/flowmind.js",
8
+ "flowmind-codex": "./bin/flowmind-codex.js",
8
9
  "flowmind-mcp": "./mcp/server.js"
9
10
  },
10
11
  "scripts": {