mattermost-claude-code 0.3.4 → 0.4.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/dist/config.d.ts +12 -1
- package/dist/config.js +35 -22
- package/dist/index.js +37 -10
- package/dist/onboarding.d.ts +1 -0
- package/dist/onboarding.js +117 -0
- package/package.json +4 -1
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/** Check if any .env config file exists */
|
|
2
|
+
export declare function configExists(): boolean;
|
|
3
|
+
/** CLI arguments that can override config */
|
|
4
|
+
export interface CliArgs {
|
|
5
|
+
url?: string;
|
|
6
|
+
token?: string;
|
|
7
|
+
channel?: string;
|
|
8
|
+
botName?: string;
|
|
9
|
+
allowedUsers?: string;
|
|
10
|
+
skipPermissions?: boolean;
|
|
11
|
+
}
|
|
1
12
|
export interface Config {
|
|
2
13
|
mattermost: {
|
|
3
14
|
url: string;
|
|
@@ -8,4 +19,4 @@ export interface Config {
|
|
|
8
19
|
allowedUsers: string[];
|
|
9
20
|
skipPermissions: boolean;
|
|
10
21
|
}
|
|
11
|
-
export declare function loadConfig(): Config;
|
|
22
|
+
export declare function loadConfig(cliArgs?: CliArgs): Config;
|
package/dist/config.js
CHANGED
|
@@ -3,17 +3,17 @@ import { resolve } from 'path';
|
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
let envLoaded = false;
|
|
6
|
+
// Paths to search for .env files (in order of priority)
|
|
7
|
+
const ENV_PATHS = [
|
|
8
|
+
resolve(process.cwd(), '.env'), // Current directory
|
|
9
|
+
resolve(homedir(), '.config', 'mm-claude', '.env'), // ~/.config/mm-claude/.env
|
|
10
|
+
resolve(homedir(), '.mm-claude.env'), // ~/.mm-claude.env
|
|
11
|
+
];
|
|
6
12
|
function loadEnv() {
|
|
7
13
|
if (envLoaded)
|
|
8
14
|
return;
|
|
9
15
|
envLoaded = true;
|
|
10
|
-
|
|
11
|
-
const envPaths = [
|
|
12
|
-
resolve(process.cwd(), '.env'), // Current directory
|
|
13
|
-
resolve(homedir(), '.config', 'mm-claude', '.env'), // ~/.config/mm-claude/.env
|
|
14
|
-
resolve(homedir(), '.mm-claude.env'), // ~/.mm-claude.env
|
|
15
|
-
];
|
|
16
|
-
for (const envPath of envPaths) {
|
|
16
|
+
for (const envPath of ENV_PATHS) {
|
|
17
17
|
if (existsSync(envPath)) {
|
|
18
18
|
if (process.env.DEBUG === '1' || process.argv.includes('--debug')) {
|
|
19
19
|
console.log(` [config] Loading from: ${envPath}`);
|
|
@@ -23,28 +23,41 @@ function loadEnv() {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
/** Check if any .env config file exists */
|
|
27
|
+
export function configExists() {
|
|
28
|
+
return ENV_PATHS.some(p => existsSync(p));
|
|
29
|
+
}
|
|
30
|
+
function getRequired(cliValue, envName, name) {
|
|
31
|
+
const value = cliValue || process.env[envName];
|
|
28
32
|
if (!value) {
|
|
29
|
-
throw new Error(`Missing required
|
|
33
|
+
throw new Error(`Missing required config: ${name}. Set ${envName} in .env or use --${name.toLowerCase().replace(/ /g, '-')} flag.`);
|
|
30
34
|
}
|
|
31
35
|
return value;
|
|
32
36
|
}
|
|
33
|
-
export function loadConfig() {
|
|
37
|
+
export function loadConfig(cliArgs) {
|
|
34
38
|
loadEnv();
|
|
39
|
+
// CLI args take priority over env vars
|
|
40
|
+
const url = getRequired(cliArgs?.url, 'MATTERMOST_URL', 'url');
|
|
41
|
+
const token = getRequired(cliArgs?.token, 'MATTERMOST_TOKEN', 'token');
|
|
42
|
+
const channelId = getRequired(cliArgs?.channel, 'MATTERMOST_CHANNEL_ID', 'channel');
|
|
43
|
+
const botName = cliArgs?.botName || process.env.MATTERMOST_BOT_NAME || 'claude-code';
|
|
44
|
+
const allowedUsersStr = cliArgs?.allowedUsers || process.env.ALLOWED_USERS || '';
|
|
45
|
+
const allowedUsers = allowedUsersStr
|
|
46
|
+
.split(',')
|
|
47
|
+
.map(u => u.trim())
|
|
48
|
+
.filter(u => u.length > 0);
|
|
49
|
+
// CLI --skip-permissions, env SKIP_PERMISSIONS=true, or legacy flag
|
|
50
|
+
const skipPermissions = cliArgs?.skipPermissions ||
|
|
51
|
+
process.env.SKIP_PERMISSIONS === 'true' ||
|
|
52
|
+
process.argv.includes('--dangerously-skip-permissions');
|
|
35
53
|
return {
|
|
36
54
|
mattermost: {
|
|
37
|
-
url:
|
|
38
|
-
token
|
|
39
|
-
channelId
|
|
40
|
-
botName
|
|
55
|
+
url: url.replace(/\/$/, ''), // Remove trailing slash
|
|
56
|
+
token,
|
|
57
|
+
channelId,
|
|
58
|
+
botName,
|
|
41
59
|
},
|
|
42
|
-
allowedUsers
|
|
43
|
-
|
|
44
|
-
.map(u => u.trim())
|
|
45
|
-
.filter(u => u.length > 0),
|
|
46
|
-
// SKIP_PERMISSIONS=true or --dangerously-skip-permissions flag
|
|
47
|
-
skipPermissions: process.env.SKIP_PERMISSIONS === 'true' ||
|
|
48
|
-
process.argv.includes('--dangerously-skip-permissions'),
|
|
60
|
+
allowedUsers,
|
|
61
|
+
skipPermissions,
|
|
49
62
|
};
|
|
50
63
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import { loadConfig, configExists } from './config.js';
|
|
4
|
+
import { runOnboarding } from './onboarding.js';
|
|
3
5
|
import { MattermostClient } from './mattermost/client.js';
|
|
4
6
|
import { SessionManager } from './claude/session.js';
|
|
5
7
|
import { readFileSync } from 'fs';
|
|
@@ -10,19 +12,44 @@ const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'u
|
|
|
10
12
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
11
13
|
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
12
14
|
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
15
|
+
// Define CLI options
|
|
16
|
+
program
|
|
17
|
+
.name('mm-claude')
|
|
18
|
+
.version(pkg.version)
|
|
19
|
+
.description('Share Claude Code sessions in Mattermost')
|
|
20
|
+
.option('--url <url>', 'Mattermost server URL')
|
|
21
|
+
.option('--token <token>', 'Mattermost bot token')
|
|
22
|
+
.option('--channel <id>', 'Mattermost channel ID')
|
|
23
|
+
.option('--bot-name <name>', 'Bot mention name (default: claude-code)')
|
|
24
|
+
.option('--allowed-users <users>', 'Comma-separated allowed usernames')
|
|
25
|
+
.option('--skip-permissions', 'Skip interactive permission prompts')
|
|
26
|
+
.option('--debug', 'Enable debug logging')
|
|
27
|
+
.parse();
|
|
28
|
+
const opts = program.opts();
|
|
29
|
+
// Check if required args are provided via CLI
|
|
30
|
+
function hasRequiredCliArgs(args) {
|
|
31
|
+
return !!(args.url && args.token && args.channel);
|
|
32
|
+
}
|
|
13
33
|
async function main() {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
process.
|
|
34
|
+
// Set debug mode from CLI flag
|
|
35
|
+
if (opts.debug) {
|
|
36
|
+
process.env.DEBUG = '1';
|
|
17
37
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
38
|
+
// Build CLI args object
|
|
39
|
+
const cliArgs = {
|
|
40
|
+
url: opts.url,
|
|
41
|
+
token: opts.token,
|
|
42
|
+
channel: opts.channel,
|
|
43
|
+
botName: opts.botName,
|
|
44
|
+
allowedUsers: opts.allowedUsers,
|
|
45
|
+
skipPermissions: opts.skipPermissions,
|
|
46
|
+
};
|
|
47
|
+
// Check if we need onboarding
|
|
48
|
+
if (!configExists() && !hasRequiredCliArgs(opts)) {
|
|
49
|
+
await runOnboarding();
|
|
23
50
|
}
|
|
24
51
|
const workingDir = process.cwd();
|
|
25
|
-
const config = loadConfig();
|
|
52
|
+
const config = loadConfig(cliArgs);
|
|
26
53
|
// Nice startup banner
|
|
27
54
|
console.log('');
|
|
28
55
|
console.log(bold(` 🤖 mm-claude v${pkg.version}`));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runOnboarding(): Promise<void>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import { writeFileSync, mkdirSync } from 'fs';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { resolve } from 'path';
|
|
5
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
6
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
7
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
8
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
9
|
+
export async function runOnboarding() {
|
|
10
|
+
console.log('');
|
|
11
|
+
console.log(bold(' Welcome to mm-claude!'));
|
|
12
|
+
console.log(dim(' ─────────────────────────────────'));
|
|
13
|
+
console.log('');
|
|
14
|
+
console.log(' No configuration found. Let\'s set things up.');
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log(dim(' You\'ll need:'));
|
|
17
|
+
console.log(dim(' • A Mattermost bot account with a token'));
|
|
18
|
+
console.log(dim(' • A channel ID where the bot will listen'));
|
|
19
|
+
console.log('');
|
|
20
|
+
// Handle Ctrl+C gracefully
|
|
21
|
+
prompts.override({});
|
|
22
|
+
const onCancel = () => {
|
|
23
|
+
console.log('');
|
|
24
|
+
console.log(dim(' Setup cancelled.'));
|
|
25
|
+
process.exit(0);
|
|
26
|
+
};
|
|
27
|
+
const response = await prompts([
|
|
28
|
+
{
|
|
29
|
+
type: 'text',
|
|
30
|
+
name: 'url',
|
|
31
|
+
message: 'Mattermost URL',
|
|
32
|
+
initial: 'https://your-mattermost-server.com',
|
|
33
|
+
validate: (v) => v.startsWith('http') ? true : 'URL must start with http:// or https://',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: 'password',
|
|
37
|
+
name: 'token',
|
|
38
|
+
message: 'Bot token',
|
|
39
|
+
hint: 'Create at: Integrations > Bot Accounts > Add Bot Account',
|
|
40
|
+
validate: (v) => v.length > 0 ? true : 'Token is required',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'text',
|
|
44
|
+
name: 'channelId',
|
|
45
|
+
message: 'Channel ID',
|
|
46
|
+
hint: 'Click channel name > View Info > copy ID from URL',
|
|
47
|
+
validate: (v) => v.length > 0 ? true : 'Channel ID is required',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'text',
|
|
51
|
+
name: 'botName',
|
|
52
|
+
message: 'Bot mention name',
|
|
53
|
+
initial: 'claude-code',
|
|
54
|
+
hint: 'Users will @mention this name',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'text',
|
|
58
|
+
name: 'allowedUsers',
|
|
59
|
+
message: 'Allowed usernames',
|
|
60
|
+
initial: '',
|
|
61
|
+
hint: 'Comma-separated, or empty for all users',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
type: 'confirm',
|
|
65
|
+
name: 'skipPermissions',
|
|
66
|
+
message: 'Skip permission prompts?',
|
|
67
|
+
initial: true,
|
|
68
|
+
hint: 'If no, you\'ll approve each action via emoji reactions',
|
|
69
|
+
},
|
|
70
|
+
], { onCancel });
|
|
71
|
+
// Check if user cancelled
|
|
72
|
+
if (!response.url || !response.token || !response.channelId) {
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log(dim(' Setup incomplete. Run mm-claude again to retry.'));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
// Build .env content
|
|
78
|
+
const envContent = `# mm-claude configuration
|
|
79
|
+
# Generated by mm-claude onboarding
|
|
80
|
+
|
|
81
|
+
# Mattermost server URL
|
|
82
|
+
MATTERMOST_URL=${response.url}
|
|
83
|
+
|
|
84
|
+
# Bot token (from Integrations > Bot Accounts)
|
|
85
|
+
MATTERMOST_TOKEN=${response.token}
|
|
86
|
+
|
|
87
|
+
# Channel ID where the bot listens
|
|
88
|
+
MATTERMOST_CHANNEL_ID=${response.channelId}
|
|
89
|
+
|
|
90
|
+
# Bot mention name (users @mention this)
|
|
91
|
+
MATTERMOST_BOT_NAME=${response.botName || 'claude-code'}
|
|
92
|
+
|
|
93
|
+
# Allowed usernames (comma-separated, empty = all users)
|
|
94
|
+
ALLOWED_USERS=${response.allowedUsers || ''}
|
|
95
|
+
|
|
96
|
+
# Skip permission prompts (true = auto-approve, false = require emoji approval)
|
|
97
|
+
SKIP_PERMISSIONS=${response.skipPermissions ? 'true' : 'false'}
|
|
98
|
+
`;
|
|
99
|
+
// Save to ~/.config/mm-claude/.env
|
|
100
|
+
const configDir = resolve(homedir(), '.config', 'mm-claude');
|
|
101
|
+
const envPath = resolve(configDir, '.env');
|
|
102
|
+
try {
|
|
103
|
+
mkdirSync(configDir, { recursive: true });
|
|
104
|
+
writeFileSync(envPath, envContent, { mode: 0o600 }); // Secure permissions
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.error('');
|
|
108
|
+
console.error(` Failed to save config: ${err}`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(green(' ✓ Configuration saved!'));
|
|
113
|
+
console.log(dim(` ${envPath}`));
|
|
114
|
+
console.log('');
|
|
115
|
+
console.log(dim(' Starting mm-claude...'));
|
|
116
|
+
console.log('');
|
|
117
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mattermost-claude-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Share Claude Code sessions live in a Mattermost channel with interactive features",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -41,12 +41,15 @@
|
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
44
|
+
"commander": "^14.0.2",
|
|
44
45
|
"dotenv": "^16.4.7",
|
|
46
|
+
"prompts": "^2.4.2",
|
|
45
47
|
"ws": "^8.18.0",
|
|
46
48
|
"zod": "^4.2.1"
|
|
47
49
|
},
|
|
48
50
|
"devDependencies": {
|
|
49
51
|
"@types/node": "^22.10.2",
|
|
52
|
+
"@types/prompts": "^2.4.9",
|
|
50
53
|
"@types/ws": "^8.5.13",
|
|
51
54
|
"tsx": "^4.19.2",
|
|
52
55
|
"typescript": "^5.7.2"
|