claude-threads 0.15.0 → 0.16.3

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 (79) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +5 -5
  3. package/dist/index.js +20410 -387
  4. package/dist/mcp/permission-server.js +34038 -139
  5. package/package.json +14 -18
  6. package/dist/changelog.d.ts +0 -20
  7. package/dist/changelog.js +0 -134
  8. package/dist/claude/cli.d.ts +0 -50
  9. package/dist/claude/cli.js +0 -181
  10. package/dist/config/migration.d.ts +0 -45
  11. package/dist/config/migration.js +0 -35
  12. package/dist/config.d.ts +0 -21
  13. package/dist/config.js +0 -7
  14. package/dist/git/worktree.d.ts +0 -46
  15. package/dist/git/worktree.js +0 -228
  16. package/dist/index.d.ts +0 -2
  17. package/dist/logo.d.ts +0 -14
  18. package/dist/logo.js +0 -41
  19. package/dist/mattermost/api.d.ts +0 -85
  20. package/dist/mattermost/api.js +0 -124
  21. package/dist/mattermost/api.test.d.ts +0 -1
  22. package/dist/mattermost/api.test.js +0 -319
  23. package/dist/mcp/permission-server.d.ts +0 -2
  24. package/dist/onboarding.d.ts +0 -1
  25. package/dist/onboarding.js +0 -318
  26. package/dist/persistence/session-store.d.ts +0 -71
  27. package/dist/persistence/session-store.js +0 -152
  28. package/dist/platform/client.d.ts +0 -140
  29. package/dist/platform/client.js +0 -1
  30. package/dist/platform/formatter.d.ts +0 -74
  31. package/dist/platform/formatter.js +0 -1
  32. package/dist/platform/index.d.ts +0 -11
  33. package/dist/platform/index.js +0 -8
  34. package/dist/platform/mattermost/client.d.ts +0 -70
  35. package/dist/platform/mattermost/client.js +0 -404
  36. package/dist/platform/mattermost/formatter.d.ts +0 -20
  37. package/dist/platform/mattermost/formatter.js +0 -46
  38. package/dist/platform/mattermost/permission-api.d.ts +0 -10
  39. package/dist/platform/mattermost/permission-api.js +0 -139
  40. package/dist/platform/mattermost/types.d.ts +0 -71
  41. package/dist/platform/mattermost/types.js +0 -1
  42. package/dist/platform/permission-api-factory.d.ts +0 -11
  43. package/dist/platform/permission-api-factory.js +0 -21
  44. package/dist/platform/permission-api.d.ts +0 -67
  45. package/dist/platform/permission-api.js +0 -8
  46. package/dist/platform/types.d.ts +0 -70
  47. package/dist/platform/types.js +0 -7
  48. package/dist/session/commands.d.ts +0 -52
  49. package/dist/session/commands.js +0 -323
  50. package/dist/session/events.d.ts +0 -25
  51. package/dist/session/events.js +0 -368
  52. package/dist/session/index.d.ts +0 -7
  53. package/dist/session/index.js +0 -6
  54. package/dist/session/lifecycle.d.ts +0 -70
  55. package/dist/session/lifecycle.js +0 -456
  56. package/dist/session/manager.d.ts +0 -96
  57. package/dist/session/manager.js +0 -537
  58. package/dist/session/reactions.d.ts +0 -25
  59. package/dist/session/reactions.js +0 -151
  60. package/dist/session/streaming.d.ts +0 -47
  61. package/dist/session/streaming.js +0 -152
  62. package/dist/session/types.d.ts +0 -78
  63. package/dist/session/types.js +0 -9
  64. package/dist/session/worktree.d.ts +0 -56
  65. package/dist/session/worktree.js +0 -339
  66. package/dist/update-notifier.d.ts +0 -3
  67. package/dist/update-notifier.js +0 -41
  68. package/dist/utils/emoji.d.ts +0 -43
  69. package/dist/utils/emoji.js +0 -65
  70. package/dist/utils/emoji.test.d.ts +0 -1
  71. package/dist/utils/emoji.test.js +0 -131
  72. package/dist/utils/logger.d.ts +0 -34
  73. package/dist/utils/logger.js +0 -42
  74. package/dist/utils/logger.test.d.ts +0 -1
  75. package/dist/utils/logger.test.js +0 -121
  76. package/dist/utils/tool-formatter.d.ts +0 -53
  77. package/dist/utils/tool-formatter.js +0 -252
  78. package/dist/utils/tool-formatter.test.d.ts +0 -1
  79. package/dist/utils/tool-formatter.test.js +0 -372
