@doist/twist-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/dist/__tests__/auth.test.d.ts +2 -0
- package/dist/__tests__/auth.test.d.ts.map +1 -0
- package/dist/__tests__/auth.test.js +300 -0
- package/dist/__tests__/auth.test.js.map +1 -0
- package/dist/__tests__/lib/dates.test.d.ts +2 -0
- package/dist/__tests__/lib/dates.test.d.ts.map +1 -0
- package/dist/__tests__/lib/dates.test.js +58 -0
- package/dist/__tests__/lib/dates.test.js.map +1 -0
- package/dist/__tests__/lib/refs.test.d.ts +2 -0
- package/dist/__tests__/lib/refs.test.d.ts.map +1 -0
- package/dist/__tests__/lib/refs.test.js +121 -0
- package/dist/__tests__/lib/refs.test.js.map +1 -0
- package/dist/__tests__/spinner.test.d.ts +2 -0
- package/dist/__tests__/spinner.test.d.ts.map +1 -0
- package/dist/__tests__/spinner.test.js +153 -0
- package/dist/__tests__/spinner.test.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +93 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/channel.d.ts +3 -0
- package/dist/commands/channel.d.ts.map +1 -0
- package/dist/commands/channel.js +49 -0
- package/dist/commands/channel.js.map +1 -0
- package/dist/commands/inbox.d.ts +3 -0
- package/dist/commands/inbox.d.ts.map +1 -0
- package/dist/commands/inbox.js +121 -0
- package/dist/commands/inbox.js.map +1 -0
- package/dist/commands/msg.d.ts +3 -0
- package/dist/commands/msg.d.ts.map +1 -0
- package/dist/commands/msg.js +205 -0
- package/dist/commands/msg.js.map +1 -0
- package/dist/commands/react.d.ts +3 -0
- package/dist/commands/react.d.ts.map +1 -0
- package/dist/commands/react.js +100 -0
- package/dist/commands/react.js.map +1 -0
- package/dist/commands/search.d.ts +3 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +134 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/thread.d.ts +3 -0
- package/dist/commands/thread.d.ts.map +1 -0
- package/dist/commands/thread.js +219 -0
- package/dist/commands/thread.js.map +1 -0
- package/dist/commands/user.d.ts +3 -0
- package/dist/commands/user.d.ts.map +1 -0
- package/dist/commands/user.js +67 -0
- package/dist/commands/user.js.map +1 -0
- package/dist/commands/workspace.d.ts +3 -0
- package/dist/commands/workspace.d.ts.map +1 -0
- package/dist/commands/workspace.js +48 -0
- package/dist/commands/workspace.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api.d.ts +10 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/api.js +143 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/auth.d.ts +4 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +40 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/config.d.ts +9 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +26 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/dates.d.ts +3 -0
- package/dist/lib/dates.d.ts.map +1 -0
- package/dist/lib/dates.js +44 -0
- package/dist/lib/dates.js.map +1 -0
- package/dist/lib/input.d.ts +3 -0
- package/dist/lib/input.d.ts.map +1 -0
- package/dist/lib/input.js +52 -0
- package/dist/lib/input.js.map +1 -0
- package/dist/lib/markdown.d.ts +2 -0
- package/dist/lib/markdown.d.ts.map +1 -0
- package/dist/lib/markdown.js +12 -0
- package/dist/lib/markdown.js.map +1 -0
- package/dist/lib/oauth-server.d.ts +13 -0
- package/dist/lib/oauth-server.d.ts.map +1 -0
- package/dist/lib/oauth-server.js +168 -0
- package/dist/lib/oauth-server.js.map +1 -0
- package/dist/lib/oauth.d.ts +28 -0
- package/dist/lib/oauth.d.ts.map +1 -0
- package/dist/lib/oauth.js +125 -0
- package/dist/lib/oauth.js.map +1 -0
- package/dist/lib/output.d.ts +22 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +104 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/pkce.d.ts +16 -0
- package/dist/lib/pkce.d.ts.map +1 -0
- package/dist/lib/pkce.js +35 -0
- package/dist/lib/pkce.js.map +1 -0
- package/dist/lib/refs.d.ts +29 -0
- package/dist/lib/refs.d.ts.map +1 -0
- package/dist/lib/refs.js +168 -0
- package/dist/lib/refs.js.map +1 -0
- package/dist/lib/search-api.d.ts +25 -0
- package/dist/lib/search-api.d.ts.map +1 -0
- package/dist/lib/search-api.js +85 -0
- package/dist/lib/search-api.js.map +1 -0
- package/dist/lib/spinner.d.ts +20 -0
- package/dist/lib/spinner.d.ts.map +1 -0
- package/dist/lib/spinner.js +70 -0
- package/dist/lib/spinner.js.map +1 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Twist CLI
|
|
2
|
+
|
|
3
|
+
A command-line interface for Twist.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
> **Note**: This package is not yet published to npm. Once published, install with:
|
|
8
|
+
>
|
|
9
|
+
> ```bash
|
|
10
|
+
> npm install -g @doist/twist-cli
|
|
11
|
+
> ```
|
|
12
|
+
|
|
13
|
+
### Local Setup (for now)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
git clone https://github.com/Doist/twist-cli.git
|
|
17
|
+
cd twist-cli
|
|
18
|
+
npm install
|
|
19
|
+
npm run build
|
|
20
|
+
npm link
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This makes the `tw` command available globally.
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
Save your Twist API token:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
tw login token "your-token"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Alternatively, set via environment variable:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
export TWIST_API_TOKEN="your-token"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
tw workspaces # list all workspaces
|
|
43
|
+
tw inbox # inbox threads
|
|
44
|
+
tw inbox --unread # unread threads only
|
|
45
|
+
tw thread view <thread-ref> # view thread with comments
|
|
46
|
+
tw thread reply <thread-ref> # reply to a thread
|
|
47
|
+
tw msg list # list conversations
|
|
48
|
+
tw msg view <conversation-ref> # view conversation messages
|
|
49
|
+
tw search "keyword" # search across workspace
|
|
50
|
+
tw react thread <ref> 👍 # add reaction
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
References accept IDs (`123` or `id:123`), Twist URLs, or fuzzy names (for workspaces/users).
|
|
54
|
+
|
|
55
|
+
Run `tw --help` or `tw <command> --help` for more options.
|
|
56
|
+
|
|
57
|
+
### Machine-readable output
|
|
58
|
+
|
|
59
|
+
All list/view commands support `--json` and `--ndjson` flags for scripting:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
tw inbox --json # JSON array
|
|
63
|
+
tw inbox --ndjson # newline-delimited JSON
|
|
64
|
+
tw inbox --json --full # include all fields
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Development
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm install
|
|
71
|
+
npm run build # compile
|
|
72
|
+
npm run dev # watch mode
|
|
73
|
+
npm run type-check # type check
|
|
74
|
+
npm run format # format code
|
|
75
|
+
npm test # run tests
|
|
76
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/auth.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
// Mock the auth module
|
|
4
|
+
vi.mock('../lib/auth.js', () => ({
|
|
5
|
+
saveApiToken: vi.fn(),
|
|
6
|
+
clearApiToken: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
// Mock the api module
|
|
9
|
+
vi.mock('../lib/api.js', () => ({
|
|
10
|
+
getSessionUser: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
// Mock the config module
|
|
13
|
+
vi.mock('../lib/config.js', () => ({
|
|
14
|
+
getConfigPath: vi.fn(() => '/home/user/.config/twist-cli/config.json'),
|
|
15
|
+
}));
|
|
16
|
+
// Mock OAuth modules
|
|
17
|
+
vi.mock('../lib/oauth.js', () => ({
|
|
18
|
+
buildAuthorizationUrl: vi.fn(),
|
|
19
|
+
exchangeCodeForToken: vi.fn(),
|
|
20
|
+
registerDynamicClient: vi.fn(),
|
|
21
|
+
}));
|
|
22
|
+
vi.mock('../lib/oauth-server.js', () => ({
|
|
23
|
+
startCallbackServer: vi.fn(),
|
|
24
|
+
}));
|
|
25
|
+
vi.mock('../lib/pkce.js', () => ({
|
|
26
|
+
generateCodeVerifier: vi.fn(),
|
|
27
|
+
generateCodeChallenge: vi.fn(),
|
|
28
|
+
generateState: vi.fn(),
|
|
29
|
+
}));
|
|
30
|
+
// Mock open package
|
|
31
|
+
vi.mock('open', () => ({
|
|
32
|
+
default: vi.fn(),
|
|
33
|
+
}));
|
|
34
|
+
// Mock chalk to avoid colors in tests
|
|
35
|
+
vi.mock('chalk', () => ({
|
|
36
|
+
default: {
|
|
37
|
+
green: vi.fn((text) => text),
|
|
38
|
+
yellow: vi.fn((text) => text),
|
|
39
|
+
red: vi.fn((text) => text),
|
|
40
|
+
blue: vi.fn((text) => text),
|
|
41
|
+
dim: vi.fn((text) => text),
|
|
42
|
+
},
|
|
43
|
+
}));
|
|
44
|
+
import open from 'open';
|
|
45
|
+
import { registerAuthCommand } from '../commands/auth.js';
|
|
46
|
+
import { getSessionUser } from '../lib/api.js';
|
|
47
|
+
import { clearApiToken, saveApiToken } from '../lib/auth.js';
|
|
48
|
+
import { buildAuthorizationUrl, exchangeCodeForToken, registerDynamicClient } from '../lib/oauth.js';
|
|
49
|
+
import { startCallbackServer } from '../lib/oauth-server.js';
|
|
50
|
+
import { generateCodeChallenge, generateCodeVerifier, generateState } from '../lib/pkce.js';
|
|
51
|
+
const mockSaveApiToken = vi.mocked(saveApiToken);
|
|
52
|
+
const mockClearApiToken = vi.mocked(clearApiToken);
|
|
53
|
+
const mockGetSessionUser = vi.mocked(getSessionUser);
|
|
54
|
+
// OAuth mocks
|
|
55
|
+
const mockGenerateCodeVerifier = vi.mocked(generateCodeVerifier);
|
|
56
|
+
const mockGenerateCodeChallenge = vi.mocked(generateCodeChallenge);
|
|
57
|
+
const mockGenerateState = vi.mocked(generateState);
|
|
58
|
+
const mockBuildAuthorizationUrl = vi.mocked(buildAuthorizationUrl);
|
|
59
|
+
const mockStartCallbackServer = vi.mocked(startCallbackServer);
|
|
60
|
+
const mockExchangeCodeForToken = vi.mocked(exchangeCodeForToken);
|
|
61
|
+
const mockRegisterDynamicClient = vi.mocked(registerDynamicClient);
|
|
62
|
+
const mockOpen = vi.mocked(open);
|
|
63
|
+
function createProgram() {
|
|
64
|
+
const program = new Command();
|
|
65
|
+
program.exitOverride();
|
|
66
|
+
registerAuthCommand(program);
|
|
67
|
+
return program;
|
|
68
|
+
}
|
|
69
|
+
describe('auth command', () => {
|
|
70
|
+
let consoleSpy;
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
vi.clearAllMocks();
|
|
73
|
+
// Mock console.log to capture output
|
|
74
|
+
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
75
|
+
});
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
consoleSpy.mockRestore();
|
|
78
|
+
});
|
|
79
|
+
describe('token subcommand', () => {
|
|
80
|
+
it('successfully saves a token', async () => {
|
|
81
|
+
const program = createProgram();
|
|
82
|
+
const token = 'some_token_123456789';
|
|
83
|
+
// Mock successful token save
|
|
84
|
+
mockSaveApiToken.mockResolvedValue(undefined);
|
|
85
|
+
await program.parseAsync(['node', 'tw', 'auth', 'token', token]);
|
|
86
|
+
// Verify token was saved
|
|
87
|
+
expect(mockSaveApiToken).toHaveBeenCalledWith(token);
|
|
88
|
+
// Verify success message
|
|
89
|
+
expect(consoleSpy).toHaveBeenCalledWith('✓', 'API token saved successfully!');
|
|
90
|
+
expect(consoleSpy).toHaveBeenCalledWith('Token saved to /home/user/.config/twist-cli/config.json');
|
|
91
|
+
});
|
|
92
|
+
it('handles saveApiToken errors', async () => {
|
|
93
|
+
const program = createProgram();
|
|
94
|
+
const token = 'some_token_123456789';
|
|
95
|
+
// Mock save failure
|
|
96
|
+
mockSaveApiToken.mockRejectedValue(new Error('Permission denied'));
|
|
97
|
+
await expect(program.parseAsync(['node', 'tw', 'auth', 'token', token])).rejects.toThrow('Permission denied');
|
|
98
|
+
expect(mockSaveApiToken).toHaveBeenCalledWith(token);
|
|
99
|
+
});
|
|
100
|
+
it('trims whitespace from token', async () => {
|
|
101
|
+
const program = createProgram();
|
|
102
|
+
const tokenWithWhitespace = ' some_token_123456789 ';
|
|
103
|
+
const expectedToken = 'some_token_123456789';
|
|
104
|
+
mockSaveApiToken.mockResolvedValue(undefined);
|
|
105
|
+
await program.parseAsync(['node', 'tw', 'auth', 'token', tokenWithWhitespace]);
|
|
106
|
+
expect(mockSaveApiToken).toHaveBeenCalledWith(expectedToken);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe('status subcommand', () => {
|
|
110
|
+
it('shows authenticated status when logged in', async () => {
|
|
111
|
+
const program = createProgram();
|
|
112
|
+
const mockUser = {
|
|
113
|
+
id: 1,
|
|
114
|
+
name: 'Test User',
|
|
115
|
+
shortName: 'test',
|
|
116
|
+
bot: false,
|
|
117
|
+
timezone: 'UTC',
|
|
118
|
+
removed: false,
|
|
119
|
+
email: 'test@example.com',
|
|
120
|
+
lang: 'en',
|
|
121
|
+
};
|
|
122
|
+
mockGetSessionUser.mockResolvedValue(mockUser);
|
|
123
|
+
await program.parseAsync(['node', 'tw', 'auth', 'status']);
|
|
124
|
+
expect(mockGetSessionUser).toHaveBeenCalled();
|
|
125
|
+
expect(consoleSpy).toHaveBeenCalledWith('✓', 'Authenticated');
|
|
126
|
+
expect(consoleSpy).toHaveBeenCalledWith(' Email: test@example.com');
|
|
127
|
+
expect(consoleSpy).toHaveBeenCalledWith(' Name: Test User');
|
|
128
|
+
});
|
|
129
|
+
it('shows not authenticated when no token', async () => {
|
|
130
|
+
const program = createProgram();
|
|
131
|
+
mockGetSessionUser.mockRejectedValue(new Error('No API token found'));
|
|
132
|
+
await program.parseAsync(['node', 'tw', 'auth', 'status']);
|
|
133
|
+
expect(consoleSpy).toHaveBeenCalledWith('Not authenticated');
|
|
134
|
+
expect(consoleSpy).toHaveBeenCalledWith('Run `tw auth login` for OAuth or `tw auth token <token>` for manual authentication');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe('login subcommand', () => {
|
|
138
|
+
it('successfully completes OAuth flow with dynamic client registration', async () => {
|
|
139
|
+
const program = createProgram();
|
|
140
|
+
// Mock dynamic client registration
|
|
141
|
+
mockRegisterDynamicClient.mockResolvedValue({
|
|
142
|
+
client_id: 'twd_dynamic_client_id',
|
|
143
|
+
client_secret: 'dynamic_client_secret',
|
|
144
|
+
});
|
|
145
|
+
// Mock PKCE parameters
|
|
146
|
+
mockGenerateCodeVerifier.mockReturnValue('test_code_verifier');
|
|
147
|
+
mockGenerateCodeChallenge.mockReturnValue('test_code_challenge');
|
|
148
|
+
mockGenerateState.mockReturnValue('test_state');
|
|
149
|
+
// Mock authorization URL
|
|
150
|
+
mockBuildAuthorizationUrl.mockReturnValue('https://twist.com/oauth/authorize?...');
|
|
151
|
+
// Mock callback server that resolves immediately
|
|
152
|
+
const mockCleanup = vi.fn();
|
|
153
|
+
mockStartCallbackServer.mockImplementation(async (expectedState) => {
|
|
154
|
+
// Simulate the browser opening behavior by calling our mocks
|
|
155
|
+
mockBuildAuthorizationUrl('twd_dynamic_client_id', 'test_code_challenge', expectedState);
|
|
156
|
+
await mockOpen('https://twist.com/oauth/authorize?...');
|
|
157
|
+
return Promise.resolve({
|
|
158
|
+
code: 'auth_code_123',
|
|
159
|
+
cleanup: mockCleanup,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
// Mock token exchange
|
|
163
|
+
mockExchangeCodeForToken.mockResolvedValue('access_token_123');
|
|
164
|
+
// Mock browser opening
|
|
165
|
+
mockOpen.mockResolvedValue({});
|
|
166
|
+
// Mock token saving
|
|
167
|
+
mockSaveApiToken.mockResolvedValue(undefined);
|
|
168
|
+
await program.parseAsync(['node', 'tw', 'auth', 'login']);
|
|
169
|
+
// Verify dynamic client registration
|
|
170
|
+
expect(mockRegisterDynamicClient).toHaveBeenCalled();
|
|
171
|
+
// Verify PKCE parameters were generated
|
|
172
|
+
expect(mockGenerateCodeVerifier).toHaveBeenCalled();
|
|
173
|
+
expect(mockGenerateCodeChallenge).toHaveBeenCalledWith('test_code_verifier');
|
|
174
|
+
expect(mockGenerateState).toHaveBeenCalled();
|
|
175
|
+
// Verify authorization URL was built with dynamic client ID
|
|
176
|
+
expect(mockBuildAuthorizationUrl).toHaveBeenCalledWith('twd_dynamic_client_id', 'test_code_challenge', 'test_state');
|
|
177
|
+
// Verify callback server was started
|
|
178
|
+
expect(mockStartCallbackServer).toHaveBeenCalledWith('test_state');
|
|
179
|
+
// Verify browser was opened
|
|
180
|
+
expect(mockOpen).toHaveBeenCalledWith('https://twist.com/oauth/authorize?...');
|
|
181
|
+
// Verify token exchange with client credentials
|
|
182
|
+
expect(mockExchangeCodeForToken).toHaveBeenCalledWith('auth_code_123', 'test_code_verifier', {
|
|
183
|
+
client_id: 'twd_dynamic_client_id',
|
|
184
|
+
client_secret: 'dynamic_client_secret',
|
|
185
|
+
});
|
|
186
|
+
// Verify token was saved
|
|
187
|
+
expect(mockSaveApiToken).toHaveBeenCalledWith('access_token_123');
|
|
188
|
+
// Verify cleanup was called
|
|
189
|
+
expect(mockCleanup).toHaveBeenCalled();
|
|
190
|
+
// Verify success messages
|
|
191
|
+
expect(consoleSpy).toHaveBeenCalledWith('Starting OAuth authentication...');
|
|
192
|
+
expect(consoleSpy).toHaveBeenCalledWith('✓', 'OAuth authentication successful!');
|
|
193
|
+
});
|
|
194
|
+
it('handles callback server errors', async () => {
|
|
195
|
+
const program = createProgram();
|
|
196
|
+
// Mock dynamic client registration
|
|
197
|
+
mockRegisterDynamicClient.mockResolvedValue({
|
|
198
|
+
client_id: 'twd_dynamic_client_id',
|
|
199
|
+
client_secret: 'dynamic_client_secret',
|
|
200
|
+
});
|
|
201
|
+
// Mock PKCE parameters
|
|
202
|
+
mockGenerateCodeVerifier.mockReturnValue('test_code_verifier');
|
|
203
|
+
mockGenerateCodeChallenge.mockReturnValue('test_code_challenge');
|
|
204
|
+
mockGenerateState.mockReturnValue('test_state');
|
|
205
|
+
// Mock callback server error
|
|
206
|
+
mockStartCallbackServer.mockRejectedValue(new Error('Port 8766 is already in use'));
|
|
207
|
+
await program.parseAsync(['node', 'tw', 'auth', 'login']);
|
|
208
|
+
expect(consoleSpy).toHaveBeenCalledWith('✗', 'OAuth authentication failed');
|
|
209
|
+
expect(consoleSpy).toHaveBeenCalledWith('Port 8766 is already in use');
|
|
210
|
+
});
|
|
211
|
+
it('handles token exchange errors', async () => {
|
|
212
|
+
const program = createProgram();
|
|
213
|
+
// Mock dynamic client registration
|
|
214
|
+
mockRegisterDynamicClient.mockResolvedValue({
|
|
215
|
+
client_id: 'twd_dynamic_client_id',
|
|
216
|
+
client_secret: 'dynamic_client_secret',
|
|
217
|
+
});
|
|
218
|
+
// Mock PKCE parameters
|
|
219
|
+
mockGenerateCodeVerifier.mockReturnValue('test_code_verifier');
|
|
220
|
+
mockGenerateCodeChallenge.mockReturnValue('test_code_challenge');
|
|
221
|
+
mockGenerateState.mockReturnValue('test_state');
|
|
222
|
+
// Mock successful callback
|
|
223
|
+
const mockCleanup = vi.fn();
|
|
224
|
+
mockStartCallbackServer.mockResolvedValue({
|
|
225
|
+
code: 'auth_code_123',
|
|
226
|
+
cleanup: mockCleanup,
|
|
227
|
+
});
|
|
228
|
+
// Mock token exchange error
|
|
229
|
+
mockExchangeCodeForToken.mockRejectedValue(new Error('Invalid authorization code'));
|
|
230
|
+
await program.parseAsync(['node', 'tw', 'auth', 'login']);
|
|
231
|
+
expect(mockCleanup).toHaveBeenCalled();
|
|
232
|
+
expect(consoleSpy).toHaveBeenCalledWith('✗', 'OAuth authentication failed');
|
|
233
|
+
expect(consoleSpy).toHaveBeenCalledWith('Invalid authorization code');
|
|
234
|
+
});
|
|
235
|
+
it('handles browser opening errors gracefully', async () => {
|
|
236
|
+
const program = createProgram();
|
|
237
|
+
// Mock dynamic client registration
|
|
238
|
+
mockRegisterDynamicClient.mockResolvedValue({
|
|
239
|
+
client_id: 'twd_dynamic_client_id',
|
|
240
|
+
client_secret: 'dynamic_client_secret',
|
|
241
|
+
});
|
|
242
|
+
// Mock PKCE parameters
|
|
243
|
+
mockGenerateCodeVerifier.mockReturnValue('test_code_verifier');
|
|
244
|
+
mockGenerateCodeChallenge.mockReturnValue('test_code_challenge');
|
|
245
|
+
mockGenerateState.mockReturnValue('test_state');
|
|
246
|
+
// Mock callback server
|
|
247
|
+
const mockCleanup = vi.fn();
|
|
248
|
+
mockStartCallbackServer.mockResolvedValue({
|
|
249
|
+
code: 'auth_code_123',
|
|
250
|
+
cleanup: mockCleanup,
|
|
251
|
+
});
|
|
252
|
+
// Mock browser opening error
|
|
253
|
+
mockOpen.mockRejectedValue(new Error('No browser available'));
|
|
254
|
+
// Mock successful token exchange (flow should still continue)
|
|
255
|
+
mockExchangeCodeForToken.mockResolvedValue('access_token_123');
|
|
256
|
+
mockSaveApiToken.mockResolvedValue(undefined);
|
|
257
|
+
await program.parseAsync(['node', 'tw', 'auth', 'login']);
|
|
258
|
+
// Should still complete successfully despite browser error
|
|
259
|
+
expect(consoleSpy).toHaveBeenCalledWith('✓', 'OAuth authentication successful!');
|
|
260
|
+
});
|
|
261
|
+
it('calls cleanup when OAuth server throws', async () => {
|
|
262
|
+
const program = createProgram();
|
|
263
|
+
// Mock dynamic client registration
|
|
264
|
+
mockRegisterDynamicClient.mockResolvedValue({
|
|
265
|
+
client_id: 'twd_dynamic_client_id',
|
|
266
|
+
client_secret: 'dynamic_client_secret',
|
|
267
|
+
});
|
|
268
|
+
// Mock PKCE parameters
|
|
269
|
+
mockGenerateCodeVerifier.mockReturnValue('test_code_verifier');
|
|
270
|
+
mockGenerateCodeChallenge.mockReturnValue('test_code_challenge');
|
|
271
|
+
mockGenerateState.mockReturnValue('test_state');
|
|
272
|
+
// Mock server that throws an error
|
|
273
|
+
mockStartCallbackServer.mockRejectedValue(new Error('Server failed to start'));
|
|
274
|
+
await program.parseAsync(['node', 'tw', 'auth', 'login']);
|
|
275
|
+
// Should handle the error gracefully
|
|
276
|
+
expect(consoleSpy).toHaveBeenCalledWith('✗', 'OAuth authentication failed');
|
|
277
|
+
expect(consoleSpy).toHaveBeenCalledWith('Server failed to start');
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
describe('login subcommand with unconfigured client ID', () => {
|
|
281
|
+
// Note: Testing the unconfigured client ID scenario is complex with the current mock setup
|
|
282
|
+
// In practice, users would need to configure their client ID before OAuth works
|
|
283
|
+
it('would show error when client ID is not configured', () => {
|
|
284
|
+
// This test documents the expected behavior when TWIST_CLIENT_ID === 'YOUR_CLIENT_ID'
|
|
285
|
+
// The actual implementation checks this condition and shows an error message
|
|
286
|
+
expect(true).toBe(true); // Placeholder for documentation purposes
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
describe('logout subcommand', () => {
|
|
290
|
+
it('clears the API token', async () => {
|
|
291
|
+
const program = createProgram();
|
|
292
|
+
mockClearApiToken.mockResolvedValue(undefined);
|
|
293
|
+
await program.parseAsync(['node', 'tw', 'auth', 'logout']);
|
|
294
|
+
expect(mockClearApiToken).toHaveBeenCalled();
|
|
295
|
+
expect(consoleSpy).toHaveBeenCalledWith('✓', 'Logged out');
|
|
296
|
+
expect(consoleSpy).toHaveBeenCalledWith('Token removed from /home/user/.config/twist-cli/config.json');
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
//# sourceMappingURL=auth.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../src/__tests__/auth.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dates.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/lib/dates.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { formatRelativeDate, parseDate } from '../../lib/dates.js';
|
|
3
|
+
describe('formatRelativeDate', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.useFakeTimers();
|
|
6
|
+
vi.setSystemTime(new Date('2024-06-15T12:00:00Z'));
|
|
7
|
+
});
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
vi.useRealTimers();
|
|
10
|
+
});
|
|
11
|
+
it('returns "just now" for times less than a minute ago', () => {
|
|
12
|
+
const date = new Date('2024-06-15T11:59:30Z');
|
|
13
|
+
expect(formatRelativeDate(date)).toBe('just now');
|
|
14
|
+
});
|
|
15
|
+
it('returns minutes ago for times less than an hour ago', () => {
|
|
16
|
+
expect(formatRelativeDate(new Date('2024-06-15T11:55:00Z'))).toBe('5 minutes ago');
|
|
17
|
+
expect(formatRelativeDate(new Date('2024-06-15T11:59:00Z'))).toBe('1 minute ago');
|
|
18
|
+
expect(formatRelativeDate(new Date('2024-06-15T11:30:00Z'))).toBe('30 minutes ago');
|
|
19
|
+
});
|
|
20
|
+
it('returns hours ago for times less than a day ago', () => {
|
|
21
|
+
expect(formatRelativeDate(new Date('2024-06-15T10:00:00Z'))).toBe('2 hours ago');
|
|
22
|
+
expect(formatRelativeDate(new Date('2024-06-15T11:00:00Z'))).toBe('1 hour ago');
|
|
23
|
+
});
|
|
24
|
+
it('returns "yesterday" for times 1-2 days ago', () => {
|
|
25
|
+
expect(formatRelativeDate(new Date('2024-06-14T12:00:00Z'))).toBe('yesterday');
|
|
26
|
+
});
|
|
27
|
+
it('returns days ago for times less than a week ago', () => {
|
|
28
|
+
expect(formatRelativeDate(new Date('2024-06-12T12:00:00Z'))).toBe('3 days ago');
|
|
29
|
+
expect(formatRelativeDate(new Date('2024-06-10T12:00:00Z'))).toBe('5 days ago');
|
|
30
|
+
});
|
|
31
|
+
it('returns absolute date for older times', () => {
|
|
32
|
+
const result = formatRelativeDate(new Date('2024-05-01T12:00:00Z'));
|
|
33
|
+
expect(result).toMatch(/May 1/);
|
|
34
|
+
});
|
|
35
|
+
it('includes year for dates from previous years', () => {
|
|
36
|
+
const result = formatRelativeDate(new Date('2023-01-15T12:00:00Z'));
|
|
37
|
+
expect(result).toMatch(/Jan 15, 2023/);
|
|
38
|
+
});
|
|
39
|
+
it('handles string dates', () => {
|
|
40
|
+
expect(formatRelativeDate('2024-06-15T11:59:00Z')).toBe('1 minute ago');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('parseDate', () => {
|
|
44
|
+
it('parses valid ISO dates', () => {
|
|
45
|
+
const result = parseDate('2024-06-15T12:00:00Z');
|
|
46
|
+
expect(result).toBeInstanceOf(Date);
|
|
47
|
+
expect(result?.toISOString()).toBe('2024-06-15T12:00:00.000Z');
|
|
48
|
+
});
|
|
49
|
+
it('parses date-only strings', () => {
|
|
50
|
+
const result = parseDate('2024-06-15');
|
|
51
|
+
expect(result).toBeInstanceOf(Date);
|
|
52
|
+
});
|
|
53
|
+
it('returns null for invalid dates', () => {
|
|
54
|
+
expect(parseDate('invalid')).toBeNull();
|
|
55
|
+
expect(parseDate('')).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=dates.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dates.test.js","sourceRoot":"","sources":["../../../src/__tests__/lib/dates.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refs.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/lib/refs.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { extractId, isIdRef, parseRef, parseTwistUrl, resolveCommentId, resolveConversationId, resolveMessageId, resolveThreadId, } from '../../lib/refs.js';
|
|
3
|
+
describe('isIdRef', () => {
|
|
4
|
+
it('returns true for id: prefixed strings', () => {
|
|
5
|
+
expect(isIdRef('id:123')).toBe(true);
|
|
6
|
+
expect(isIdRef('id:456789')).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
it('returns false for non-id refs', () => {
|
|
9
|
+
expect(isIdRef('123')).toBe(false);
|
|
10
|
+
expect(isIdRef('workspace-name')).toBe(false);
|
|
11
|
+
expect(isIdRef('https://twist.com/a/123')).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
describe('extractId', () => {
|
|
15
|
+
it('extracts ID from id: prefix', () => {
|
|
16
|
+
expect(extractId('id:123')).toBe(123);
|
|
17
|
+
expect(extractId('id:456789')).toBe(456789);
|
|
18
|
+
});
|
|
19
|
+
it('parses bare numbers', () => {
|
|
20
|
+
expect(extractId('123')).toBe(123);
|
|
21
|
+
});
|
|
22
|
+
it('throws on invalid input', () => {
|
|
23
|
+
expect(() => extractId('invalid')).toThrow('Invalid ID');
|
|
24
|
+
expect(() => extractId('id:abc')).toThrow('Invalid ID');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe('parseTwistUrl', () => {
|
|
28
|
+
it('parses workspace URL', () => {
|
|
29
|
+
const result = parseTwistUrl('https://twist.com/a/12345');
|
|
30
|
+
expect(result).toEqual({ workspaceId: 12345 });
|
|
31
|
+
});
|
|
32
|
+
it('parses channel URL', () => {
|
|
33
|
+
const result = parseTwistUrl('https://twist.com/a/12345/ch/67890');
|
|
34
|
+
expect(result).toEqual({ workspaceId: 12345, channelId: 67890 });
|
|
35
|
+
});
|
|
36
|
+
it('parses thread URL', () => {
|
|
37
|
+
const result = parseTwistUrl('https://twist.com/a/12345/ch/67890/t/111');
|
|
38
|
+
expect(result).toEqual({ workspaceId: 12345, channelId: 67890, threadId: 111 });
|
|
39
|
+
});
|
|
40
|
+
it('parses thread with comment URL', () => {
|
|
41
|
+
const result = parseTwistUrl('https://twist.com/a/12345/ch/67890/t/111/c/222');
|
|
42
|
+
expect(result).toEqual({
|
|
43
|
+
workspaceId: 12345,
|
|
44
|
+
channelId: 67890,
|
|
45
|
+
threadId: 111,
|
|
46
|
+
commentId: 222,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
it('parses conversation URL', () => {
|
|
50
|
+
const result = parseTwistUrl('https://twist.com/a/12345/msg/333');
|
|
51
|
+
expect(result).toEqual({ workspaceId: 12345, conversationId: 333 });
|
|
52
|
+
});
|
|
53
|
+
it('parses message URL', () => {
|
|
54
|
+
const result = parseTwistUrl('https://twist.com/a/12345/msg/333/m/444');
|
|
55
|
+
expect(result).toEqual({ workspaceId: 12345, conversationId: 333, messageId: 444 });
|
|
56
|
+
});
|
|
57
|
+
it('returns null for non-twist URLs', () => {
|
|
58
|
+
expect(parseTwistUrl('https://google.com')).toBeNull();
|
|
59
|
+
expect(parseTwistUrl('https://example.com/a/123')).toBeNull();
|
|
60
|
+
});
|
|
61
|
+
it('returns null for invalid URLs', () => {
|
|
62
|
+
expect(parseTwistUrl('not-a-url')).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('parseRef', () => {
|
|
66
|
+
it('parses id: refs', () => {
|
|
67
|
+
expect(parseRef('id:123')).toEqual({ type: 'id', id: 123 });
|
|
68
|
+
});
|
|
69
|
+
it('parses bare numbers', () => {
|
|
70
|
+
expect(parseRef('456')).toEqual({ type: 'id', id: 456 });
|
|
71
|
+
});
|
|
72
|
+
it('parses URLs', () => {
|
|
73
|
+
const result = parseRef('https://twist.com/a/12345/ch/67890/t/111');
|
|
74
|
+
expect(result).toEqual({
|
|
75
|
+
type: 'url',
|
|
76
|
+
parsed: { workspaceId: 12345, channelId: 67890, threadId: 111 },
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
it('parses names', () => {
|
|
80
|
+
expect(parseRef('My Workspace')).toEqual({ type: 'name', name: 'My Workspace' });
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe('resolveThreadId', () => {
|
|
84
|
+
it('resolves id: refs', () => {
|
|
85
|
+
expect(resolveThreadId('id:123')).toBe(123);
|
|
86
|
+
});
|
|
87
|
+
it('resolves bare numbers', () => {
|
|
88
|
+
expect(resolveThreadId('456')).toBe(456);
|
|
89
|
+
});
|
|
90
|
+
it('resolves thread URLs', () => {
|
|
91
|
+
expect(resolveThreadId('https://twist.com/a/12345/ch/67890/t/111')).toBe(111);
|
|
92
|
+
});
|
|
93
|
+
it('throws on invalid refs', () => {
|
|
94
|
+
expect(() => resolveThreadId('invalid-name')).toThrow('Invalid thread reference');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('resolveCommentId', () => {
|
|
98
|
+
it('resolves id: refs', () => {
|
|
99
|
+
expect(resolveCommentId('id:222')).toBe(222);
|
|
100
|
+
});
|
|
101
|
+
it('resolves comment URLs', () => {
|
|
102
|
+
expect(resolveCommentId('https://twist.com/a/12345/ch/67890/t/111/c/222')).toBe(222);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe('resolveConversationId', () => {
|
|
106
|
+
it('resolves id: refs', () => {
|
|
107
|
+
expect(resolveConversationId('id:333')).toBe(333);
|
|
108
|
+
});
|
|
109
|
+
it('resolves conversation URLs', () => {
|
|
110
|
+
expect(resolveConversationId('https://twist.com/a/12345/msg/333')).toBe(333);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('resolveMessageId', () => {
|
|
114
|
+
it('resolves id: refs', () => {
|
|
115
|
+
expect(resolveMessageId('id:444')).toBe(444);
|
|
116
|
+
});
|
|
117
|
+
it('resolves message URLs', () => {
|
|
118
|
+
expect(resolveMessageId('https://twist.com/a/12345/msg/333/m/444')).toBe(444);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=refs.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refs.test.js","sourceRoot":"","sources":["../../../src/__tests__/lib/refs.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spinner.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/spinner.test.ts"],"names":[],"mappings":""}
|