@meltstudio/meltctl 4.18.0 → 4.20.0
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.md +7 -0
- package/dist/commands/coins.d.ts +5 -0
- package/dist/commands/coins.js +49 -0
- package/dist/commands/feedback.d.ts +7 -0
- package/dist/commands/feedback.js +99 -0
- package/dist/index.js +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,13 @@ meltctl project templates
|
|
|
31
31
|
# Submit daily standup report
|
|
32
32
|
meltctl standup
|
|
33
33
|
|
|
34
|
+
# Send feedback and coins to a teammate
|
|
35
|
+
meltctl feedback
|
|
36
|
+
|
|
37
|
+
# Check your coin balance or team leaderboard
|
|
38
|
+
meltctl coins
|
|
39
|
+
meltctl coins --leaderboard
|
|
40
|
+
|
|
34
41
|
# Other commands
|
|
35
42
|
meltctl logout
|
|
36
43
|
meltctl version --check
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { isAuthenticated, authenticatedFetch } from '../utils/auth.js';
|
|
3
|
+
export async function coinsCommand(options) {
|
|
4
|
+
if (!(await isAuthenticated())) {
|
|
5
|
+
console.error(chalk.red('Not authenticated. Run `npx @meltstudio/meltctl@latest login` first.'));
|
|
6
|
+
process.exit(1);
|
|
7
|
+
}
|
|
8
|
+
if (options.leaderboard) {
|
|
9
|
+
await showLeaderboard();
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
await showBalance();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function showBalance() {
|
|
16
|
+
const res = await authenticatedFetch('/coins');
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
const body = (await res.json());
|
|
19
|
+
console.error(chalk.red(`Failed to fetch coins: ${body.error ?? res.statusText}`));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const data = (await res.json());
|
|
23
|
+
console.log(chalk.bold.cyan('\n Your Coins (last 28 days)'));
|
|
24
|
+
console.log(` ${chalk.bold(String(data.coins))} coin${data.coins !== 1 ? 's' : ''} received\n`);
|
|
25
|
+
}
|
|
26
|
+
async function showLeaderboard() {
|
|
27
|
+
const res = await authenticatedFetch('/coins/leaderboard');
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
const body = (await res.json());
|
|
30
|
+
console.error(chalk.red(`Failed to fetch leaderboard: ${body.error ?? res.statusText}`));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
const entries = (await res.json());
|
|
34
|
+
if (entries.length === 0) {
|
|
35
|
+
console.log(chalk.yellow('\n No coins have been sent in the last 28 days.\n'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
console.log(chalk.bold.cyan('\n Leaderboard (last 28 days)\n'));
|
|
39
|
+
// Calculate column widths
|
|
40
|
+
const maxNameLen = Math.max(...entries.map(e => e.name.length), 4);
|
|
41
|
+
console.log(chalk.dim(` ${'#'.padEnd(4)} ${'Name'.padEnd(maxNameLen)} Coins`));
|
|
42
|
+
entries.forEach((entry, i) => {
|
|
43
|
+
const rank = String(i + 1).padEnd(4);
|
|
44
|
+
const name = entry.name.padEnd(maxNameLen);
|
|
45
|
+
const coins = String(entry.coins);
|
|
46
|
+
console.log(` ${rank} ${name} ${coins}`);
|
|
47
|
+
});
|
|
48
|
+
console.log();
|
|
49
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { input, select, editor } from '@inquirer/prompts';
|
|
3
|
+
import { isAuthenticated, authenticatedFetch } from '../utils/auth.js';
|
|
4
|
+
const EDITOR_HINT = chalk.dim('(type \\\\e to open your editor)');
|
|
5
|
+
async function promptDescription() {
|
|
6
|
+
const value = await input({
|
|
7
|
+
message: `Write your feedback (50–500 chars): ${EDITOR_HINT}`,
|
|
8
|
+
});
|
|
9
|
+
if (value === '\\e') {
|
|
10
|
+
try {
|
|
11
|
+
const editorValue = await editor({ message: 'Write your feedback:' });
|
|
12
|
+
return validateDescription(editorValue);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
console.log(chalk.yellow('Editor failed to open. Falling back to inline input.'));
|
|
16
|
+
return promptDescription();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return validateDescription(value);
|
|
20
|
+
}
|
|
21
|
+
function validateDescription(value) {
|
|
22
|
+
const trimmed = value.trim();
|
|
23
|
+
if (trimmed.length < 50) {
|
|
24
|
+
console.log(chalk.yellow(`Too short (${trimmed.length}/50 min). Please write more.`));
|
|
25
|
+
return promptDescription();
|
|
26
|
+
}
|
|
27
|
+
if (trimmed.length > 500) {
|
|
28
|
+
console.log(chalk.yellow(`Too long (${trimmed.length}/500 max). Please shorten it.`));
|
|
29
|
+
return promptDescription();
|
|
30
|
+
}
|
|
31
|
+
return trimmed;
|
|
32
|
+
}
|
|
33
|
+
export async function feedbackCommand(options) {
|
|
34
|
+
if (!(await isAuthenticated())) {
|
|
35
|
+
console.error(chalk.red('Not authenticated. Run `npx @meltstudio/meltctl@latest login` first.'));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
let toUserId;
|
|
39
|
+
let coins;
|
|
40
|
+
let description;
|
|
41
|
+
if (options.to && options.coins && options.description) {
|
|
42
|
+
// Hidden flags for E2E testing
|
|
43
|
+
toUserId = parseInt(options.to, 10);
|
|
44
|
+
coins = parseInt(options.coins, 10);
|
|
45
|
+
description = options.description;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
console.log(chalk.bold.cyan('\n Send Feedback\n'));
|
|
49
|
+
// Fetch recipients
|
|
50
|
+
let recipients;
|
|
51
|
+
try {
|
|
52
|
+
const res = await authenticatedFetch('/feedback/recipients');
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
const body = (await res.json());
|
|
55
|
+
console.error(chalk.red(`Failed to load recipients: ${body.error ?? res.statusText}`));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
recipients = (await res.json());
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
console.error(chalk.red('Failed to connect to the server.'));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
if (recipients.length === 0) {
|
|
65
|
+
console.log(chalk.yellow('No recipients available.'));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const selectedRecipient = await select({
|
|
69
|
+
message: 'Who would you like to recognize?',
|
|
70
|
+
choices: recipients.map(r => ({
|
|
71
|
+
name: `${r.firstName} ${r.lastName} (${r.email})`,
|
|
72
|
+
value: r.id,
|
|
73
|
+
})),
|
|
74
|
+
});
|
|
75
|
+
toUserId = selectedRecipient;
|
|
76
|
+
coins = await select({
|
|
77
|
+
message: 'How many coins?',
|
|
78
|
+
choices: [
|
|
79
|
+
{ name: '1 coin', value: 1 },
|
|
80
|
+
{ name: '2 coins', value: 2 },
|
|
81
|
+
{ name: '3 coins', value: 3 },
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
description = await promptDescription();
|
|
85
|
+
}
|
|
86
|
+
const res = await authenticatedFetch('/feedback', {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: { 'Content-Type': 'application/json' },
|
|
89
|
+
body: JSON.stringify({ toUserId, coins, description }),
|
|
90
|
+
});
|
|
91
|
+
if (res.ok) {
|
|
92
|
+
console.log(chalk.green(`\n ✓ Feedback sent! You gave ${coins} coin${coins > 1 ? 's' : ''}.\n`));
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const body = (await res.json());
|
|
96
|
+
console.error(chalk.red(`\nFailed to send feedback: ${body.error ?? res.statusText}`));
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,8 @@ import { printBanner } from './utils/banner.js';
|
|
|
12
12
|
import { checkAndEnforceUpdate } from './utils/version-check.js';
|
|
13
13
|
import { versionCheckCommand } from './commands/version.js';
|
|
14
14
|
import { standupCommand } from './commands/standup.js';
|
|
15
|
+
import { feedbackCommand } from './commands/feedback.js';
|
|
16
|
+
import { coinsCommand } from './commands/coins.js';
|
|
15
17
|
// Read version from package.json
|
|
16
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
19
|
const __dirname = dirname(__filename);
|
|
@@ -65,6 +67,22 @@ program
|
|
|
65
67
|
.action(async (options) => {
|
|
66
68
|
await standupCommand(options);
|
|
67
69
|
});
|
|
70
|
+
program
|
|
71
|
+
.command('feedback')
|
|
72
|
+
.description('send feedback and coins to a teammate')
|
|
73
|
+
.addOption(new Option('--to <userId>').hideHelp())
|
|
74
|
+
.addOption(new Option('--coins <amount>').hideHelp())
|
|
75
|
+
.addOption(new Option('--description <text>').hideHelp())
|
|
76
|
+
.action(async (options) => {
|
|
77
|
+
await feedbackCommand(options);
|
|
78
|
+
});
|
|
79
|
+
program
|
|
80
|
+
.command('coins')
|
|
81
|
+
.description('check your coin balance or the team leaderboard')
|
|
82
|
+
.option('--leaderboard', 'show the team leaderboard')
|
|
83
|
+
.action(async (options) => {
|
|
84
|
+
await coinsCommand(options);
|
|
85
|
+
});
|
|
68
86
|
program
|
|
69
87
|
.command('version')
|
|
70
88
|
.description('show current version')
|
package/package.json
CHANGED