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.
Files changed (67) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +78 -28
  3. package/dist/claude/cli.d.ts +8 -0
  4. package/dist/claude/cli.js +16 -8
  5. package/dist/config/migration.d.ts +45 -0
  6. package/dist/config/migration.js +35 -0
  7. package/dist/config.d.ts +12 -18
  8. package/dist/config.js +7 -94
  9. package/dist/git/worktree.d.ts +0 -4
  10. package/dist/git/worktree.js +1 -1
  11. package/dist/index.js +31 -13
  12. package/dist/logo.d.ts +3 -20
  13. package/dist/logo.js +7 -23
  14. package/dist/mcp/permission-server.js +61 -112
  15. package/dist/onboarding.js +262 -137
  16. package/dist/persistence/session-store.d.ts +8 -2
  17. package/dist/persistence/session-store.js +41 -16
  18. package/dist/platform/client.d.ts +140 -0
  19. package/dist/platform/formatter.d.ts +74 -0
  20. package/dist/platform/index.d.ts +11 -0
  21. package/dist/platform/index.js +8 -0
  22. package/dist/platform/mattermost/client.d.ts +70 -0
  23. package/dist/{mattermost → platform/mattermost}/client.js +117 -34
  24. package/dist/platform/mattermost/formatter.d.ts +20 -0
  25. package/dist/platform/mattermost/formatter.js +46 -0
  26. package/dist/platform/mattermost/permission-api.d.ts +10 -0
  27. package/dist/platform/mattermost/permission-api.js +139 -0
  28. package/dist/platform/mattermost/types.js +1 -0
  29. package/dist/platform/permission-api-factory.d.ts +11 -0
  30. package/dist/platform/permission-api-factory.js +21 -0
  31. package/dist/platform/permission-api.d.ts +67 -0
  32. package/dist/platform/permission-api.js +8 -0
  33. package/dist/platform/types.d.ts +70 -0
  34. package/dist/platform/types.js +7 -0
  35. package/dist/session/commands.d.ts +52 -0
  36. package/dist/session/commands.js +323 -0
  37. package/dist/session/events.d.ts +25 -0
  38. package/dist/session/events.js +368 -0
  39. package/dist/session/index.d.ts +7 -0
  40. package/dist/session/index.js +6 -0
  41. package/dist/session/lifecycle.d.ts +70 -0
  42. package/dist/session/lifecycle.js +456 -0
  43. package/dist/session/manager.d.ts +96 -0
  44. package/dist/session/manager.js +537 -0
  45. package/dist/session/reactions.d.ts +25 -0
  46. package/dist/session/reactions.js +151 -0
  47. package/dist/session/streaming.d.ts +47 -0
  48. package/dist/session/streaming.js +152 -0
  49. package/dist/session/types.d.ts +78 -0
  50. package/dist/session/types.js +9 -0
  51. package/dist/session/worktree.d.ts +56 -0
  52. package/dist/session/worktree.js +339 -0
  53. package/dist/update-notifier.js +10 -0
  54. package/dist/{mattermost → utils}/emoji.d.ts +3 -3
  55. package/dist/{mattermost → utils}/emoji.js +3 -3
  56. package/dist/utils/emoji.test.d.ts +1 -0
  57. package/dist/utils/tool-formatter.d.ts +10 -13
  58. package/dist/utils/tool-formatter.js +48 -43
  59. package/dist/utils/tool-formatter.test.js +67 -52
  60. package/package.json +4 -3
  61. package/dist/claude/session.d.ts +0 -256
  62. package/dist/claude/session.js +0 -1964
  63. package/dist/mattermost/client.d.ts +0 -56
  64. /package/dist/{mattermost/emoji.test.d.ts → platform/client.js} +0 -0
  65. /package/dist/{mattermost/types.js → platform/formatter.js} +0 -0
  66. /package/dist/{mattermost → platform/mattermost}/types.d.ts +0 -0
  67. /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
- Welcome to claude-threads!
71
+ claude-threads setup
72
+ ─────────────────────────────────
72
73
 
73
- No configuration found. Let's set things up.
74
+ Welcome! Let's configure claude-threads.
74
75
 
75
- You'll need:
76
- A Mattermost bot account with a token
77
- A channel ID where the bot will listen
76
+ ? Default working directory: /home/user/projects
77
+ ? Enable Chrome integration? No
78
+ ? Git worktree mode: Prompt
78
79
 
79
- ? Mattermost URL: https://your-mattermost.com
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
- ? Skip permission prompts? No
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
- ~/.config/claude-threads/.env
99
+ ✓ Configuration saved!
100
+ ~/.config/claude-threads/config.yaml
88
101
 
89
- Starting claude-threads...
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 `ALLOWED_USERS` to restrict who can use the bot:
309
+ Set `allowedUsers` in your platform config to restrict who can use the bot:
294
310
 
295
- ```env
296
- ALLOWED_USERS=alice,bob,carol
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
- ## Environment Variables
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
 
@@ -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;
@@ -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
- /** Check if any .env config file exists */
2
- export declare function configExists(): boolean;
3
- export type WorktreeMode = 'off' | 'prompt' | 'require';
4
- /** CLI arguments that can override config */
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?: 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
- import { config } from 'dotenv';
2
- import { resolve } from 'path';
3
- import { existsSync } from 'fs';
4
- import { homedir } from 'os';
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', '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 {};
@@ -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
@@ -119,7 +119,7 @@ export async function listWorktrees(repoRoot) {
119
119
  /**
120
120
  * Check if a branch exists (local or remote)
121
121
  */
122
- export async function branchExists(repoRoot, branch) {
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 { loadConfig, configExists } from './config.js';
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 './claude/session.js';
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 (!configExists() && !hasRequiredCliArgs(opts)) {
63
+ else if (!checkConfigExists() && !hasRequiredCliArgs(opts)) {
64
64
  await runOnboarding(false); // first-time mode
65
65
  }
66
66
  const workingDir = process.cwd();
67
- const config = loadConfig(cliArgs);
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('@' + config.mattermost.botName)}`);
75
- console.log(` 🌐 ${dim(config.mattermost.url)}`);
76
- if (config.skipPermissions) {
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(config);
87
- const session = new SessionManager(mattermost, workingDir, config.skipPermissions, config.chrome, config.worktreeMode);
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.root_id || post.id;
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.root_id || post.id;
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 @${config.mattermost.botName} mentions...`);
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 CLI display (with ANSI colors)
8
- * Stylized CT in block characters
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 const CLI_LOGO: string;
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
- // Mattermost blue (#1C58D9)
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
- export const CLI_LOGO = `
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}Mattermost × Claude Code${colors.reset}
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 Mattermost (plain text, no ANSI codes)
27
- * Use getMattermostLogo(version) instead to include version
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 const MATTERMOST_LOGO = `\`\`\`
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
- ✴ █▀ █ ✴ Mattermost × Claude Code
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
  */