paean 0.1.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/LICENSE +21 -0
- package/README.md +296 -0
- package/dist/api/auth.d.ts +71 -0
- package/dist/api/auth.d.ts.map +1 -0
- package/dist/api/auth.js +257 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/client.d.ts +37 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +101 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/todo.d.ts +167 -0
- package/dist/api/todo.d.ts.map +1 -0
- package/dist/api/todo.js +137 -0
- package/dist/api/todo.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +36 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/context.d.ts +7 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +155 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/login.d.ts +7 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +108 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +7 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +27 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/serve.d.ts +7 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +29 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/tasks.d.ts +7 -0
- package/dist/commands/tasks.d.ts.map +1 -0
- package/dist/commands/tasks.js +276 -0
- package/dist/commands/tasks.js.map +1 -0
- package/dist/commands/validate.d.ts +7 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +123 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/resources.d.ts +14 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +166 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts +12 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +82 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +14 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +341 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/utils/config.d.ts +69 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +129 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/output.d.ts +79 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +157 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/project.d.ts +29 -0
- package/dist/utils/project.d.ts.map +1 -0
- package/dist/utils/project.js +135 -0
- package/dist/utils/project.js.map +1 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Paean AI (Zero One Intelligence)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Paean CLI
|
|
2
|
+
|
|
3
|
+
> Connect your local development environment with Paean AI cloud for intelligent task management and AI agent workflows.
|
|
4
|
+
|
|
5
|
+
**Paean CLI** is a dual-mode command-line tool that serves both as:
|
|
6
|
+
- A **human-friendly CLI** for developers to manage tasks and authenticate
|
|
7
|
+
- An **MCP (Model Context Protocol) server** for AI agent integration with Cursor, Claude Code, and other AI coding assistants
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Task Management**: View, create, complete, and manage tasks from the command line
|
|
12
|
+
- **MCP Server Mode**: Seamless integration with AI coding assistants via MCP protocol
|
|
13
|
+
- **Dual Authentication**: Support for both QR code login and browser-based OAuth
|
|
14
|
+
- **Context Generation**: Generate context files for AI agents with current task information
|
|
15
|
+
- **Project Detection**: Automatically detects project type and associates tasks
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Install globally via npm
|
|
21
|
+
npm install -g paean
|
|
22
|
+
|
|
23
|
+
# Or use directly with npx
|
|
24
|
+
npx paean --help
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Authenticate
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Browser-based login (recommended)
|
|
33
|
+
paean login
|
|
34
|
+
|
|
35
|
+
# QR code login (scan with Paean mobile app)
|
|
36
|
+
paean login --qr
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. View Tasks
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# List all tasks
|
|
43
|
+
paean tasks
|
|
44
|
+
|
|
45
|
+
# List pending tasks only
|
|
46
|
+
paean tasks --status pending
|
|
47
|
+
|
|
48
|
+
# Output as JSON
|
|
49
|
+
paean tasks --json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Manage Tasks
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Create a new task
|
|
56
|
+
paean tasks add "Implement user authentication"
|
|
57
|
+
|
|
58
|
+
# Complete a task
|
|
59
|
+
paean tasks complete <task-id> --summary "Added JWT authentication"
|
|
60
|
+
|
|
61
|
+
# Update task priority
|
|
62
|
+
paean tasks update <task-id> --priority high
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 4. Generate Context for AI
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Generate context file
|
|
69
|
+
paean context
|
|
70
|
+
|
|
71
|
+
# Output to stdout
|
|
72
|
+
paean context --stdout
|
|
73
|
+
|
|
74
|
+
# Output as JSON
|
|
75
|
+
paean context --json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## MCP Server Mode
|
|
79
|
+
|
|
80
|
+
Paean CLI can run as an MCP server, allowing AI coding assistants to read tasks and update their status.
|
|
81
|
+
|
|
82
|
+
### Configure in Cursor
|
|
83
|
+
|
|
84
|
+
Add to your `.cursor/mcp.json`:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"mcpServers": {
|
|
89
|
+
"paean": {
|
|
90
|
+
"command": "npx",
|
|
91
|
+
"args": ["paean", "serve"]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Configure in Claude Desktop
|
|
98
|
+
|
|
99
|
+
Add to your Claude Desktop config:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"mcpServers": {
|
|
104
|
+
"paean": {
|
|
105
|
+
"command": "npx",
|
|
106
|
+
"args": ["paean", "serve"]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Available MCP Resources
|
|
113
|
+
|
|
114
|
+
| Resource URI | Description |
|
|
115
|
+
|-------------|-------------|
|
|
116
|
+
| `paean://tasks/pending` | Current pending tasks |
|
|
117
|
+
| `paean://tasks/completed` | Recently completed tasks |
|
|
118
|
+
| `paean://tasks/all` | All tasks with full details |
|
|
119
|
+
| `paean://context` | Full project context |
|
|
120
|
+
| `paean://pending-changes` | AI-suggested changes awaiting review |
|
|
121
|
+
|
|
122
|
+
### Available MCP Tools
|
|
123
|
+
|
|
124
|
+
| Tool | Description |
|
|
125
|
+
|------|-------------|
|
|
126
|
+
| `paean_complete_task` | Mark a task as completed |
|
|
127
|
+
| `paean_create_task` | Create a new task |
|
|
128
|
+
| `paean_update_task` | Update task status/priority |
|
|
129
|
+
| `paean_list_tasks` | List tasks with filters |
|
|
130
|
+
| `paean_accept_change` | Accept an AI-suggested change |
|
|
131
|
+
| `paean_reject_change` | Reject an AI-suggested change |
|
|
132
|
+
|
|
133
|
+
## Commands Reference
|
|
134
|
+
|
|
135
|
+
### `paean login`
|
|
136
|
+
|
|
137
|
+
Authenticate with Paean AI.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
paean login # Browser-based login
|
|
141
|
+
paean login --qr # QR code login
|
|
142
|
+
paean login --check # Check current auth status
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### `paean logout`
|
|
146
|
+
|
|
147
|
+
Sign out and clear stored credentials.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
paean logout
|
|
151
|
+
paean logout --force # Skip confirmation
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### `paean tasks`
|
|
155
|
+
|
|
156
|
+
View and manage tasks.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
paean tasks # List all tasks
|
|
160
|
+
paean tasks --status pending # Filter by status
|
|
161
|
+
paean tasks --priority high # Filter by priority
|
|
162
|
+
paean tasks --json # JSON output
|
|
163
|
+
|
|
164
|
+
paean tasks add "Task description" # Create task
|
|
165
|
+
paean tasks complete <id> # Complete task
|
|
166
|
+
paean tasks update <id> # Update task
|
|
167
|
+
paean tasks delete <id> # Delete task
|
|
168
|
+
|
|
169
|
+
paean tasks pending # View AI-suggested changes
|
|
170
|
+
paean tasks accept <changeId> # Accept change
|
|
171
|
+
paean tasks reject <changeId> # Reject change
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### `paean context`
|
|
175
|
+
|
|
176
|
+
Generate context file for AI agents.
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
paean context # Write to .paean/context.md
|
|
180
|
+
paean context --output custom.md # Custom output path
|
|
181
|
+
paean context --json # JSON format
|
|
182
|
+
paean context --stdout # Print to stdout
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### `paean serve`
|
|
186
|
+
|
|
187
|
+
Start MCP server for AI agent integration.
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
paean serve # Start MCP server (stdio)
|
|
191
|
+
paean serve --debug # Enable debug logging
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### `paean validate`
|
|
195
|
+
|
|
196
|
+
Check if current changes satisfy pending tasks.
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
paean validate # Validate pending tasks
|
|
200
|
+
paean validate --auto-complete # Auto-complete validated tasks
|
|
201
|
+
paean validate --json # JSON output
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Configuration
|
|
205
|
+
|
|
206
|
+
Configuration is stored in `~/.paean/config.json`:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"token": "your-jwt-token",
|
|
211
|
+
"email": "user@example.com",
|
|
212
|
+
"apiUrl": "https://api.paean.ai",
|
|
213
|
+
"webUrl": "https://m.paean.ai",
|
|
214
|
+
"defaultPriority": "medium",
|
|
215
|
+
"outputFormat": "table"
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
View config path:
|
|
220
|
+
```bash
|
|
221
|
+
paean --config
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Programmatic Usage
|
|
225
|
+
|
|
226
|
+
Paean CLI can also be used as a library:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import {
|
|
230
|
+
qrLogin,
|
|
231
|
+
browserLogin,
|
|
232
|
+
getTodoList,
|
|
233
|
+
completeTodoItem,
|
|
234
|
+
startMcpServer,
|
|
235
|
+
} from 'paean';
|
|
236
|
+
|
|
237
|
+
// Authenticate
|
|
238
|
+
const result = await browserLogin();
|
|
239
|
+
if (result.success) {
|
|
240
|
+
// Get tasks
|
|
241
|
+
const tasks = await getTodoList({ status: 'pending' });
|
|
242
|
+
console.log(tasks.data.items);
|
|
243
|
+
|
|
244
|
+
// Complete a task
|
|
245
|
+
await completeTodoItem('task-id', 'Completed the feature');
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## A2A Workflow
|
|
250
|
+
|
|
251
|
+
Paean CLI enables an Agent-to-Agent (A2A) workflow:
|
|
252
|
+
|
|
253
|
+
1. **Manager (Paean AI Cloud)**: Creates and organizes tasks based on business requirements
|
|
254
|
+
2. **Worker (Claude/Cursor)**: Reads tasks via MCP, implements changes, reports completion
|
|
255
|
+
3. **Reviewer (Human)**: Reviews AI suggestions and approves changes
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
259
|
+
│ Paean AI │────▶│ Paean CLI │────▶│ Cursor/Claude │
|
|
260
|
+
│ (Manager) │ │ (MCP Bridge) │ │ (Worker) │
|
|
261
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
262
|
+
│ │ │
|
|
263
|
+
│ Create tasks │ Read via MCP │
|
|
264
|
+
│ │ Complete tasks │
|
|
265
|
+
▼ ▼ ▼
|
|
266
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
267
|
+
│ Human (Reviewer) │
|
|
268
|
+
│ Reviews changes, accepts/rejects suggestions │
|
|
269
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Security
|
|
273
|
+
|
|
274
|
+
- **Local Storage**: Credentials are stored locally in `~/.paean/`
|
|
275
|
+
- **Localhost Only**: Browser OAuth callbacks only accept localhost URLs
|
|
276
|
+
- **Token-Based**: Uses JWT tokens that expire and can be revoked
|
|
277
|
+
- **No Password Storage**: Passwords are never stored locally
|
|
278
|
+
|
|
279
|
+
## Requirements
|
|
280
|
+
|
|
281
|
+
- Node.js 18.0.0 or higher
|
|
282
|
+
- npm or yarn
|
|
283
|
+
|
|
284
|
+
## Contributing
|
|
285
|
+
|
|
286
|
+
Contributions are welcome! Please see our contributing guidelines.
|
|
287
|
+
|
|
288
|
+
## License
|
|
289
|
+
|
|
290
|
+
MIT License - see LICENSE file for details.
|
|
291
|
+
|
|
292
|
+
## Links
|
|
293
|
+
|
|
294
|
+
- **Website**: https://paean.ai
|
|
295
|
+
- **API Documentation**: https://api.paean.ai/docs
|
|
296
|
+
- **Support**: support@paean.ai
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication API
|
|
3
|
+
* Handles QR code login and browser OAuth flows
|
|
4
|
+
*/
|
|
5
|
+
export interface QrSessionResponse {
|
|
6
|
+
success: boolean;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
expiresAt: string;
|
|
9
|
+
expiresInSeconds: number;
|
|
10
|
+
qrContent: string;
|
|
11
|
+
}
|
|
12
|
+
export interface QrStatusResponse {
|
|
13
|
+
success: boolean;
|
|
14
|
+
status: 'pending' | 'scanned' | 'confirmed' | 'expired' | 'used';
|
|
15
|
+
token?: string;
|
|
16
|
+
userId?: number;
|
|
17
|
+
isExpired?: boolean;
|
|
18
|
+
expiresAt?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface LoginResponse {
|
|
21
|
+
user: {
|
|
22
|
+
id: number;
|
|
23
|
+
email: string;
|
|
24
|
+
username?: string;
|
|
25
|
+
displayName?: string;
|
|
26
|
+
};
|
|
27
|
+
token: string;
|
|
28
|
+
}
|
|
29
|
+
export interface UserInfo {
|
|
30
|
+
id: number;
|
|
31
|
+
email: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
picture?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create a QR login session
|
|
37
|
+
*/
|
|
38
|
+
export declare function createQrSession(deviceType?: string): Promise<QrSessionResponse>;
|
|
39
|
+
/**
|
|
40
|
+
* Check QR session status
|
|
41
|
+
*/
|
|
42
|
+
export declare function getQrSessionStatus(sessionId: string): Promise<QrStatusResponse>;
|
|
43
|
+
/**
|
|
44
|
+
* Perform QR code login
|
|
45
|
+
* Displays QR code in terminal and polls for confirmation
|
|
46
|
+
*/
|
|
47
|
+
export declare function qrLogin(onStatus?: (status: string) => void): Promise<{
|
|
48
|
+
success: boolean;
|
|
49
|
+
error?: string;
|
|
50
|
+
}>;
|
|
51
|
+
/**
|
|
52
|
+
* Perform browser-based OAuth login
|
|
53
|
+
* Opens browser and starts local server to receive callback
|
|
54
|
+
*/
|
|
55
|
+
export declare function browserLogin(): Promise<{
|
|
56
|
+
success: boolean;
|
|
57
|
+
error?: string;
|
|
58
|
+
}>;
|
|
59
|
+
/**
|
|
60
|
+
* Get current user info
|
|
61
|
+
*/
|
|
62
|
+
export declare function getCurrentUser(): Promise<UserInfo | null>;
|
|
63
|
+
/**
|
|
64
|
+
* Validate current token
|
|
65
|
+
*/
|
|
66
|
+
export declare function validateToken(): Promise<boolean>;
|
|
67
|
+
/**
|
|
68
|
+
* Logout - clear stored credentials
|
|
69
|
+
*/
|
|
70
|
+
export declare function logout(): void;
|
|
71
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,UAAU,SAAQ,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAOpF;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAIrF;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAClC,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAiF/C;AAED;;;GAGG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA2HlF;AAED;;GAEG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAc/D;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAOtD;AAED;;GAEG;AACH,wBAAgB,MAAM,IAAI,IAAI,CAG7B"}
|
package/dist/api/auth.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication API
|
|
3
|
+
* Handles QR code login and browser OAuth flows
|
|
4
|
+
*/
|
|
5
|
+
import { createServer } from 'http';
|
|
6
|
+
import { URL } from 'url';
|
|
7
|
+
import open from 'open';
|
|
8
|
+
import qrcode from 'qrcode-terminal';
|
|
9
|
+
import { getPublicApiClient, getApiClient, resetApiClients } from './client.js';
|
|
10
|
+
import { storeAuth, clearAuth, getWebUrl } from '../utils/config.js';
|
|
11
|
+
import * as output from '../utils/output.js';
|
|
12
|
+
/**
|
|
13
|
+
* Create a QR login session
|
|
14
|
+
*/
|
|
15
|
+
export async function createQrSession(deviceType = 'CLI') {
|
|
16
|
+
const client = getPublicApiClient();
|
|
17
|
+
const response = await client.post('/auth/qr/create-session', {
|
|
18
|
+
deviceType,
|
|
19
|
+
deviceId: `cli-${Date.now()}`,
|
|
20
|
+
});
|
|
21
|
+
return response.data;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check QR session status
|
|
25
|
+
*/
|
|
26
|
+
export async function getQrSessionStatus(sessionId) {
|
|
27
|
+
const client = getPublicApiClient();
|
|
28
|
+
const response = await client.get(`/auth/qr/status/${sessionId}`);
|
|
29
|
+
return response.data;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Perform QR code login
|
|
33
|
+
* Displays QR code in terminal and polls for confirmation
|
|
34
|
+
*/
|
|
35
|
+
export async function qrLogin(onStatus) {
|
|
36
|
+
try {
|
|
37
|
+
// Create QR session
|
|
38
|
+
const session = await createQrSession();
|
|
39
|
+
// Display QR code in terminal
|
|
40
|
+
output.newline();
|
|
41
|
+
output.info('Scan this QR code with the Paean mobile app to log in:');
|
|
42
|
+
output.newline();
|
|
43
|
+
qrcode.generate(session.qrContent, { small: true });
|
|
44
|
+
output.newline();
|
|
45
|
+
output.dim(`Session expires in ${Math.floor(session.expiresInSeconds / 60)} minutes`);
|
|
46
|
+
output.newline();
|
|
47
|
+
// Poll for status
|
|
48
|
+
const maxPollTime = session.expiresInSeconds * 1000;
|
|
49
|
+
const pollInterval = 2000;
|
|
50
|
+
const startTime = Date.now();
|
|
51
|
+
while (Date.now() - startTime < maxPollTime) {
|
|
52
|
+
const status = await getQrSessionStatus(session.sessionId);
|
|
53
|
+
if (onStatus) {
|
|
54
|
+
onStatus(status.status);
|
|
55
|
+
}
|
|
56
|
+
switch (status.status) {
|
|
57
|
+
case 'scanned':
|
|
58
|
+
output.info('QR code scanned! Waiting for confirmation...');
|
|
59
|
+
break;
|
|
60
|
+
case 'confirmed':
|
|
61
|
+
if (status.token && status.userId) {
|
|
62
|
+
// Store authentication
|
|
63
|
+
storeAuth({
|
|
64
|
+
token: status.token,
|
|
65
|
+
userId: status.userId,
|
|
66
|
+
});
|
|
67
|
+
resetApiClients();
|
|
68
|
+
// Try to fetch user info
|
|
69
|
+
try {
|
|
70
|
+
const userInfo = await getCurrentUser();
|
|
71
|
+
if (userInfo) {
|
|
72
|
+
storeAuth({
|
|
73
|
+
token: status.token,
|
|
74
|
+
userId: status.userId,
|
|
75
|
+
email: userInfo.email,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Ignore - we still have the token
|
|
81
|
+
}
|
|
82
|
+
return { success: true };
|
|
83
|
+
}
|
|
84
|
+
return { success: false, error: 'No token received' };
|
|
85
|
+
case 'expired':
|
|
86
|
+
return { success: false, error: 'QR code expired. Please try again.' };
|
|
87
|
+
case 'used':
|
|
88
|
+
return { success: false, error: 'This QR code has already been used.' };
|
|
89
|
+
case 'pending':
|
|
90
|
+
default:
|
|
91
|
+
// Continue polling
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
// Wait before next poll
|
|
95
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
96
|
+
}
|
|
97
|
+
return { success: false, error: 'Login timed out. Please try again.' };
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
101
|
+
return { success: false, error: message };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Perform browser-based OAuth login
|
|
106
|
+
* Opens browser and starts local server to receive callback
|
|
107
|
+
*/
|
|
108
|
+
export async function browserLogin() {
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
// Find an available port
|
|
111
|
+
const port = 9876 + Math.floor(Math.random() * 100);
|
|
112
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
113
|
+
let server = null;
|
|
114
|
+
let timeoutId = null;
|
|
115
|
+
const cleanup = () => {
|
|
116
|
+
if (timeoutId) {
|
|
117
|
+
clearTimeout(timeoutId);
|
|
118
|
+
timeoutId = null;
|
|
119
|
+
}
|
|
120
|
+
if (server) {
|
|
121
|
+
server.close();
|
|
122
|
+
server = null;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
// Create local server to receive callback
|
|
126
|
+
server = createServer((req, res) => {
|
|
127
|
+
const url = new URL(req.url || '/', `http://localhost:${port}`);
|
|
128
|
+
if (url.pathname === '/callback') {
|
|
129
|
+
const token = url.searchParams.get('token');
|
|
130
|
+
const error = url.searchParams.get('error');
|
|
131
|
+
const userId = url.searchParams.get('userId');
|
|
132
|
+
const email = url.searchParams.get('email');
|
|
133
|
+
// Send response to browser
|
|
134
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
135
|
+
if (token) {
|
|
136
|
+
res.end(`
|
|
137
|
+
<!DOCTYPE html>
|
|
138
|
+
<html>
|
|
139
|
+
<head>
|
|
140
|
+
<title>Login Successful</title>
|
|
141
|
+
<style>
|
|
142
|
+
body { font-family: system-ui; text-align: center; padding: 50px; background: #0a0a0a; color: #fff; }
|
|
143
|
+
.success { color: #22c55e; font-size: 48px; }
|
|
144
|
+
h1 { color: #06b6d4; }
|
|
145
|
+
</style>
|
|
146
|
+
</head>
|
|
147
|
+
<body>
|
|
148
|
+
<div class="success">✓</div>
|
|
149
|
+
<h1>Login Successful!</h1>
|
|
150
|
+
<p>You can close this window and return to the terminal.</p>
|
|
151
|
+
</body>
|
|
152
|
+
</html>
|
|
153
|
+
`);
|
|
154
|
+
// Store authentication
|
|
155
|
+
storeAuth({
|
|
156
|
+
token,
|
|
157
|
+
userId: userId ? parseInt(userId, 10) : undefined,
|
|
158
|
+
email: email || undefined,
|
|
159
|
+
});
|
|
160
|
+
resetApiClients();
|
|
161
|
+
cleanup();
|
|
162
|
+
resolve({ success: true });
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
res.end(`
|
|
166
|
+
<!DOCTYPE html>
|
|
167
|
+
<html>
|
|
168
|
+
<head>
|
|
169
|
+
<title>Login Failed</title>
|
|
170
|
+
<style>
|
|
171
|
+
body { font-family: system-ui; text-align: center; padding: 50px; background: #0a0a0a; color: #fff; }
|
|
172
|
+
.error { color: #ef4444; font-size: 48px; }
|
|
173
|
+
h1 { color: #ef4444; }
|
|
174
|
+
</style>
|
|
175
|
+
</head>
|
|
176
|
+
<body>
|
|
177
|
+
<div class="error">✗</div>
|
|
178
|
+
<h1>Login Failed</h1>
|
|
179
|
+
<p>${error || 'An error occurred during login.'}</p>
|
|
180
|
+
<p>Please return to the terminal and try again.</p>
|
|
181
|
+
</body>
|
|
182
|
+
</html>
|
|
183
|
+
`);
|
|
184
|
+
cleanup();
|
|
185
|
+
resolve({ success: false, error: error || 'Login failed' });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
res.writeHead(404);
|
|
190
|
+
res.end('Not found');
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
server.on('error', (err) => {
|
|
194
|
+
cleanup();
|
|
195
|
+
resolve({ success: false, error: `Server error: ${err.message}` });
|
|
196
|
+
});
|
|
197
|
+
server.listen(port, async () => {
|
|
198
|
+
// Open browser to login page
|
|
199
|
+
const webUrl = getWebUrl();
|
|
200
|
+
const loginUrl = `${webUrl}/auth/cli?callback=${encodeURIComponent(callbackUrl)}`;
|
|
201
|
+
output.info('Opening browser for login...');
|
|
202
|
+
output.dim(`If browser doesn't open, visit: ${loginUrl}`);
|
|
203
|
+
output.newline();
|
|
204
|
+
try {
|
|
205
|
+
await open(loginUrl);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
output.warning('Could not open browser automatically.');
|
|
209
|
+
output.info(`Please open this URL manually: ${loginUrl}`);
|
|
210
|
+
}
|
|
211
|
+
output.info('Waiting for login...');
|
|
212
|
+
// Set timeout (5 minutes)
|
|
213
|
+
timeoutId = setTimeout(() => {
|
|
214
|
+
cleanup();
|
|
215
|
+
resolve({ success: false, error: 'Login timed out. Please try again.' });
|
|
216
|
+
}, 5 * 60 * 1000);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get current user info
|
|
222
|
+
*/
|
|
223
|
+
export async function getCurrentUser() {
|
|
224
|
+
try {
|
|
225
|
+
const client = getApiClient();
|
|
226
|
+
const response = await client.get('/user/profile');
|
|
227
|
+
// Handle both response formats
|
|
228
|
+
const data = response.data;
|
|
229
|
+
if ('user' in data) {
|
|
230
|
+
return data.user;
|
|
231
|
+
}
|
|
232
|
+
return data;
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Validate current token
|
|
240
|
+
*/
|
|
241
|
+
export async function validateToken() {
|
|
242
|
+
try {
|
|
243
|
+
const user = await getCurrentUser();
|
|
244
|
+
return user !== null;
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Logout - clear stored credentials
|
|
252
|
+
*/
|
|
253
|
+
export function logout() {
|
|
254
|
+
clearAuth();
|
|
255
|
+
resetApiClients();
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"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"}
|