claude-threads 0.13.0 → 0.14.1
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 +32 -0
- package/README.md +78 -28
- package/dist/claude/cli.d.ts +8 -0
- package/dist/claude/cli.js +16 -8
- package/dist/config/migration.d.ts +45 -0
- package/dist/config/migration.js +35 -0
- package/dist/config.d.ts +12 -18
- package/dist/config.js +7 -94
- package/dist/git/worktree.d.ts +0 -4
- package/dist/git/worktree.js +1 -1
- package/dist/index.js +31 -13
- package/dist/logo.d.ts +3 -20
- package/dist/logo.js +7 -23
- package/dist/mcp/permission-server.js +61 -112
- package/dist/onboarding.js +262 -137
- package/dist/persistence/session-store.d.ts +8 -2
- package/dist/persistence/session-store.js +41 -16
- package/dist/platform/client.d.ts +140 -0
- package/dist/platform/formatter.d.ts +74 -0
- package/dist/platform/index.d.ts +11 -0
- package/dist/platform/index.js +8 -0
- package/dist/platform/mattermost/client.d.ts +70 -0
- package/dist/{mattermost → platform/mattermost}/client.js +117 -34
- package/dist/platform/mattermost/formatter.d.ts +20 -0
- package/dist/platform/mattermost/formatter.js +46 -0
- package/dist/platform/mattermost/permission-api.d.ts +10 -0
- package/dist/platform/mattermost/permission-api.js +139 -0
- package/dist/platform/mattermost/types.js +1 -0
- package/dist/platform/permission-api-factory.d.ts +11 -0
- package/dist/platform/permission-api-factory.js +21 -0
- package/dist/platform/permission-api.d.ts +67 -0
- package/dist/platform/permission-api.js +8 -0
- package/dist/platform/types.d.ts +70 -0
- package/dist/platform/types.js +7 -0
- package/dist/session/commands.d.ts +52 -0
- package/dist/session/commands.js +323 -0
- package/dist/session/events.d.ts +25 -0
- package/dist/session/events.js +368 -0
- package/dist/session/index.d.ts +7 -0
- package/dist/session/index.js +6 -0
- package/dist/session/lifecycle.d.ts +70 -0
- package/dist/session/lifecycle.js +456 -0
- package/dist/session/manager.d.ts +96 -0
- package/dist/session/manager.js +537 -0
- package/dist/session/reactions.d.ts +25 -0
- package/dist/session/reactions.js +151 -0
- package/dist/session/streaming.d.ts +47 -0
- package/dist/session/streaming.js +152 -0
- package/dist/session/types.d.ts +78 -0
- package/dist/session/types.js +9 -0
- package/dist/session/worktree.d.ts +56 -0
- package/dist/session/worktree.js +339 -0
- package/dist/update-notifier.js +10 -0
- package/dist/{mattermost → utils}/emoji.d.ts +3 -3
- package/dist/{mattermost → utils}/emoji.js +3 -3
- package/dist/utils/emoji.test.d.ts +1 -0
- package/dist/utils/tool-formatter.d.ts +10 -13
- package/dist/utils/tool-formatter.js +48 -43
- package/dist/utils/tool-formatter.test.js +67 -52
- package/package.json +4 -3
- package/dist/claude/session.d.ts +0 -256
- package/dist/claude/session.js +0 -1964
- package/dist/mattermost/client.d.ts +0 -56
- /package/dist/{mattermost/emoji.test.d.ts → platform/client.js} +0 -0
- /package/dist/{mattermost/types.js → platform/formatter.js} +0 -0
- /package/dist/{mattermost → platform/mattermost}/types.d.ts +0 -0
- /package/dist/{mattermost → utils}/emoji.test.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.14.0] - 2025-12-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Multi-platform architecture** - Foundation for supporting multiple chat platforms
|
|
14
|
+
- New `PlatformClient` interface abstracts platform differences
|
|
15
|
+
- Normalized types: `PlatformPost`, `PlatformUser`, `PlatformReaction`, `PlatformFile`
|
|
16
|
+
- Mattermost implementation moved to `src/platform/mattermost/`
|
|
17
|
+
- Slack support architecture ready (implementation pending)
|
|
18
|
+
- **YAML-based configuration** - New config format
|
|
19
|
+
- Config file: `~/.config/claude-threads/config.yaml`
|
|
20
|
+
- Support for multiple platform instances simultaneously
|
|
21
|
+
- Interactive onboarding wizard creates YAML config
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **Modular session management** - Broke 2,500-line monolith into focused modules
|
|
25
|
+
- `session/manager.ts` (~635 lines) - Thin orchestrator
|
|
26
|
+
- `session/lifecycle.ts` (~590 lines) - Session start/resume/exit
|
|
27
|
+
- `session/events.ts` (~480 lines) - Claude CLI event handling
|
|
28
|
+
- `session/commands.ts` (~510 lines) - User commands
|
|
29
|
+
- `session/reactions.ts` (~210 lines) - Emoji reaction handling
|
|
30
|
+
- `session/worktree.ts` (~520 lines) - Git worktree management
|
|
31
|
+
- `session/streaming.ts` (~180 lines) - Message batching
|
|
32
|
+
- Uses dependency injection for testability
|
|
33
|
+
- **Platform-agnostic utilities** - Moved emoji helpers to `src/utils/emoji.ts`
|
|
34
|
+
- **Cleaner logo exports** - Renamed to generic `getLogo()`, `LOGO`, `LOGO_INLINE`
|
|
35
|
+
|
|
36
|
+
### Removed
|
|
37
|
+
- **Legacy `.env` configuration** - Now uses YAML only (`config.yaml`)
|
|
38
|
+
- **`dotenv` dependency** - No longer needed
|
|
39
|
+
- Deprecated Mattermost-specific exports (`getMattermostLogo`, `MATTERMOST_LOGO`)
|
|
40
|
+
- Internal documentation files (moved to CLAUDE.md)
|
|
41
|
+
|
|
10
42
|
## [0.13.0] - 2025-12-29
|
|
11
43
|
|
|
12
44
|
### Added
|
package/README.md
CHANGED
|
@@ -68,25 +68,41 @@ claude-threads
|
|
|
68
68
|
On first run, an interactive setup wizard guides you through configuration:
|
|
69
69
|
|
|
70
70
|
```
|
|
71
|
-
|
|
71
|
+
claude-threads setup
|
|
72
|
+
─────────────────────────────────
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
Welcome! Let's configure claude-threads.
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
? Default working directory: /home/user/projects
|
|
77
|
+
? Enable Chrome integration? No
|
|
78
|
+
? Git worktree mode: Prompt
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
Now let's add your platform connections.
|
|
81
|
+
|
|
82
|
+
? First platform: Mattermost
|
|
83
|
+
? Platform ID: default
|
|
84
|
+
? Display name: Mattermost
|
|
85
|
+
|
|
86
|
+
Mattermost setup:
|
|
87
|
+
|
|
88
|
+
? Server URL: https://chat.example.com
|
|
80
89
|
? Bot token: ********
|
|
81
90
|
? Channel ID: abc123def456
|
|
82
91
|
? Bot mention name: claude-code
|
|
83
|
-
? Allowed usernames: alice,bob
|
|
84
|
-
?
|
|
92
|
+
? Allowed usernames (optional): alice,bob
|
|
93
|
+
? Auto-approve all actions? No
|
|
94
|
+
|
|
95
|
+
✓ Added Mattermost
|
|
96
|
+
|
|
97
|
+
? Add another platform? No
|
|
85
98
|
|
|
86
|
-
✓ Configuration saved!
|
|
87
|
-
|
|
99
|
+
✓ Configuration saved!
|
|
100
|
+
~/.config/claude-threads/config.yaml
|
|
88
101
|
|
|
89
|
-
|
|
102
|
+
Configured 1 platform(s):
|
|
103
|
+
• Mattermost (mattermost)
|
|
104
|
+
|
|
105
|
+
Starting claude-threads...
|
|
90
106
|
```
|
|
91
107
|
|
|
92
108
|
### 3. Use
|
|
@@ -290,10 +306,13 @@ Stop a running session:
|
|
|
290
306
|
|
|
291
307
|
## Access Control
|
|
292
308
|
|
|
293
|
-
Set `
|
|
309
|
+
Set `allowedUsers` in your platform config to restrict who can use the bot:
|
|
294
310
|
|
|
295
|
-
```
|
|
296
|
-
|
|
311
|
+
```yaml
|
|
312
|
+
platforms:
|
|
313
|
+
- id: mattermost-main
|
|
314
|
+
# ...
|
|
315
|
+
allowedUsers: [alice, bob, carol]
|
|
297
316
|
```
|
|
298
317
|
|
|
299
318
|
- Only listed users can start sessions
|
|
@@ -301,26 +320,57 @@ ALLOWED_USERS=alice,bob,carol
|
|
|
301
320
|
- Session owners can `!invite` others temporarily
|
|
302
321
|
- Empty = anyone can use (be careful!)
|
|
303
322
|
|
|
304
|
-
##
|
|
323
|
+
## Configuration
|
|
324
|
+
|
|
325
|
+
Configuration is stored in YAML format at `~/.config/claude-threads/config.yaml`.
|
|
326
|
+
|
|
327
|
+
### Example Config
|
|
328
|
+
|
|
329
|
+
```yaml
|
|
330
|
+
version: 1
|
|
331
|
+
workingDir: /home/user/repos/myproject
|
|
332
|
+
chrome: false
|
|
333
|
+
worktreeMode: prompt
|
|
334
|
+
|
|
335
|
+
platforms:
|
|
336
|
+
- id: mattermost-main
|
|
337
|
+
type: mattermost
|
|
338
|
+
displayName: Main Team
|
|
339
|
+
url: https://chat.example.com
|
|
340
|
+
token: your-bot-token
|
|
341
|
+
channelId: abc123
|
|
342
|
+
botName: claude-code
|
|
343
|
+
allowedUsers: [alice, bob]
|
|
344
|
+
skipPermissions: false
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Global Settings
|
|
348
|
+
|
|
349
|
+
| Setting | Description |
|
|
350
|
+
|---------|-------------|
|
|
351
|
+
| `workingDir` | Default working directory for Claude |
|
|
352
|
+
| `chrome` | Enable Chrome integration (`true`/`false`) |
|
|
353
|
+
| `worktreeMode` | Git worktree mode: `off`, `prompt`, or `require` |
|
|
354
|
+
|
|
355
|
+
### Platform Settings (Mattermost)
|
|
356
|
+
|
|
357
|
+
| Setting | Description |
|
|
358
|
+
|---------|-------------|
|
|
359
|
+
| `url` | Mattermost server URL |
|
|
360
|
+
| `token` | Bot access token |
|
|
361
|
+
| `channelId` | Channel to listen in |
|
|
362
|
+
| `botName` | Mention name (default: `claude-code`) |
|
|
363
|
+
| `allowedUsers` | List of usernames who can use the bot |
|
|
364
|
+
| `skipPermissions` | Auto-approve actions (`true`/`false`) |
|
|
365
|
+
|
|
366
|
+
### Environment Variables
|
|
305
367
|
|
|
306
368
|
| Variable | Description |
|
|
307
369
|
|----------|-------------|
|
|
308
|
-
| `MATTERMOST_URL` | Server URL |
|
|
309
|
-
| `MATTERMOST_TOKEN` | Bot token |
|
|
310
|
-
| `MATTERMOST_CHANNEL_ID` | Channel to listen in |
|
|
311
|
-
| `MATTERMOST_BOT_NAME` | Mention name (default: `claude-code`) |
|
|
312
|
-
| `ALLOWED_USERS` | Comma-separated usernames |
|
|
313
|
-
| `SKIP_PERMISSIONS` | `true` to auto-approve actions |
|
|
314
|
-
| `CLAUDE_CHROME` | `true` to enable Chrome integration |
|
|
315
|
-
| `WORKTREE_MODE` | `off`, `prompt`, or `require` (default: `prompt`) |
|
|
316
370
|
| `MAX_SESSIONS` | Max concurrent sessions (default: `5`) |
|
|
317
371
|
| `SESSION_TIMEOUT_MS` | Idle timeout in ms (default: `1800000` = 30 min) |
|
|
318
372
|
| `NO_UPDATE_NOTIFIER` | Set to `1` to disable update checks |
|
|
319
|
-
|
|
320
|
-
Config file locations (in priority order):
|
|
321
|
-
1. `./.env` (current directory)
|
|
322
|
-
2. `~/.config/claude-threads/.env`
|
|
323
|
-
3. `~/.claude-threads.env`
|
|
373
|
+
| `DEBUG` | Set to `1` for verbose logging |
|
|
324
374
|
|
|
325
375
|
## Code Display
|
|
326
376
|
|
package/dist/claude/cli.d.ts
CHANGED
|
@@ -16,6 +16,13 @@ export interface ImageContentBlock {
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
export type ContentBlock = TextContentBlock | ImageContentBlock;
|
|
19
|
+
export interface PlatformMcpConfig {
|
|
20
|
+
type: string;
|
|
21
|
+
url: string;
|
|
22
|
+
token: string;
|
|
23
|
+
channelId: string;
|
|
24
|
+
allowedUsers: string[];
|
|
25
|
+
}
|
|
19
26
|
export interface ClaudeCliOptions {
|
|
20
27
|
workingDir: string;
|
|
21
28
|
threadId?: string;
|
|
@@ -23,6 +30,7 @@ export interface ClaudeCliOptions {
|
|
|
23
30
|
sessionId?: string;
|
|
24
31
|
resume?: boolean;
|
|
25
32
|
chrome?: boolean;
|
|
33
|
+
platformConfig?: PlatformMcpConfig;
|
|
26
34
|
}
|
|
27
35
|
export declare class ClaudeCli extends EventEmitter {
|
|
28
36
|
private process;
|
package/dist/claude/cli.js
CHANGED
|
@@ -36,20 +36,28 @@ export class ClaudeCli extends EventEmitter {
|
|
|
36
36
|
else {
|
|
37
37
|
// Configure the permission MCP server
|
|
38
38
|
const mcpServerPath = this.getMcpServerPath();
|
|
39
|
+
// Platform config is required for MCP permission server
|
|
40
|
+
const platformConfig = this.options.platformConfig;
|
|
41
|
+
if (!platformConfig) {
|
|
42
|
+
throw new Error('platformConfig is required when skipPermissions is false');
|
|
43
|
+
}
|
|
44
|
+
// Platform-agnostic environment variables for MCP permission server
|
|
45
|
+
const mcpEnv = {
|
|
46
|
+
PLATFORM_TYPE: platformConfig.type,
|
|
47
|
+
PLATFORM_URL: platformConfig.url,
|
|
48
|
+
PLATFORM_TOKEN: platformConfig.token,
|
|
49
|
+
PLATFORM_CHANNEL_ID: platformConfig.channelId,
|
|
50
|
+
PLATFORM_THREAD_ID: this.options.threadId || '',
|
|
51
|
+
ALLOWED_USERS: platformConfig.allowedUsers.join(','),
|
|
52
|
+
DEBUG: this.debug ? '1' : '',
|
|
53
|
+
};
|
|
39
54
|
const mcpConfig = {
|
|
40
55
|
mcpServers: {
|
|
41
56
|
'claude-threads-permissions': {
|
|
42
57
|
type: 'stdio',
|
|
43
58
|
command: 'node',
|
|
44
59
|
args: [mcpServerPath],
|
|
45
|
-
env:
|
|
46
|
-
MM_THREAD_ID: this.options.threadId || '',
|
|
47
|
-
MATTERMOST_URL: process.env.MATTERMOST_URL || '',
|
|
48
|
-
MATTERMOST_TOKEN: process.env.MATTERMOST_TOKEN || '',
|
|
49
|
-
MATTERMOST_CHANNEL_ID: process.env.MATTERMOST_CHANNEL_ID || '',
|
|
50
|
-
ALLOWED_USERS: process.env.ALLOWED_USERS || '',
|
|
51
|
-
DEBUG: this.debug ? '1' : '',
|
|
52
|
-
},
|
|
60
|
+
env: mcpEnv,
|
|
53
61
|
},
|
|
54
62
|
},
|
|
55
63
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const CONFIG_PATH: string;
|
|
2
|
+
export type WorktreeMode = 'off' | 'prompt' | 'require';
|
|
3
|
+
export interface NewConfig {
|
|
4
|
+
version: number;
|
|
5
|
+
workingDir: string;
|
|
6
|
+
chrome: boolean;
|
|
7
|
+
worktreeMode: WorktreeMode;
|
|
8
|
+
platforms: PlatformInstanceConfig[];
|
|
9
|
+
}
|
|
10
|
+
export interface PlatformInstanceConfig {
|
|
11
|
+
id: string;
|
|
12
|
+
type: 'mattermost' | 'slack';
|
|
13
|
+
displayName: string;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface MattermostPlatformConfig extends PlatformInstanceConfig {
|
|
17
|
+
type: 'mattermost';
|
|
18
|
+
url: string;
|
|
19
|
+
token: string;
|
|
20
|
+
channelId: string;
|
|
21
|
+
botName: string;
|
|
22
|
+
allowedUsers: string[];
|
|
23
|
+
skipPermissions: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface SlackPlatformConfig extends PlatformInstanceConfig {
|
|
26
|
+
type: 'slack';
|
|
27
|
+
botToken: string;
|
|
28
|
+
appToken: string;
|
|
29
|
+
channelId: string;
|
|
30
|
+
botName: string;
|
|
31
|
+
allowedUsers: string[];
|
|
32
|
+
skipPermissions: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Load config from YAML file
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadConfigWithMigration(): NewConfig | null;
|
|
38
|
+
/**
|
|
39
|
+
* Save config to YAML file
|
|
40
|
+
*/
|
|
41
|
+
export declare function saveConfig(config: NewConfig): void;
|
|
42
|
+
/**
|
|
43
|
+
* Check if config exists
|
|
44
|
+
*/
|
|
45
|
+
export declare function configExists(): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
2
|
+
import YAML from 'yaml';
|
|
3
|
+
import { resolve, dirname } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
// YAML config path
|
|
6
|
+
export const CONFIG_PATH = resolve(homedir(), '.config', 'claude-threads', 'config.yaml');
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Config Loading
|
|
9
|
+
// =============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Load config from YAML file
|
|
12
|
+
*/
|
|
13
|
+
export function loadConfigWithMigration() {
|
|
14
|
+
if (existsSync(CONFIG_PATH)) {
|
|
15
|
+
const content = readFileSync(CONFIG_PATH, 'utf-8');
|
|
16
|
+
return YAML.parse(content);
|
|
17
|
+
}
|
|
18
|
+
return null; // No config found
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Save config to YAML file
|
|
22
|
+
*/
|
|
23
|
+
export function saveConfig(config) {
|
|
24
|
+
const configDir = dirname(CONFIG_PATH);
|
|
25
|
+
if (!existsSync(configDir)) {
|
|
26
|
+
mkdirSync(configDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
writeFileSync(CONFIG_PATH, YAML.stringify(config), 'utf-8');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check if config exists
|
|
32
|
+
*/
|
|
33
|
+
export function configExists() {
|
|
34
|
+
return existsSync(CONFIG_PATH);
|
|
35
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Configuration type exports
|
|
3
|
+
*
|
|
4
|
+
* Re-exports types from config/migration.ts for convenience.
|
|
5
|
+
* The actual config loading and migration logic is in config/migration.ts.
|
|
6
|
+
*/
|
|
7
|
+
import type { WorktreeMode as WorktreeModeType } from './config/migration.js';
|
|
8
|
+
export type { NewConfig, PlatformInstanceConfig, MattermostPlatformConfig, WorktreeMode, } from './config/migration.js';
|
|
9
|
+
/**
|
|
10
|
+
* CLI arguments that can override config
|
|
11
|
+
*/
|
|
5
12
|
export interface CliArgs {
|
|
6
13
|
url?: string;
|
|
7
14
|
token?: string;
|
|
@@ -10,18 +17,5 @@ export interface CliArgs {
|
|
|
10
17
|
allowedUsers?: string;
|
|
11
18
|
skipPermissions?: boolean;
|
|
12
19
|
chrome?: boolean;
|
|
13
|
-
worktreeMode?:
|
|
20
|
+
worktreeMode?: WorktreeModeType;
|
|
14
21
|
}
|
|
15
|
-
export interface Config {
|
|
16
|
-
mattermost: {
|
|
17
|
-
url: string;
|
|
18
|
-
token: string;
|
|
19
|
-
channelId: string;
|
|
20
|
-
botName: string;
|
|
21
|
-
};
|
|
22
|
-
allowedUsers: string[];
|
|
23
|
-
skipPermissions: boolean;
|
|
24
|
-
chrome: boolean;
|
|
25
|
-
worktreeMode: WorktreeMode;
|
|
26
|
-
}
|
|
27
|
-
export declare function loadConfig(cliArgs?: CliArgs): Config;
|
package/dist/config.js
CHANGED
|
@@ -1,94 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
resolve(process.cwd(), '.env'), // Current directory
|
|
9
|
-
resolve(homedir(), '.config', 'claude-threads', '.env'), // ~/.config/claude-threads/.env
|
|
10
|
-
resolve(homedir(), '.claude-threads.env'), // ~/.claude-threads.env
|
|
11
|
-
];
|
|
12
|
-
function loadEnv() {
|
|
13
|
-
if (envLoaded)
|
|
14
|
-
return;
|
|
15
|
-
envLoaded = true;
|
|
16
|
-
for (const envPath of ENV_PATHS) {
|
|
17
|
-
if (existsSync(envPath)) {
|
|
18
|
-
if (process.env.DEBUG === '1' || process.argv.includes('--debug')) {
|
|
19
|
-
console.log(` [config] Loading from: ${envPath}`);
|
|
20
|
-
}
|
|
21
|
-
config({ path: envPath });
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
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];
|
|
32
|
-
if (!value) {
|
|
33
|
-
throw new Error(`Missing required config: ${name}. Set ${envName} in .env or use --${name.toLowerCase().replace(/ /g, '-')} flag.`);
|
|
34
|
-
}
|
|
35
|
-
return value;
|
|
36
|
-
}
|
|
37
|
-
export function loadConfig(cliArgs) {
|
|
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 or --no-skip-permissions takes priority
|
|
50
|
-
// Then env SKIP_PERMISSIONS, then legacy flag
|
|
51
|
-
let skipPermissions;
|
|
52
|
-
if (cliArgs?.skipPermissions !== undefined) {
|
|
53
|
-
// CLI explicitly set (--skip-permissions or --no-skip-permissions)
|
|
54
|
-
skipPermissions = cliArgs.skipPermissions;
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
skipPermissions = process.env.SKIP_PERMISSIONS === 'true' ||
|
|
58
|
-
process.argv.includes('--dangerously-skip-permissions');
|
|
59
|
-
}
|
|
60
|
-
// Chrome integration: CLI flag or env var
|
|
61
|
-
let chrome;
|
|
62
|
-
if (cliArgs?.chrome !== undefined) {
|
|
63
|
-
chrome = cliArgs.chrome;
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
chrome = process.env.CLAUDE_CHROME === 'true';
|
|
67
|
-
}
|
|
68
|
-
// Worktree mode: CLI flag or env var, default to 'prompt'
|
|
69
|
-
let worktreeMode;
|
|
70
|
-
if (cliArgs?.worktreeMode !== undefined) {
|
|
71
|
-
worktreeMode = cliArgs.worktreeMode;
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
const envValue = process.env.WORKTREE_MODE?.toLowerCase();
|
|
75
|
-
if (envValue === 'off' || envValue === 'prompt' || envValue === 'require') {
|
|
76
|
-
worktreeMode = envValue;
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
worktreeMode = 'prompt'; // Default
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
mattermost: {
|
|
84
|
-
url: url.replace(/\/$/, ''), // Remove trailing slash
|
|
85
|
-
token,
|
|
86
|
-
channelId,
|
|
87
|
-
botName,
|
|
88
|
-
},
|
|
89
|
-
allowedUsers,
|
|
90
|
-
skipPermissions,
|
|
91
|
-
chrome,
|
|
92
|
-
worktreeMode,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Configuration type exports
|
|
3
|
+
*
|
|
4
|
+
* Re-exports types from config/migration.ts for convenience.
|
|
5
|
+
* The actual config loading and migration logic is in config/migration.ts.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
package/dist/git/worktree.d.ts
CHANGED
|
@@ -21,10 +21,6 @@ export declare function hasUncommittedChanges(dir: string): Promise<boolean>;
|
|
|
21
21
|
* List all worktrees for a repository
|
|
22
22
|
*/
|
|
23
23
|
export declare function listWorktrees(repoRoot: string): Promise<WorktreeInfo[]>;
|
|
24
|
-
/**
|
|
25
|
-
* Check if a branch exists (local or remote)
|
|
26
|
-
*/
|
|
27
|
-
export declare function branchExists(repoRoot: string, branch: string): Promise<boolean>;
|
|
28
24
|
/**
|
|
29
25
|
* Generate the worktree directory path
|
|
30
26
|
* Creates path like: /path/to/repo-worktrees/branch-name-abc123
|
package/dist/git/worktree.js
CHANGED
|
@@ -119,7 +119,7 @@ export async function listWorktrees(repoRoot) {
|
|
|
119
119
|
/**
|
|
120
120
|
* Check if a branch exists (local or remote)
|
|
121
121
|
*/
|
|
122
|
-
|
|
122
|
+
async function branchExists(repoRoot, branch) {
|
|
123
123
|
try {
|
|
124
124
|
// Check local branches
|
|
125
125
|
await execGit(['rev-parse', '--verify', `refs/heads/${branch}`], repoRoot);
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { program } from 'commander';
|
|
3
|
-
import {
|
|
3
|
+
import { loadConfigWithMigration, configExists as checkConfigExists } from './config/migration.js';
|
|
4
4
|
import { runOnboarding } from './onboarding.js';
|
|
5
|
-
import { MattermostClient } from './mattermost/client.js';
|
|
6
|
-
import { SessionManager } from './
|
|
5
|
+
import { MattermostClient } from './platform/mattermost/client.js';
|
|
6
|
+
import { SessionManager } from './session/index.js';
|
|
7
7
|
import { readFileSync } from 'fs';
|
|
8
8
|
import { dirname, resolve } from 'path';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
@@ -60,20 +60,36 @@ async function main() {
|
|
|
60
60
|
if (opts.setup) {
|
|
61
61
|
await runOnboarding(true); // reconfigure mode
|
|
62
62
|
}
|
|
63
|
-
else if (!
|
|
63
|
+
else if (!checkConfigExists() && !hasRequiredCliArgs(opts)) {
|
|
64
64
|
await runOnboarding(false); // first-time mode
|
|
65
65
|
}
|
|
66
66
|
const workingDir = process.cwd();
|
|
67
|
-
const
|
|
67
|
+
const newConfig = loadConfigWithMigration();
|
|
68
|
+
if (!newConfig) {
|
|
69
|
+
throw new Error('No configuration found. Run with --setup to configure.');
|
|
70
|
+
}
|
|
71
|
+
// CLI args can override global settings
|
|
72
|
+
if (cliArgs.chrome !== undefined) {
|
|
73
|
+
newConfig.chrome = cliArgs.chrome;
|
|
74
|
+
}
|
|
75
|
+
if (cliArgs.worktreeMode !== undefined) {
|
|
76
|
+
newConfig.worktreeMode = cliArgs.worktreeMode;
|
|
77
|
+
}
|
|
78
|
+
// Get first Mattermost platform
|
|
79
|
+
const platformConfig = newConfig.platforms.find(p => p.type === 'mattermost');
|
|
80
|
+
if (!platformConfig) {
|
|
81
|
+
throw new Error('No Mattermost platform configured.');
|
|
82
|
+
}
|
|
83
|
+
const config = newConfig;
|
|
68
84
|
// Print ASCII logo
|
|
69
85
|
printLogo();
|
|
70
86
|
// Startup info
|
|
71
87
|
console.log(dim(` v${pkg.version}`));
|
|
72
88
|
console.log('');
|
|
73
89
|
console.log(` 📂 ${cyan(workingDir)}`);
|
|
74
|
-
console.log(` 💬 ${cyan('@' +
|
|
75
|
-
console.log(` 🌐 ${dim(
|
|
76
|
-
if (
|
|
90
|
+
console.log(` 💬 ${cyan('@' + platformConfig.botName)}`);
|
|
91
|
+
console.log(` 🌐 ${dim(platformConfig.url)}`);
|
|
92
|
+
if (platformConfig.skipPermissions) {
|
|
77
93
|
console.log(` ⚠️ ${dim('Permissions disabled')}`);
|
|
78
94
|
}
|
|
79
95
|
else {
|
|
@@ -83,13 +99,15 @@ async function main() {
|
|
|
83
99
|
console.log(` 🌐 ${dim('Chrome integration enabled')}`);
|
|
84
100
|
}
|
|
85
101
|
console.log('');
|
|
86
|
-
const mattermost = new MattermostClient(
|
|
87
|
-
const session = new SessionManager(
|
|
102
|
+
const mattermost = new MattermostClient(platformConfig);
|
|
103
|
+
const session = new SessionManager(workingDir, platformConfig.skipPermissions, config.chrome, config.worktreeMode);
|
|
104
|
+
// Register platform (connects event handlers)
|
|
105
|
+
session.addPlatform(platformConfig.id, mattermost);
|
|
88
106
|
mattermost.on('message', async (post, user) => {
|
|
89
107
|
try {
|
|
90
108
|
const username = user?.username || 'unknown';
|
|
91
109
|
const message = post.message;
|
|
92
|
-
const threadRoot = post.
|
|
110
|
+
const threadRoot = post.rootId || post.id;
|
|
93
111
|
// Check for !kill command FIRST - works anywhere, even as the first message
|
|
94
112
|
const lowerMessage = message.trim().toLowerCase();
|
|
95
113
|
if (lowerMessage === '!kill' || (mattermost.isBotMentioned(message) && mattermost.extractPrompt(message).toLowerCase() === '!kill')) {
|
|
@@ -329,7 +347,7 @@ async function main() {
|
|
|
329
347
|
console.error(' ❌ Error handling message:', err);
|
|
330
348
|
// Try to notify user if possible
|
|
331
349
|
try {
|
|
332
|
-
const threadRoot = post.
|
|
350
|
+
const threadRoot = post.rootId || post.id;
|
|
333
351
|
await mattermost.createPost(`⚠️ An error occurred. Please try again.`, threadRoot);
|
|
334
352
|
}
|
|
335
353
|
catch {
|
|
@@ -342,7 +360,7 @@ async function main() {
|
|
|
342
360
|
await mattermost.connect();
|
|
343
361
|
// Resume any persisted sessions from before restart
|
|
344
362
|
await session.initialize();
|
|
345
|
-
console.log(` ✅ ${bold('Ready!')} Waiting for @${
|
|
363
|
+
console.log(` ✅ ${bold('Ready!')} Waiting for @${platformConfig.botName} mentions...`);
|
|
346
364
|
console.log('');
|
|
347
365
|
let isShuttingDown = false;
|
|
348
366
|
const shutdown = async () => {
|
package/dist/logo.d.ts
CHANGED
|
@@ -4,27 +4,10 @@
|
|
|
4
4
|
* Stylized CT in Claude Code's block character style.
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
|
-
* ASCII logo for
|
|
8
|
-
*
|
|
7
|
+
* Get ASCII logo for claude-threads with version included
|
|
8
|
+
* For display in chat platforms (plain text, no ANSI codes)
|
|
9
9
|
*/
|
|
10
|
-
export declare
|
|
11
|
-
/**
|
|
12
|
-
* ASCII logo for Mattermost (plain text, no ANSI codes)
|
|
13
|
-
* Use getMattermostLogo(version) instead to include version
|
|
14
|
-
*/
|
|
15
|
-
export declare const MATTERMOST_LOGO = "```\n \u2734 \u2584\u2588\u2580 \u2588\u2588\u2588 \u2734 claude-threads\n\u2734 \u2588\u2580 \u2588 \u2734 Mattermost \u00D7 Claude Code\n \u2734 \u2580\u2588\u2584 \u2588 \u2734\n```";
|
|
16
|
-
/**
|
|
17
|
-
* Get ASCII logo for Mattermost with version included
|
|
18
|
-
*/
|
|
19
|
-
export declare function getMattermostLogo(version: string): string;
|
|
20
|
-
/**
|
|
21
|
-
* Compact inline logo for Mattermost headers
|
|
22
|
-
*/
|
|
23
|
-
export declare const MATTERMOST_LOGO_INLINE = "`\u2584\u2588\u2580T` **claude-threads**";
|
|
24
|
-
/**
|
|
25
|
-
* Very compact logo for space-constrained contexts
|
|
26
|
-
*/
|
|
27
|
-
export declare const LOGO_COMPACT = "\u2584\u2588\u2580T claude-threads";
|
|
10
|
+
export declare function getLogo(version: string): string;
|
|
28
11
|
/**
|
|
29
12
|
* Print CLI logo to stdout
|
|
30
13
|
*/
|
package/dist/logo.js
CHANGED
|
@@ -8,7 +8,7 @@ const colors = {
|
|
|
8
8
|
reset: '\x1b[0m',
|
|
9
9
|
bold: '\x1b[1m',
|
|
10
10
|
dim: '\x1b[2m',
|
|
11
|
-
//
|
|
11
|
+
// Claude blue
|
|
12
12
|
blue: '\x1b[38;5;27m',
|
|
13
13
|
// Claude orange/coral
|
|
14
14
|
orange: '\x1b[38;5;209m',
|
|
@@ -17,38 +17,22 @@ const colors = {
|
|
|
17
17
|
* ASCII logo for CLI display (with ANSI colors)
|
|
18
18
|
* Stylized CT in block characters
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
const CLI_LOGO = `
|
|
21
21
|
${colors.orange} ✴${colors.reset} ${colors.blue}▄█▀ ███${colors.reset} ${colors.orange}✴${colors.reset} ${colors.bold}claude-threads${colors.reset}
|
|
22
|
-
${colors.orange}✴${colors.reset} ${colors.blue}█▀ █${colors.reset} ${colors.orange}✴${colors.reset} ${colors.dim}
|
|
22
|
+
${colors.orange}✴${colors.reset} ${colors.blue}█▀ █${colors.reset} ${colors.orange}✴${colors.reset} ${colors.dim}Chat × Claude Code${colors.reset}
|
|
23
23
|
${colors.orange}✴${colors.reset} ${colors.blue}▀█▄ █${colors.reset} ${colors.orange}✴${colors.reset}
|
|
24
24
|
`;
|
|
25
25
|
/**
|
|
26
|
-
* ASCII logo for
|
|
27
|
-
*
|
|
26
|
+
* Get ASCII logo for claude-threads with version included
|
|
27
|
+
* For display in chat platforms (plain text, no ANSI codes)
|
|
28
28
|
*/
|
|
29
|
-
export
|
|
30
|
-
✴ ▄█▀ ███ ✴ claude-threads
|
|
31
|
-
✴ █▀ █ ✴ Mattermost × Claude Code
|
|
32
|
-
✴ ▀█▄ █ ✴
|
|
33
|
-
\`\`\``;
|
|
34
|
-
/**
|
|
35
|
-
* Get ASCII logo for Mattermost with version included
|
|
36
|
-
*/
|
|
37
|
-
export function getMattermostLogo(version) {
|
|
29
|
+
export function getLogo(version) {
|
|
38
30
|
return `\`\`\`
|
|
39
31
|
✴ ▄█▀ ███ ✴ claude-threads v${version}
|
|
40
|
-
✴ █▀ █ ✴
|
|
32
|
+
✴ █▀ █ ✴ Chat × Claude Code
|
|
41
33
|
✴ ▀█▄ █ ✴
|
|
42
34
|
\`\`\``;
|
|
43
35
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Compact inline logo for Mattermost headers
|
|
46
|
-
*/
|
|
47
|
-
export const MATTERMOST_LOGO_INLINE = '`▄█▀T` **claude-threads**';
|
|
48
|
-
/**
|
|
49
|
-
* Very compact logo for space-constrained contexts
|
|
50
|
-
*/
|
|
51
|
-
export const LOGO_COMPACT = '▄█▀T claude-threads';
|
|
52
36
|
/**
|
|
53
37
|
* Print CLI logo to stdout
|
|
54
38
|
*/
|