@@ -1,228 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import { randomUUID } from 'crypto';
3
- import * as path from 'path';
4
- import * as fs from 'fs/promises';
5
- /**
6
- * Execute a git command and return stdout
7
- */
8
- async function execGit(args, cwd) {
9
- return new Promise((resolve, reject) => {
10
- const proc = spawn('git', args, { cwd });
11
- let stdout = '';
12
- let stderr = '';
13
- proc.stdout.on('data', (data) => {
14
- stdout += data.toString();
15
- });
16
- proc.stderr.on('data', (data) => {
17
- stderr += data.toString();
18
- });
19
- proc.on('close', (code) => {
20
- if (code === 0) {
21
- resolve(stdout.trim());
22
- }
23
- else {
24
- reject(new Error(`git ${args.join(' ')} failed: ${stderr || stdout}`));
25
- }
26
- });
27
- proc.on('error', (err) => {
28
- reject(err);
29
- });
30
- });
31
- }
32
- /**
33
- * Check if a directory is inside a git repository
34
- */
35
- export async function isGitRepository(dir) {
36
- try {
37
- await execGit(['rev-parse', '--git-dir'], dir);
38
- return true;
39
- }
40
- catch {
41
- return false;
42
- }
43
- }
44
- /**
45
- * Get the root directory of the git repository
46
- */
47
- export async function getRepositoryRoot(dir) {
48
- return execGit(['rev-parse', '--show-toplevel'], dir);
49
- }
50
- /**
51
- * Check if there are uncommitted changes (staged or unstaged)
52
- */
53
- export async function hasUncommittedChanges(dir) {
54
- try {
55
- // Check for staged changes
56
- const staged = await execGit(['diff', '--cached', '--quiet'], dir).catch(() => 'changes');
57
- if (staged === 'changes')
58
- return true;
59
- // Check for unstaged changes
60
- const unstaged = await execGit(['diff', '--quiet'], dir).catch(() => 'changes');
61
- if (unstaged === 'changes')
62
- return true;
63
- // Check for untracked files
64
- const untracked = await execGit(['ls-files', '--others', '--exclude-standard'], dir);
65
- return untracked.length > 0;
66
- }
67
- catch {
68
- return false;
69
- }
70
- }
71
- /**
72
- * List all worktrees for a repository
73
- */
74
- export async function listWorktrees(repoRoot) {
75
- const output = await execGit(['worktree', 'list', '--porcelain'], repoRoot);
76
- const worktrees = [];
77
- if (!output)
78
- return worktrees;
79
- // Parse porcelain output
80
- // Format:
81
- // worktree /path/to/worktree
82
- // HEAD <commit>
83
- // branch refs/heads/branch-name
84
- // <blank line>
85
- const blocks = output.split('\n\n').filter(Boolean);
86
- for (const block of blocks) {
87
- const lines = block.split('\n');
88
- const worktree = {};
89
- for (const line of lines) {
90
- if (line.startsWith('worktree ')) {
91
- worktree.path = line.slice(9);
92
- }
93
- else if (line.startsWith('HEAD ')) {
94
- worktree.commit = line.slice(5);
95
- }
96
- else if (line.startsWith('branch ')) {
97
- // refs/heads/branch-name -> branch-name
98
- worktree.branch = line.slice(7).replace('refs/heads/', '');
99
- }
100
- else if (line === 'bare') {
101
- worktree.isBare = true;
102
- }
103
- else if (line === 'detached') {
104
- worktree.branch = '(detached)';
105
- }
106
- }
107
- if (worktree.path) {
108
- worktrees.push({
109
- path: worktree.path,
110
- branch: worktree.branch || '(unknown)',
111
- commit: worktree.commit || '',
112
- isMain: worktrees.length === 0, // First worktree is the main one
113
- isBare: worktree.isBare || false,
114
- });
115
- }
116
- }
117
- return worktrees;
118
- }
119
- /**
120
- * Check if a branch exists (local or remote)
121
- */
122
- async function branchExists(repoRoot, branch) {
123
- try {
124
- // Check local branches
125
- await execGit(['rev-parse', '--verify', `refs/heads/${branch}`], repoRoot);
126
- return true;
127
- }
128
- catch {
129
- try {
130
- // Check remote branches
131
- await execGit(['rev-parse', '--verify', `refs/remotes/origin/${branch}`], repoRoot);
132
- return true;
133
- }
134
- catch {
135
- return false;
136
- }
137
- }
138
- }
139
- /**
140
- * Generate the worktree directory path
141
- * Creates path like: /path/to/repo-worktrees/branch-name-abc123
142
- */
143
- export function getWorktreeDir(repoRoot, branch) {
144
- const repoName = path.basename(repoRoot);
145
- const parentDir = path.dirname(repoRoot);
146
- const worktreesDir = path.join(parentDir, `${repoName}-worktrees`);
147
- // Sanitize branch name for filesystem
148
- const sanitizedBranch = branch
149
- .replace(/\//g, '-')
150
- .replace(/[^a-zA-Z0-9-_]/g, '');
151
- const shortUuid = randomUUID().slice(0, 8);
152
- return path.join(worktreesDir, `${sanitizedBranch}-${shortUuid}`);
153
- }
154
- /**
155
- * Create a new worktree for a branch
156
- * If the branch doesn't exist, creates it from the current HEAD
157
- */
158
- export async function createWorktree(repoRoot, branch, targetDir) {
159
- // Ensure the parent directory exists
160
- const parentDir = path.dirname(targetDir);
161
- await fs.mkdir(parentDir, { recursive: true });
162
- // Check if branch exists
163
- const exists = await branchExists(repoRoot, branch);
164
- if (exists) {
165
- // Use existing branch
166
- await execGit(['worktree', 'add', targetDir, branch], repoRoot);
167
- }
168
- else {
169
- // Create new branch from HEAD
170
- await execGit(['worktree', 'add', '-b', branch, targetDir], repoRoot);
171
- }
172
- return targetDir;
173
- }
174
- /**
175
- * Remove a worktree
176
- */
177
- export async function removeWorktree(repoRoot, worktreePath) {
178
- // First try to remove cleanly
179
- try {
180
- await execGit(['worktree', 'remove', worktreePath], repoRoot);
181
- }
182
- catch {
183
- // If that fails, try force remove
184
- await execGit(['worktree', 'remove', '--force', worktreePath], repoRoot);
185
- }
186
- // Prune any stale worktree references
187
- await execGit(['worktree', 'prune'], repoRoot);
188
- }
189
- /**
190
- * Find a worktree by branch name
191
- */
192
- export async function findWorktreeByBranch(repoRoot, branch) {
193
- const worktrees = await listWorktrees(repoRoot);
194
- return worktrees.find((wt) => wt.branch === branch) || null;
195
- }
196
- /**
197
- * Validate a git branch name
198
- * Based on git-check-ref-format rules
199
- */
200
- export function isValidBranchName(name) {
201
- if (!name || name.length === 0)
202
- return false;
203
- // Cannot start or end with /
204
- if (name.startsWith('/') || name.endsWith('/'))
205
- return false;
206
- // Cannot contain ..
207
- if (name.includes('..'))
208
- return false;
209
- // Cannot contain special characters
210
- if (/[\s~^:?*[\]\\]/.test(name))
211
- return false;
212
- // Cannot start with -
213
- if (name.startsWith('-'))
214
- return false;
215
- // Cannot end with .lock
216
- if (name.endsWith('.lock'))
217
- return false;
218
- // Cannot contain @{
219
- if (name.includes('@{'))
220
- return false;
221
- // Cannot be @
222
- if (name === '@')
223
- return false;
224
- // Cannot contain consecutive dots
225
- if (/\.\./.test(name))
226
- return false;
227
- return true;
228
- }
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
package/dist/logo.d.ts DELETED
@@ -1,14 +0,0 @@
1
- /**
2
- * ASCII Art Logo for Claude Threads
3
- *
4
- * Stylized CT in Claude Code's block character style.
5
- */
6
- /**
7
- * Get ASCII logo for claude-threads with version included
8
- * For display in chat platforms (plain text, no ANSI codes)
9
- */
10
- export declare function getLogo(version: string): string;
11
- /**
12
- * Print CLI logo to stdout
13
- */
14
- export declare function printLogo(): void;
package/dist/logo.js DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * ASCII Art Logo for Claude Threads
3
- *
4
- * Stylized CT in Claude Code's block character style.
5
- */
6
- // ANSI color codes for terminal
7
- const colors = {
8
- reset: '\x1b[0m',
9
- bold: '\x1b[1m',
10
- dim: '\x1b[2m',
11
- // Claude blue
12
- blue: '\x1b[38;5;27m',
13
- // Claude orange/coral
14
- orange: '\x1b[38;5;209m',
15
- };
16
- /**
17
- * ASCII logo for CLI display (with ANSI colors)
18
- * Stylized CT in block characters
19
- */
20
- const CLI_LOGO = `
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}Chat × Claude Code${colors.reset}
23
- ${colors.orange}✴${colors.reset} ${colors.blue}▀█▄ █${colors.reset} ${colors.orange}✴${colors.reset}
24
- `;
25
- /**
26
- * Get ASCII logo for claude-threads with version included
27
- * For display in chat platforms (plain text, no ANSI codes)
28
- */
29
- export function getLogo(version) {
30
- return `\`\`\`
31
- ✴ ▄█▀ ███ ✴ claude-threads v${version}
32
- ✴ █▀ █ ✴ Chat × Claude Code
33
- ✴ ▀█▄ █ ✴
34
- \`\`\``;
35
- }
36
- /**
37
- * Print CLI logo to stdout
38
- */
39
- export function printLogo() {
40
- console.log(CLI_LOGO);
41
- }
@@ -1,85 +0,0 @@
1
- /**
2
- * Shared Mattermost REST API layer
3
- *
4
- * Provides standalone API functions that can be used by both:
5
- * - src/mattermost/client.ts (main bot with WebSocket)
6
- * - src/mcp/permission-server.ts (MCP subprocess)
7
- *
8
- * These functions take config as parameters (not from global state)
9
- * to support the MCP server running as a separate process.
10
- */
11
- export interface MattermostApiConfig {
12
- url: string;
13
- token: string;
14
- }
15
- export interface MattermostApiPost {
16
- id: string;
17
- channel_id: string;
18
- message: string;
19
- root_id?: string;
20
- user_id?: string;
21
- create_at?: number;
22
- }
23
- export interface MattermostApiUser {
24
- id: string;
25
- username: string;
26
- email?: string;
27
- first_name?: string;
28
- last_name?: string;
29
- }
30
- /**
31
- * Make a request to the Mattermost REST API
32
- *
33
- * @param config - API configuration (url and token)
34
- * @param method - HTTP method
35
- * @param path - API path (starting with /)
36
- * @param body - Optional request body
37
- * @returns Promise with the response data
38
- */
39
- export declare function mattermostApi<T>(config: MattermostApiConfig, method: string, path: string, body?: unknown): Promise<T>;
40
- /**
41
- * Get the current authenticated user (bot user)
42
- */
43
- export declare function getMe(config: MattermostApiConfig): Promise<MattermostApiUser>;
44
- /**
45
- * Get a user by ID
46
- */
47
- export declare function getUser(config: MattermostApiConfig, userId: string): Promise<MattermostApiUser | null>;
48
- /**
49
- * Create a new post in a channel
50
- */
51
- export declare function createPost(config: MattermostApiConfig, channelId: string, message: string, rootId?: string): Promise<MattermostApiPost>;
52
- /**
53
- * Update an existing post
54
- */
55
- export declare function updatePost(config: MattermostApiConfig, postId: string, message: string): Promise<MattermostApiPost>;
56
- /**
57
- * Add a reaction to a post
58
- */
59
- export declare function addReaction(config: MattermostApiConfig, postId: string, userId: string, emojiName: string): Promise<void>;
60
- /**
61
- * Check if a user is allowed based on an allowlist
62
- *
63
- * @param username - Username to check
64
- * @param allowList - List of allowed usernames (empty = all allowed)
65
- * @returns true if user is allowed
66
- */
67
- export declare function isUserAllowed(username: string, allowList: string[]): boolean;
68
- /**
69
- * Create a post with reaction options for user interaction
70
- *
71
- * This is a common pattern used for:
72
- * - Permission prompts (approve/deny/allow-all)
73
- * - Plan approval (approve/deny)
74
- * - Question answering (numbered options)
75
- * - Message approval (approve/allow-all/deny)
76
- *
77
- * @param config - API configuration
78
- * @param channelId - Channel to post in
79
- * @param message - Post message content
80
- * @param reactions - Array of emoji names to add as reaction options
81
- * @param rootId - Optional thread root ID
82
- * @param botUserId - Bot user ID (required for adding reactions)
83
- * @returns The created post
84
- */
85
- export declare function createInteractivePost(config: MattermostApiConfig, channelId: string, message: string, reactions: string[], rootId: string | undefined, botUserId: string): Promise<MattermostApiPost>;
@@ -1,124 +0,0 @@
1
- /**
2
- * Shared Mattermost REST API layer
3
- *
4
- * Provides standalone API functions that can be used by both:
5
- * - src/mattermost/client.ts (main bot with WebSocket)
6
- * - src/mcp/permission-server.ts (MCP subprocess)
7
- *
8
- * These functions take config as parameters (not from global state)
9
- * to support the MCP server running as a separate process.
10
- */
11
- /**
12
- * Make a request to the Mattermost REST API
13
- *
14
- * @param config - API configuration (url and token)
15
- * @param method - HTTP method
16
- * @param path - API path (starting with /)
17
- * @param body - Optional request body
18
- * @returns Promise with the response data
19
- */
20
- export async function mattermostApi(config, method, path, body) {
21
- const url = `${config.url}/api/v4${path}`;
22
- const response = await fetch(url, {
23
- method,
24
- headers: {
25
- Authorization: `Bearer ${config.token}`,
26
- 'Content-Type': 'application/json',
27
- },
28
- body: body ? JSON.stringify(body) : undefined,
29
- });
30
- if (!response.ok) {
31
- const text = await response.text();
32
- throw new Error(`Mattermost API error ${response.status}: ${text}`);
33
- }
34
- return response.json();
35
- }
36
- /**
37
- * Get the current authenticated user (bot user)
38
- */
39
- export async function getMe(config) {
40
- return mattermostApi(config, 'GET', '/users/me');
41
- }
42
- /**
43
- * Get a user by ID
44
- */
45
- export async function getUser(config, userId) {
46
- try {
47
- return await mattermostApi(config, 'GET', `/users/${userId}`);
48
- }
49
- catch {
50
- return null;
51
- }
52
- }
53
- /**
54
- * Create a new post in a channel
55
- */
56
- export async function createPost(config, channelId, message, rootId) {
57
- return mattermostApi(config, 'POST', '/posts', {
58
- channel_id: channelId,
59
- message,
60
- root_id: rootId,
61
- });
62
- }
63
- /**
64
- * Update an existing post
65
- */
66
- export async function updatePost(config, postId, message) {
67
- return mattermostApi(config, 'PUT', `/posts/${postId}`, {
68
- id: postId,
69
- message,
70
- });
71
- }
72
- /**
73
- * Add a reaction to a post
74
- */
75
- export async function addReaction(config, postId, userId, emojiName) {
76
- await mattermostApi(config, 'POST', '/reactions', {
77
- user_id: userId,
78
- post_id: postId,
79
- emoji_name: emojiName,
80
- });
81
- }
82
- /**
83
- * Check if a user is allowed based on an allowlist
84
- *
85
- * @param username - Username to check
86
- * @param allowList - List of allowed usernames (empty = all allowed)
87
- * @returns true if user is allowed
88
- */
89
- export function isUserAllowed(username, allowList) {
90
- if (allowList.length === 0)
91
- return true;
92
- return allowList.includes(username);
93
- }
94
- /**
95
- * Create a post with reaction options for user interaction
96
- *
97
- * This is a common pattern used for:
98
- * - Permission prompts (approve/deny/allow-all)
99
- * - Plan approval (approve/deny)
100
- * - Question answering (numbered options)
101
- * - Message approval (approve/allow-all/deny)
102
- *
103
- * @param config - API configuration
104
- * @param channelId - Channel to post in
105
- * @param message - Post message content
106
- * @param reactions - Array of emoji names to add as reaction options
107
- * @param rootId - Optional thread root ID
108
- * @param botUserId - Bot user ID (required for adding reactions)
109
- * @returns The created post
110
- */
111
- export async function createInteractivePost(config, channelId, message, reactions, rootId, botUserId) {
112
- const post = await createPost(config, channelId, message, rootId);
113
- // Add each reaction option, continuing even if some fail
114
- for (const emoji of reactions) {
115
- try {
116
- await addReaction(config, post.id, botUserId, emoji);
117
- }
118
- catch (err) {
119
- // Log error but continue - the post was created successfully
120
- console.error(` ⚠️ Failed to add reaction ${emoji}:`, err);
121
- }
122
- }
123
- return post;
124
- }
@@ -1 +0,0 @@
1
- export {};