flowmind 1.5.3 → 1.5.6
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/CHANGELOG.md +31 -1
- package/README.md +2 -0
- package/README_CN.md +2 -0
- package/bin/flowmind.js +104 -118
- package/core/adapters/mcp-adapter.js +9 -8
- package/core/ai/providers/mimo.js +1 -1
- package/core/cli-ink.js +79 -0
- package/core/index.js +139 -1
- package/core/log-query-parser.js +324 -0
- package/core/providers/aliyun/dms-adapter.js +7 -35
- package/core/providers/aliyun/redis-adapter.js +4 -20
- package/core/providers/aliyun/sls-adapter.js +3 -10
- package/core/providers/friday/report-adapter.js +5 -25
- package/core/providers/yapi/yapi-adapter.js +6 -30
- package/core/providers/yuque/yuque-adapter.js +7 -35
- package/core/update-notifier.js +74 -0
- package/dashboard/app.jsx +69 -10
- package/dashboard/components/ActivityFeed.jsx +5 -4
- package/dashboard/components/DragonPanel.jsx +17 -1
- package/dashboard/components/McpStatusBar.jsx +19 -1
- package/dashboard/components/StatsRow.jsx +27 -2
- package/package.json +2 -1
- package/scripts/check-update.js +52 -0
- package/skills/auto-flow/index.js +451 -122
- package/skills/log-audit/index.js +146 -25
- package/skills/sls-log-audit/index.js +7 -30
- package/skills/yapi-sync-interface/index.js +146 -13
- package/skills/yuque-sync-design/index.js +132 -13
- package/tui/app.jsx +86 -9
- package/tui/components/ChatPanel.jsx +10 -7
- package/tui/components/DragonTotem.jsx +12 -1
- package/tui/components/Sidebar.jsx +19 -7
- package/tui/components/StatusBar.jsx +28 -1
- package/tui/format-result.js +60 -0
- package/tui/layout.js +60 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
function compareVersions(currentVersion, latestVersion) {
|
|
4
|
+
const current = parseVersion(currentVersion);
|
|
5
|
+
const latest = parseVersion(latestVersion);
|
|
6
|
+
|
|
7
|
+
for (let index = 0; index < 3; index += 1) {
|
|
8
|
+
if (latest[index] > current[index]) return -1;
|
|
9
|
+
if (latest[index] < current[index]) return 1;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseVersion(version) {
|
|
16
|
+
return String(version || '0.0.0')
|
|
17
|
+
.split('.')
|
|
18
|
+
.slice(0, 3)
|
|
19
|
+
.map((part) => Number.parseInt(part, 10) || 0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getLatestVersion(packageName = 'flowmind') {
|
|
23
|
+
return execSync(`npm view ${packageName} version`, { encoding: 'utf-8' }).trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isGlobalInstall(packageJsonPath, packageName = 'flowmind') {
|
|
27
|
+
try {
|
|
28
|
+
const globalRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();
|
|
29
|
+
return String(packageJsonPath || '').startsWith(globalRoot) || String(packageJsonPath || '').includes(`${globalRoot}/${packageName}`);
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildInstallCommand({ packageName = 'flowmind', version, globalInstall = false }) {
|
|
36
|
+
if (!version) {
|
|
37
|
+
return globalInstall ? `npm install -g ${packageName}@latest` : `npm install ${packageName}@latest`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return globalInstall
|
|
41
|
+
? `npm install -g ${packageName}@${version}`
|
|
42
|
+
: `npm install ${packageName}@${version}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getUpdateStatus({
|
|
46
|
+
packageName = 'flowmind',
|
|
47
|
+
currentVersion = '0.0.0',
|
|
48
|
+
packageJsonPath = ''
|
|
49
|
+
} = {}) {
|
|
50
|
+
const latestVersion = getLatestVersion(packageName);
|
|
51
|
+
const comparison = compareVersions(currentVersion, latestVersion);
|
|
52
|
+
const globalInstall = isGlobalInstall(packageJsonPath, packageName);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
packageName,
|
|
56
|
+
currentVersion,
|
|
57
|
+
latestVersion,
|
|
58
|
+
comparison,
|
|
59
|
+
globalInstall,
|
|
60
|
+
installCommand: buildInstallCommand({
|
|
61
|
+
packageName,
|
|
62
|
+
version: latestVersion,
|
|
63
|
+
globalInstall
|
|
64
|
+
})
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
buildInstallCommand,
|
|
70
|
+
compareVersions,
|
|
71
|
+
getLatestVersion,
|
|
72
|
+
getUpdateStatus,
|
|
73
|
+
isGlobalInstall
|
|
74
|
+
};
|
package/dashboard/app.jsx
CHANGED
|
@@ -1,27 +1,86 @@
|
|
|
1
1
|
const React = require('react');
|
|
2
|
-
const { Box, useApp, useInput } = require('ink');
|
|
2
|
+
const { Box, Text, useApp, useInput } = require('ink');
|
|
3
3
|
const ActivityFeed = require('./components/ActivityFeed.jsx');
|
|
4
4
|
const StatsRow = require('./components/StatsRow.jsx');
|
|
5
5
|
const DragonPanel = require('./components/DragonPanel.jsx');
|
|
6
6
|
const McpStatusBar = require('./components/McpStatusBar.jsx');
|
|
7
|
+
const { getTuiLayout, MIN_COLUMNS, MIN_ROWS } = require('../tui/layout');
|
|
8
|
+
|
|
9
|
+
class DashboardErrorBoundary extends React.Component {
|
|
10
|
+
constructor(props) {
|
|
11
|
+
super(props);
|
|
12
|
+
this.state = { error: null };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static getDerivedStateFromError(error) {
|
|
16
|
+
return { error };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
render() {
|
|
20
|
+
if (this.state.error) {
|
|
21
|
+
return React.createElement(
|
|
22
|
+
Box,
|
|
23
|
+
{ flexDirection: 'column', borderStyle: 'single', borderColor: 'red', paddingX: 1, paddingY: 1 },
|
|
24
|
+
React.createElement(Text, { bold: true, color: 'red' }, 'Dashboard render error'),
|
|
25
|
+
React.createElement(Text, { color: 'gray' }, this.state.error.message || 'Unknown render failure'),
|
|
26
|
+
React.createElement(Text, { color: 'gray' }, 'Resize the terminal or restart FlowMind.')
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return this.props.children;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
7
32
|
|
|
8
33
|
function DashboardApp({ flowmind, eventBus, asciiMode = false }) {
|
|
9
34
|
const { exit } = useApp();
|
|
35
|
+
const [terminalSize, setTerminalSize] = React.useState(() => ({
|
|
36
|
+
columns: Number(process.stdout?.columns) || 0,
|
|
37
|
+
rows: Number(process.stdout?.rows) || 0
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
const updateSize = () => {
|
|
42
|
+
setTerminalSize({
|
|
43
|
+
columns: Number(process.stdout?.columns) || 0,
|
|
44
|
+
rows: Number(process.stdout?.rows) || 0
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
updateSize();
|
|
49
|
+
process.stdout?.on?.('resize', updateSize);
|
|
50
|
+
|
|
51
|
+
return () => {
|
|
52
|
+
process.stdout?.removeListener?.('resize', updateSize);
|
|
53
|
+
};
|
|
54
|
+
}, []);
|
|
10
55
|
|
|
11
56
|
useInput((input, key) => {
|
|
12
57
|
if (key.ctrl && input === 'c') exit();
|
|
13
58
|
});
|
|
14
59
|
|
|
60
|
+
const layout = getTuiLayout(terminalSize.columns, terminalSize.rows);
|
|
61
|
+
|
|
62
|
+
if (layout.tooSmall) {
|
|
63
|
+
return (
|
|
64
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'red', paddingX: 1, paddingY: 1 },
|
|
65
|
+
React.createElement(Text, { bold: true, color: 'red' }, 'Terminal too small'),
|
|
66
|
+
React.createElement(Text, { color: 'gray' }, `Need at least ${MIN_COLUMNS}x${MIN_ROWS}. Current size: ${layout.columns}x${layout.rows}.`),
|
|
67
|
+
React.createElement(Text, { color: 'gray' }, 'Resize the terminal to keep the dashboard stable.')
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
15
72
|
return (
|
|
16
|
-
React.createElement(
|
|
17
|
-
React.createElement(Box, { flexDirection: '
|
|
18
|
-
React.createElement(
|
|
19
|
-
|
|
20
|
-
React.createElement(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
73
|
+
React.createElement(DashboardErrorBoundary, null,
|
|
74
|
+
React.createElement(Box, { flexDirection: 'column', width: '100%', height: '100%' },
|
|
75
|
+
React.createElement(Box, { flexDirection: layout.columns < 120 ? 'column' : 'row', flexGrow: 1 },
|
|
76
|
+
React.createElement(ActivityFeed, { eventBus: eventBus, asciiMode: asciiMode, width: layout.columns, compact: layout.columns < 120 }),
|
|
77
|
+
React.createElement(Box, { flexDirection: 'column', width: layout.columns < 120 ? '100%' : '60%', flexGrow: 1 },
|
|
78
|
+
React.createElement(StatsRow, { flowmind: flowmind, asciiMode: asciiMode, width: layout.columns, compact: layout.columns < 120 }),
|
|
79
|
+
React.createElement(DragonPanel, { flowmind: flowmind, asciiMode: asciiMode, width: layout.columns, compact: layout.columns < 120 })
|
|
80
|
+
)
|
|
81
|
+
),
|
|
82
|
+
React.createElement(McpStatusBar, { eventBus: eventBus, asciiMode: asciiMode, width: layout.columns, compact: layout.columns < 120 })
|
|
83
|
+
)
|
|
25
84
|
)
|
|
26
85
|
);
|
|
27
86
|
}
|
|
@@ -38,7 +38,7 @@ function formatEvent(event, asciiMode) {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function ActivityFeed({ eventBus, asciiMode = false }) {
|
|
41
|
+
function ActivityFeed({ eventBus, asciiMode = false, width = 0, compact = false }) {
|
|
42
42
|
const [events, setEvents] = React.useState([]);
|
|
43
43
|
|
|
44
44
|
React.useEffect(() => {
|
|
@@ -66,13 +66,14 @@ function ActivityFeed({ eventBus, asciiMode = false }) {
|
|
|
66
66
|
};
|
|
67
67
|
}, [eventBus]);
|
|
68
68
|
|
|
69
|
-
const displayEvents = events.slice(-30);
|
|
69
|
+
const displayEvents = events.slice(-(compact ? 12 : 30));
|
|
70
|
+
const feedWidth = compact ? '100%' : '40%';
|
|
70
71
|
|
|
71
72
|
return (
|
|
72
|
-
React.createElement(Box, { flexDirection: 'column', borderStyle: getBorderStyle(asciiMode), borderColor: 'green', paddingX: 1, width:
|
|
73
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: getBorderStyle(asciiMode), borderColor: 'green', paddingX: 1, width: feedWidth },
|
|
73
74
|
React.createElement(Text, { bold: true, color: 'green' }, 'Activity Feed'),
|
|
74
75
|
React.createElement(Box, { flexDirection: 'column', marginTop: 1, overflow: 'hidden' },
|
|
75
|
-
displayEvents.length === 0 && React.createElement(Text, { color: 'gray' }, 'Waiting for events...'),
|
|
76
|
+
displayEvents.length === 0 && React.createElement(Text, { color: 'gray' }, compact ? 'Waiting...' : 'Waiting for events...'),
|
|
76
77
|
displayEvents.map((event, i) =>
|
|
77
78
|
React.createElement(Text, { key: i },
|
|
78
79
|
React.createElement(Text, { color: 'gray' }, formatTime(event.timestamp) + ' '),
|
|
@@ -2,7 +2,7 @@ const React = require('react');
|
|
|
2
2
|
const { Box, Text } = require('ink');
|
|
3
3
|
const { LEVEL_COLORS, LEVEL_NAMES, LEVEL_STATES, getBorderStyle, getDragonArt } = require('../../tui/ui');
|
|
4
4
|
|
|
5
|
-
function DragonPanel({ flowmind, asciiMode = false }) {
|
|
5
|
+
function DragonPanel({ flowmind, asciiMode = false, width = 0, compact = false }) {
|
|
6
6
|
const [honorData, setHonorData] = React.useState({ points: 0, level: 0, stats: {} });
|
|
7
7
|
|
|
8
8
|
React.useEffect(() => {
|
|
@@ -23,6 +23,22 @@ function DragonPanel({ flowmind, asciiMode = false }) {
|
|
|
23
23
|
const nextLevelPoints = [1, 10, 30, 60, 100];
|
|
24
24
|
const nextPoints = nextLevelPoints[level] || null;
|
|
25
25
|
const pointsToNext = nextPoints !== null ? nextPoints - honorData.points : 0;
|
|
26
|
+
const superCompact = compact || width < 120;
|
|
27
|
+
|
|
28
|
+
if (superCompact) {
|
|
29
|
+
return (
|
|
30
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: getBorderStyle(asciiMode), borderColor: 'cyan', paddingX: 1, flexGrow: 1 },
|
|
31
|
+
React.createElement(Text, { bold: true, color: 'cyan' }, 'Dragon Totem'),
|
|
32
|
+
React.createElement(Text, null,
|
|
33
|
+
React.createElement(Text, { color: 'yellow', bold: true }, 'Lv' + level),
|
|
34
|
+
React.createElement(Text, { color: 'white' }, ' ' + levelName)
|
|
35
|
+
),
|
|
36
|
+
React.createElement(Text, { color: 'gray' }, 'State: ' + state),
|
|
37
|
+
React.createElement(Text, { color: 'gray' }, honorData.points + ' pts'),
|
|
38
|
+
pointsToNext > 0 && React.createElement(Text, { color: 'gray' }, pointsToNext + ' to next')
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
26
42
|
|
|
27
43
|
return (
|
|
28
44
|
React.createElement(Box, { flexDirection: 'column', borderStyle: getBorderStyle(asciiMode), borderColor: 'cyan', paddingX: 1, flexGrow: 1 },
|
|
@@ -2,10 +2,11 @@ const React = require('react');
|
|
|
2
2
|
const { Box, Text } = require('ink');
|
|
3
3
|
const { getBorderStyle } = require('../../tui/ui');
|
|
4
4
|
|
|
5
|
-
function McpStatusBar({ eventBus, asciiMode = false }) {
|
|
5
|
+
function McpStatusBar({ eventBus, asciiMode = false, width = 0, compact = false }) {
|
|
6
6
|
const [toolCount, setToolCount] = React.useState(0);
|
|
7
7
|
const [lastCall, setLastCall] = React.useState(null);
|
|
8
8
|
const [serverState, setServerState] = React.useState('running');
|
|
9
|
+
const isCompact = compact || width < 120;
|
|
9
10
|
|
|
10
11
|
React.useEffect(() => {
|
|
11
12
|
if (!eventBus) return;
|
|
@@ -19,6 +20,23 @@ function McpStatusBar({ eventBus, asciiMode = false }) {
|
|
|
19
20
|
|
|
20
21
|
const formatTime = (ts) => ts ? new Date(ts).toTimeString().substring(0, 8) : 'none';
|
|
21
22
|
|
|
23
|
+
if (isCompact) {
|
|
24
|
+
return (
|
|
25
|
+
React.createElement(Box, { borderStyle: getBorderStyle(asciiMode), borderColor: 'gray', paddingX: 1, flexDirection: 'column' },
|
|
26
|
+
React.createElement(Text, null,
|
|
27
|
+
React.createElement(Text, { color: 'gray' }, 'MCP: '),
|
|
28
|
+
React.createElement(Text, { color: 'green' }, serverState),
|
|
29
|
+
React.createElement(Text, { color: 'gray' }, ' | Tools: '),
|
|
30
|
+
React.createElement(Text, { color: 'white' }, '' + toolCount)
|
|
31
|
+
),
|
|
32
|
+
React.createElement(Text, null,
|
|
33
|
+
React.createElement(Text, { color: 'gray' }, 'Last: '),
|
|
34
|
+
React.createElement(Text, { color: 'white' }, formatTime(lastCall))
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
return (
|
|
23
41
|
React.createElement(Box, { borderStyle: getBorderStyle(asciiMode), borderColor: 'gray', paddingX: 1, justifyContent: 'space-between' },
|
|
24
42
|
React.createElement(Text, null,
|
|
@@ -2,7 +2,7 @@ const React = require('react');
|
|
|
2
2
|
const { Box, Text } = require('ink');
|
|
3
3
|
const { LEVEL_NAMES, getBorderStyle, getProgressBar } = require('../../tui/ui');
|
|
4
4
|
|
|
5
|
-
function StatsRow({ flowmind, asciiMode = false }) {
|
|
5
|
+
function StatsRow({ flowmind, asciiMode = false, width = 0, compact = false }) {
|
|
6
6
|
const [honorData, setHonorData] = React.useState({ points: 0, level: 0, stats: {} });
|
|
7
7
|
const [learningStats, setLearningStats] = React.useState({ totalRecords: 0, byType: {} });
|
|
8
8
|
const [aiStatus, setAiStatus] = React.useState({ initialized: false, defaultProvider: 'none' });
|
|
@@ -19,10 +19,35 @@ function StatsRow({ flowmind, asciiMode = false }) {
|
|
|
19
19
|
return () => clearInterval(interval);
|
|
20
20
|
}, [flowmind]);
|
|
21
21
|
|
|
22
|
-
const barWidth = 16;
|
|
22
|
+
const barWidth = compact ? Math.max(8, Math.floor(width / 10)) : 16;
|
|
23
23
|
const progress = honorData.points > 0 ? Math.min(1, honorData.points / 100) : 0;
|
|
24
24
|
const progressBar = getProgressBar(barWidth, progress, asciiMode);
|
|
25
25
|
|
|
26
|
+
if (compact) {
|
|
27
|
+
return (
|
|
28
|
+
React.createElement(Box, { flexDirection: 'column' },
|
|
29
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: getBorderStyle(asciiMode), borderColor: 'yellow', paddingX: 1, width: '100%' },
|
|
30
|
+
React.createElement(Text, { bold: true, color: 'yellow' }, 'Honor'),
|
|
31
|
+
React.createElement(Text, { color: 'yellow' }, LEVEL_NAMES[honorData.level] || 'Egg'),
|
|
32
|
+
React.createElement(Text, { color: 'green' }, progressBar),
|
|
33
|
+
React.createElement(Text, { color: 'gray' }, honorData.points + '/100 pts')
|
|
34
|
+
),
|
|
35
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: getBorderStyle(asciiMode), borderColor: 'cyan', paddingX: 1, width: '100%', marginTop: 1 },
|
|
36
|
+
React.createElement(Text, { bold: true, color: 'cyan' }, 'Learning'),
|
|
37
|
+
React.createElement(Text, { color: 'white' }, '' + (learningStats.totalRecords || 0) + ' records'),
|
|
38
|
+
React.createElement(Text, null,
|
|
39
|
+
React.createElement(Text, { color: 'gray' }, 'AI: '),
|
|
40
|
+
React.createElement(Text, { color: aiStatus.initialized ? 'green' : 'red' }, aiStatus.initialized ? 'ok' : 'off')
|
|
41
|
+
)
|
|
42
|
+
),
|
|
43
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: getBorderStyle(asciiMode), borderColor: 'blue', paddingX: 1, width: '100%', marginTop: 1 },
|
|
44
|
+
React.createElement(Text, { bold: true, color: 'blue' }, 'Components'),
|
|
45
|
+
React.createElement(Text, { color: 'gray' }, aiStatus.defaultProvider || 'none')
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
26
51
|
return (
|
|
27
52
|
React.createElement(Box, { flexDirection: 'row' },
|
|
28
53
|
React.createElement(Box, { flexDirection: 'column', borderStyle: getBorderStyle(asciiMode), borderColor: 'yellow', paddingX: 1, width: '33%' },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowmind",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.6",
|
|
4
4
|
"description": "Memory and workflow automation for MCP, Codex, and Claude Code. Reuse repeatable developer operations through skills and explicit feedback.",
|
|
5
5
|
"main": "core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"start": "node core/index.js",
|
|
13
13
|
"mcp": "node mcp/server.js",
|
|
14
|
+
"check:update": "node scripts/check-update.js",
|
|
14
15
|
"test": "jest",
|
|
15
16
|
"test:coverage": "jest --coverage",
|
|
16
17
|
"lint": "eslint .",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const packageJson = require('../package.json');
|
|
5
|
+
const {
|
|
6
|
+
getUpdateStatus
|
|
7
|
+
} = require('../core/update-notifier');
|
|
8
|
+
|
|
9
|
+
function printStatus(status) {
|
|
10
|
+
console.log(chalk.cyan(`\nCurrent version: ${status.currentVersion}`));
|
|
11
|
+
console.log(chalk.cyan(`Latest version: ${status.latestVersion}`));
|
|
12
|
+
|
|
13
|
+
if (status.comparison === 0) {
|
|
14
|
+
console.log(chalk.green('\n✓ You are already on the latest version!'));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (status.comparison > 0) {
|
|
19
|
+
console.log(chalk.green('\n✓ You are on a newer version than npm latest.'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log(chalk.yellow(`\n⬆ Update available: ${status.currentVersion} → ${status.latestVersion}`));
|
|
24
|
+
console.log(chalk.cyan('\nRun the following command to update:'));
|
|
25
|
+
console.log(chalk.white(` ${status.installCommand}`));
|
|
26
|
+
console.log(chalk.gray(' Or run `flowmind update` for the guided upgrade flow.'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function main() {
|
|
30
|
+
try {
|
|
31
|
+
const status = getUpdateStatus({
|
|
32
|
+
packageName: packageJson.name,
|
|
33
|
+
currentVersion: packageJson.version,
|
|
34
|
+
packageJsonPath: require.resolve('../package.json')
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
printStatus(status);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error(chalk.red('Failed to check for updates:'), error.message);
|
|
40
|
+
console.log(chalk.yellow('\nYou can manually update with:'));
|
|
41
|
+
console.log(chalk.white(` npm install -g ${packageJson.name}@latest`));
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (require.main === module) {
|
|
47
|
+
main();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
main
|
|
52
|
+
};
|