flowmind 1.5.4 → 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 +17 -1
- package/README.md +2 -0
- package/README_CN.md +2 -0
- package/bin/flowmind.js +19 -110
- package/core/cli-ink.js +79 -0
- package/core/log-query-parser.js +324 -0
- 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 +58 -82
- package/skills/log-audit/index.js +55 -27
- package/skills/sls-log-audit/index.js +7 -30
- package/skills/yuque-sync-design/index.js +2 -2
- package/tui/app.jsx +73 -5
- package/tui/components/ChatPanel.jsx +9 -6
- 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
|
+
};
|
|
@@ -669,34 +669,8 @@ async function executeDeploy(workflow, params, input, context) {
|
|
|
669
669
|
};
|
|
670
670
|
}
|
|
671
671
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
const batchParams = buildBatchRunParams(params);
|
|
675
|
-
const execution = await workflow.client.startBatchPipelineRun(batchParams);
|
|
676
|
-
|
|
677
|
-
return {
|
|
678
|
-
type: 'result',
|
|
679
|
-
skill: 'auto-flow',
|
|
680
|
-
message: 'Workflow status: deployment submitted',
|
|
681
|
-
data: {
|
|
682
|
-
action: 'deploy',
|
|
683
|
-
provider: workflow.provider,
|
|
684
|
-
binding: workflow.binding,
|
|
685
|
-
status: 'submitted',
|
|
686
|
-
mcpServer: getWorkflowMcpServer(workflow),
|
|
687
|
-
execution: summarizeDeployExecution(params, execution, {
|
|
688
|
-
source: 'batch-fallback',
|
|
689
|
-
lookup
|
|
690
|
-
}),
|
|
691
|
-
resolution: {
|
|
692
|
-
source: 'batch-fallback',
|
|
693
|
-
lookup
|
|
694
|
-
}
|
|
695
|
-
},
|
|
696
|
-
input,
|
|
697
|
-
timestamp: new Date().toISOString()
|
|
698
|
-
};
|
|
699
|
-
}
|
|
672
|
+
return buildUnresolvedDeployResult(workflow, params, input, context);
|
|
673
|
+
}
|
|
700
674
|
|
|
701
675
|
async function executeStatus(workflow, params, input) {
|
|
702
676
|
let execution;
|
|
@@ -894,7 +868,7 @@ function resolveLocalPipelineForService(serviceName, environment, context) {
|
|
|
894
868
|
const entry = pipelineMap[serviceName];
|
|
895
869
|
if (!entry) return null;
|
|
896
870
|
|
|
897
|
-
const pipeline = selectPipelineFromEntry(entry, environment);
|
|
871
|
+
const pipeline = selectPipelineFromEntry(entry, environment, serviceName);
|
|
898
872
|
if (!pipeline?.pipelineId) return null;
|
|
899
873
|
|
|
900
874
|
return {
|
|
@@ -923,19 +897,13 @@ function readLocalPipelineMap(context) {
|
|
|
923
897
|
return null;
|
|
924
898
|
}
|
|
925
899
|
|
|
926
|
-
function selectPipelineFromEntry(entry, environment) {
|
|
900
|
+
function selectPipelineFromEntry(entry, environment, serviceName) {
|
|
927
901
|
const preferredKeys = getEnvLookupOrder(environment);
|
|
928
902
|
for (const key of preferredKeys) {
|
|
929
903
|
const candidate = normalizePipelineCollection(entry?.[key]);
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
for (const value of Object.values(entry || {})) {
|
|
936
|
-
const candidate = normalizePipelineCollection(value);
|
|
937
|
-
if (candidate.length > 0) {
|
|
938
|
-
return candidate[0];
|
|
904
|
+
const matched = candidate.find((item) => matchesPipelineName(item.pipelineName, environment, serviceName));
|
|
905
|
+
if (matched) {
|
|
906
|
+
return matched;
|
|
939
907
|
}
|
|
940
908
|
}
|
|
941
909
|
|
|
@@ -1094,49 +1062,12 @@ function matchesRemoteService(fields, serviceName, environment) {
|
|
|
1094
1062
|
}
|
|
1095
1063
|
|
|
1096
1064
|
function selectPipelineFromFields(fields, environment, serviceName) {
|
|
1097
|
-
const indexedCandidate = selectIndexedPipelineForService(fields, environment, serviceName);
|
|
1098
|
-
if (indexedCandidate?.pipelineId) {
|
|
1099
|
-
return indexedCandidate;
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
1065
|
const candidates = collectFieldPipelineDescriptors(fields, environment);
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
const pipelineIds = String(fields.pipelineId || '')
|
|
1108
|
-
.split(',')
|
|
1109
|
-
.map((item) => item.trim())
|
|
1110
|
-
.filter(Boolean);
|
|
1111
|
-
|
|
1112
|
-
if (pipelineIds.length === 0) {
|
|
1113
|
-
return null;
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
return {
|
|
1117
|
-
pipelineName: buildPrefixedPipelineName(
|
|
1118
|
-
Array.isArray(fields.service) ? fields.service[0] : null,
|
|
1119
|
-
environment
|
|
1120
|
-
),
|
|
1121
|
-
pipelineId: pipelineIds[0]
|
|
1122
|
-
};
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
function selectIndexedPipelineForService(fields, environment, serviceName) {
|
|
1126
|
-
if (!serviceName) return null;
|
|
1127
|
-
|
|
1128
|
-
const services = Array.isArray(fields.service) ? fields.service.map((item) => String(item).trim()) : [];
|
|
1129
|
-
const serviceIndex = services.indexOf(serviceName);
|
|
1130
|
-
if (serviceIndex < 0) return null;
|
|
1131
|
-
|
|
1132
|
-
for (const key of getFieldKeysForEnvironment(environment)) {
|
|
1133
|
-
const values = Array.isArray(fields[key]) ? fields[key] : [];
|
|
1134
|
-
const parsed = parsePipelineDescriptor(values[serviceIndex]);
|
|
1135
|
-
if (parsed?.pipelineId) {
|
|
1136
|
-
return parsed;
|
|
1066
|
+
for (const candidate of candidates) {
|
|
1067
|
+
if (candidate?.pipelineId && matchesPipelineName(candidate.pipelineName, environment, serviceName)) {
|
|
1068
|
+
return candidate;
|
|
1137
1069
|
}
|
|
1138
1070
|
}
|
|
1139
|
-
|
|
1140
1071
|
return null;
|
|
1141
1072
|
}
|
|
1142
1073
|
|
|
@@ -1177,10 +1108,11 @@ function parsePipelineDescriptor(value) {
|
|
|
1177
1108
|
function getEnvLookupOrder(environment) {
|
|
1178
1109
|
const normalized = normalizeEnvironment(environment || '');
|
|
1179
1110
|
if (normalized === 'prod') return ['prod'];
|
|
1180
|
-
if (normalized === 'gray') return ['gray'
|
|
1111
|
+
if (normalized === 'gray') return ['gray'];
|
|
1181
1112
|
if (normalized === 'uat') return ['uat'];
|
|
1182
|
-
if (normalized === 'test') return ['test', 'dev'
|
|
1183
|
-
|
|
1113
|
+
if (normalized === 'test') return ['test', 'dev'];
|
|
1114
|
+
if (normalized === 'dev') return ['dev'];
|
|
1115
|
+
return [];
|
|
1184
1116
|
}
|
|
1185
1117
|
|
|
1186
1118
|
function buildPrefixedPipelineName(serviceName, environment) {
|
|
@@ -1191,6 +1123,24 @@ function buildPrefixedPipelineName(serviceName, environment) {
|
|
|
1191
1123
|
return `${prefix}-${serviceName}`;
|
|
1192
1124
|
}
|
|
1193
1125
|
|
|
1126
|
+
function matchesPipelineName(pipelineName, environment, serviceName) {
|
|
1127
|
+
if (!pipelineName || !serviceName) return false;
|
|
1128
|
+
|
|
1129
|
+
const normalized = String(pipelineName).toLowerCase();
|
|
1130
|
+
const candidatePrefixes = getAllowedPipelinePrefixes(environment);
|
|
1131
|
+
return candidatePrefixes.some((prefix) => normalized === `${prefix}-${serviceName}`.toLowerCase());
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function getAllowedPipelinePrefixes(environment) {
|
|
1135
|
+
const normalized = normalizeEnvironment(environment || '');
|
|
1136
|
+
if (normalized === 'test') return ['test', 'dev'];
|
|
1137
|
+
if (normalized === 'dev') return ['dev'];
|
|
1138
|
+
if (normalized === 'uat') return ['uat'];
|
|
1139
|
+
if (normalized === 'gray') return ['gray'];
|
|
1140
|
+
if (normalized === 'prod') return ['prod'];
|
|
1141
|
+
return [];
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1194
1144
|
function inferEnvironmentFromPipelineName(pipelineName) {
|
|
1195
1145
|
const match = String(pipelineName || '').match(/^(test|dev|uat|gray|prod)-/i);
|
|
1196
1146
|
return match ? (ENV_PREFIX_TO_ENV[match[1].toLowerCase()] || normalizeEnvironment(match[1])) : null;
|
|
@@ -1219,6 +1169,32 @@ function collectNestedValues(payload, values = []) {
|
|
|
1219
1169
|
return values;
|
|
1220
1170
|
}
|
|
1221
1171
|
|
|
1172
|
+
function buildUnresolvedDeployResult(workflow, params, input, context) {
|
|
1173
|
+
const target = buildWorkflowTargetLabel(params) || buildDeployTargetLabel(context) || 'deployment';
|
|
1174
|
+
return {
|
|
1175
|
+
type: 'error',
|
|
1176
|
+
success: false,
|
|
1177
|
+
skill: 'auto-flow',
|
|
1178
|
+
message: `Unable to resolve deployment target for ${target}. Specify an exact environment or pipelineId.`,
|
|
1179
|
+
data: {
|
|
1180
|
+
action: 'deploy',
|
|
1181
|
+
provider: workflow.provider,
|
|
1182
|
+
binding: workflow.binding,
|
|
1183
|
+
status: 'not_resolved',
|
|
1184
|
+
mcpServer: getWorkflowMcpServer(workflow),
|
|
1185
|
+
target,
|
|
1186
|
+
environment: params.environment || null,
|
|
1187
|
+
resolution: {
|
|
1188
|
+
source: 'not-resolved',
|
|
1189
|
+
serviceNames: params.serviceNames || [],
|
|
1190
|
+
environment: params.environment || null
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
input,
|
|
1194
|
+
timestamp: new Date().toISOString()
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1222
1198
|
function compactObject(value) {
|
|
1223
1199
|
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== null && item !== ''));
|
|
1224
1200
|
}
|