@clawapps/cli 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 +148 -0
- package/bin/claw.js +2 -0
- package/dist/auth/apple.d.ts +5 -0
- package/dist/auth/apple.js +15 -0
- package/dist/auth/apple.js.map +1 -0
- package/dist/auth/exchange.d.ts +6 -0
- package/dist/auth/exchange.js +25 -0
- package/dist/auth/exchange.js.map +1 -0
- package/dist/auth/google.d.ts +5 -0
- package/dist/auth/google.js +16 -0
- package/dist/auth/google.js.map +1 -0
- package/dist/auth/login-server.d.ts +10 -0
- package/dist/auth/login-server.js +62 -0
- package/dist/auth/login-server.js.map +1 -0
- package/dist/auth/server.d.ts +17 -0
- package/dist/auth/server.js +102 -0
- package/dist/auth/server.js.map +1 -0
- package/dist/commands/credit.d.ts +1 -0
- package/dist/commands/credit.js +28 -0
- package/dist/commands/credit.js.map +1 -0
- package/dist/commands/helpers/ensure-token.d.ts +6 -0
- package/dist/commands/helpers/ensure-token.js +29 -0
- package/dist/commands/helpers/ensure-token.js.map +1 -0
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.js +77 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +24 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/membership.d.ts +1 -0
- package/dist/commands/membership.js +28 -0
- package/dist/commands/membership.js.map +1 -0
- package/dist/commands/token.d.ts +1 -0
- package/dist/commands/token.js +17 -0
- package/dist/commands/token.js.map +1 -0
- package/dist/commands/whoami.d.ts +1 -0
- package/dist/commands/whoami.js +59 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/html/callback.d.ts +12 -0
- package/dist/html/callback.js +246 -0
- package/dist/html/callback.js.map +1 -0
- package/dist/html/logo-data.d.ts +1 -0
- package/dist/html/logo-data.js +2 -0
- package/dist/html/logo-data.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api.d.ts +13 -0
- package/dist/lib/api.js +26 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/config.d.ts +15 -0
- package/dist/lib/config.js +21 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/credentials.d.ts +4 -0
- package/dist/lib/credentials.js +35 -0
- package/dist/lib/credentials.js.map +1 -0
- package/dist/lib/types.d.ts +24 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ClawApps
|
|
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,148 @@
|
|
|
1
|
+
# @clawapps/cli
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://nodejs.org/)
|
|
5
|
+
|
|
6
|
+
**[中文文档](README_zh.md)**
|
|
7
|
+
|
|
8
|
+
A command-line tool for authenticating with the [ClawApps](https://www.clawapps.ai) platform. Sign in via Google or Apple directly from your terminal — tokens are stored locally for use by AI agents and scripts.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @clawapps/cli
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
> **Not yet on npm?** Install from source:
|
|
17
|
+
> ```bash
|
|
18
|
+
> git clone git@github.com:ClawApps/clawapps-cli.git
|
|
19
|
+
> cd clawapps-cli && npm install && npm run build && npm link
|
|
20
|
+
> ```
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
|
|
24
|
+
### `claw login`
|
|
25
|
+
|
|
26
|
+
Sign in with Google or Apple. Opens a browser for OAuth, then stores tokens locally.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
$ claw login
|
|
30
|
+
? Choose login method: Google
|
|
31
|
+
Opening browser for Google login...
|
|
32
|
+
✔ Logged in as user@gmail.com
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### `claw whoami`
|
|
36
|
+
|
|
37
|
+
Show current account info. Auto-refreshes expired tokens.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
$ claw whoami
|
|
41
|
+
ClawApps Account
|
|
42
|
+
──────────────────────────────
|
|
43
|
+
Name: Username
|
|
44
|
+
Email: user@gmail.com
|
|
45
|
+
ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
46
|
+
Provider: google
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### `claw logout`
|
|
50
|
+
|
|
51
|
+
Sign out and clear local credentials.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
$ claw logout
|
|
55
|
+
Logged out successfully.
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## How It Works
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
claw login
|
|
62
|
+
→ Choose Google or Apple
|
|
63
|
+
→ Local HTTP server starts on localhost (random port)
|
|
64
|
+
→ Browser opens for OAuth
|
|
65
|
+
→ Callback returns token to local server
|
|
66
|
+
→ Token exchange: Google/Apple → OpenDigits → ClawApps
|
|
67
|
+
→ Credentials saved to ~/.clawapps/credentials.json (0600)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Google flow**: Implicit OAuth → local callback page extracts token from URL hash → POST to local server → exchange for ClawApps tokens.
|
|
71
|
+
|
|
72
|
+
**Apple flow**: OpenDigits handles Apple OAuth → redirects to local callback with tokens in query params → exchange for ClawApps tokens.
|
|
73
|
+
|
|
74
|
+
## Credentials
|
|
75
|
+
|
|
76
|
+
Tokens are stored at `~/.clawapps/credentials.json` with file permissions `0600`.
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"provider": "google",
|
|
81
|
+
"access_token": "eyJ...",
|
|
82
|
+
"refresh_token": "eyJ...",
|
|
83
|
+
"logged_in_at": "2026-02-24T11:11:35.871Z"
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Use in scripts:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
TOKEN=$(cat ~/.clawapps/credentials.json | jq -r .access_token)
|
|
91
|
+
curl -H "Authorization: Bearer $TOKEN" https://api.clawapps.ai/api/v1/...
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Project Structure
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
clawapps-cli/
|
|
98
|
+
├── bin/claw.js # Entry point
|
|
99
|
+
├── src/
|
|
100
|
+
│ ├── index.ts # Commander setup
|
|
101
|
+
│ ├── commands/
|
|
102
|
+
│ │ ├── login.ts # OAuth flow orchestration
|
|
103
|
+
│ │ ├── logout.ts # Clear credentials
|
|
104
|
+
│ │ └── whoami.ts # User info with auto-refresh
|
|
105
|
+
│ ├── auth/
|
|
106
|
+
│ │ ├── server.ts # Local HTTP callback server
|
|
107
|
+
│ │ ├── google.ts # Google OAuth URL builder
|
|
108
|
+
│ │ ├── apple.ts # Apple OAuth URL builder (via OD)
|
|
109
|
+
│ │ └── exchange.ts # Token exchange (OD → ClawApps)
|
|
110
|
+
│ ├── lib/
|
|
111
|
+
│ │ ├── config.ts # API endpoints & constants
|
|
112
|
+
│ │ ├── credentials.ts # Read/write ~/.clawapps/credentials.json
|
|
113
|
+
│ │ ├── api.ts # HTTP request helpers
|
|
114
|
+
│ │ └── types.ts # TypeScript interfaces
|
|
115
|
+
│ └── html/
|
|
116
|
+
│ ├── callback.ts # OAuth callback HTML templates
|
|
117
|
+
│ └── logo-data.ts # Logo (base64 embedded)
|
|
118
|
+
├── package.json
|
|
119
|
+
└── tsconfig.json
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Development
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npm install # Install dependencies
|
|
126
|
+
npm run build # Compile TypeScript
|
|
127
|
+
npm run dev # Watch mode
|
|
128
|
+
node bin/claw.js # Run locally
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Requirements
|
|
132
|
+
|
|
133
|
+
- **Node.js >= 18** (uses native `fetch`)
|
|
134
|
+
|
|
135
|
+
## Related
|
|
136
|
+
|
|
137
|
+
- [clawapps-skill](https://github.com/ClawApps/clawapps-skill) — Agent Skill for managing apps on ClawApps
|
|
138
|
+
|
|
139
|
+
## Contributing
|
|
140
|
+
|
|
141
|
+
1. Fork the repository
|
|
142
|
+
2. Create a feature branch (`git checkout -b feat/my-feature`)
|
|
143
|
+
3. Commit your changes (use [Conventional Commits](https://www.conventionalcommits.org/))
|
|
144
|
+
4. Push and open a Pull Request
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
[MIT](LICENSE) - Copyright 2026 ClawApps
|
package/bin/claw.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CONFIG } from '../lib/config.js';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
/**
|
|
4
|
+
* Build the Apple OAuth URL via OpenDigits.
|
|
5
|
+
* OD handles the Apple OAuth flow and redirects back to our localhost with tokens.
|
|
6
|
+
*/
|
|
7
|
+
export function buildAppleAuthUrl(redirectUri) {
|
|
8
|
+
const state = randomBytes(16).toString('hex');
|
|
9
|
+
const params = new URLSearchParams({
|
|
10
|
+
redirect_uri: redirectUri,
|
|
11
|
+
state,
|
|
12
|
+
});
|
|
13
|
+
return `${CONFIG.OD_APPLE_AUTHORIZE}?${params.toString()}`;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=apple.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apple.js","sourceRoot":"","sources":["../../src/auth/apple.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE9C,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;QACjC,YAAY,EAAE,WAAW;QACzB,KAAK;KACN,CAAC,CAAC;IAEH,OAAO,GAAG,MAAM,CAAC,kBAAkB,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ODTokens, ClawTokens } from '../lib/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Exchange a Google access_token for OpenDigits tokens.
|
|
4
|
+
*/
|
|
5
|
+
export declare function googleToOD(googleAccessToken: string): Promise<ODTokens>;
|
|
6
|
+
export declare function odToClawApps(odTokens: ODTokens): Promise<ClawTokens>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CONFIG } from '../lib/config.js';
|
|
2
|
+
import { apiPost } from '../lib/api.js';
|
|
3
|
+
/**
|
|
4
|
+
* Exchange a Google access_token for OpenDigits tokens.
|
|
5
|
+
*/
|
|
6
|
+
export async function googleToOD(googleAccessToken) {
|
|
7
|
+
const res = await apiPost(CONFIG.OD_GOOGLE_AUTH, {
|
|
8
|
+
access_token: googleAccessToken,
|
|
9
|
+
});
|
|
10
|
+
if (!res.ok) {
|
|
11
|
+
throw new Error(`Google → OD exchange failed (${res.status}): ${JSON.stringify(res.data)}`);
|
|
12
|
+
}
|
|
13
|
+
return res.data.data;
|
|
14
|
+
}
|
|
15
|
+
export async function odToClawApps(odTokens) {
|
|
16
|
+
const res = await apiPost(CONFIG.CLAW_EXCHANGE, {
|
|
17
|
+
od_token: odTokens.access_token,
|
|
18
|
+
od_refresh_token: odTokens.refresh_token,
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
throw new Error(`OD → ClawApps exchange failed (${res.status}): ${JSON.stringify(res.data)}`);
|
|
22
|
+
}
|
|
23
|
+
return res.data.data;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=exchange.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exchange.js","sourceRoot":"","sources":["../../src/auth/exchange.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAUxC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,iBAAyB;IACxD,MAAM,GAAG,GAAG,MAAM,OAAO,CAA0B,MAAM,CAAC,cAAc,EAAE;QACxE,YAAY,EAAE,iBAAiB;KAChC,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,gCAAgC,GAAG,CAAC,MAAM,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;AACvB,CAAC;AAYD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAkB;IACnD,MAAM,GAAG,GAAG,MAAM,OAAO,CAA8B,MAAM,CAAC,aAAa,EAAE;QAC3E,QAAQ,EAAE,QAAQ,CAAC,YAAY;QAC/B,gBAAgB,EAAE,QAAQ,CAAC,aAAa;KACzC,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,kCAAkC,GAAG,CAAC,MAAM,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChG,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CONFIG } from '../lib/config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build the Google OAuth implicit flow URL.
|
|
4
|
+
* Uses response_type=token to get access_token in the URL hash fragment.
|
|
5
|
+
*/
|
|
6
|
+
export function buildGoogleAuthUrl(redirectUri) {
|
|
7
|
+
const params = new URLSearchParams({
|
|
8
|
+
client_id: CONFIG.GOOGLE_CLIENT_ID,
|
|
9
|
+
redirect_uri: redirectUri,
|
|
10
|
+
response_type: 'token',
|
|
11
|
+
scope: 'openid email profile',
|
|
12
|
+
prompt: 'select_account',
|
|
13
|
+
});
|
|
14
|
+
return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=google.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/auth/google.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB;IACpD,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;QACjC,SAAS,EAAE,MAAM,CAAC,gBAAgB;QAClC,YAAY,EAAE,WAAW;QACzB,aAAa,EAAE,OAAO;QACtB,KAAK,EAAE,sBAAsB;QAC7B,MAAM,EAAE,gBAAgB;KACzB,CAAC,CAAC;IAEH,OAAO,gDAAgD,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;AAC7E,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ClawTokens } from '../lib/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Start a local HTTP server that receives ClawApps tokens from the web login page redirect.
|
|
4
|
+
* The web page redirects to: http://localhost:{port}/callback?access_token=xxx&refresh_token=xxx
|
|
5
|
+
*/
|
|
6
|
+
export declare function startLoginCallbackServer(): Promise<{
|
|
7
|
+
port: number;
|
|
8
|
+
result: Promise<ClawTokens>;
|
|
9
|
+
close: () => void;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { URL } from 'node:url';
|
|
3
|
+
import { CONFIG } from '../lib/config.js';
|
|
4
|
+
import { getSuccessHtml, getErrorHtml } from '../html/callback.js';
|
|
5
|
+
/**
|
|
6
|
+
* Start a local HTTP server that receives ClawApps tokens from the web login page redirect.
|
|
7
|
+
* The web page redirects to: http://localhost:{port}/callback?access_token=xxx&refresh_token=xxx
|
|
8
|
+
*/
|
|
9
|
+
export function startLoginCallbackServer() {
|
|
10
|
+
return new Promise((resolveServer, rejectServer) => {
|
|
11
|
+
let resultResolve;
|
|
12
|
+
let resultReject;
|
|
13
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
14
|
+
resultResolve = resolve;
|
|
15
|
+
resultReject = reject;
|
|
16
|
+
});
|
|
17
|
+
const server = createServer((req, res) => {
|
|
18
|
+
const port = server.address().port;
|
|
19
|
+
const url = new URL(req.url || '/', `http://localhost:${port}`);
|
|
20
|
+
if (url.pathname === '/callback') {
|
|
21
|
+
const accessToken = url.searchParams.get('access_token');
|
|
22
|
+
const refreshToken = url.searchParams.get('refresh_token');
|
|
23
|
+
if (accessToken && refreshToken) {
|
|
24
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
25
|
+
res.end(getSuccessHtml());
|
|
26
|
+
resultResolve({ access_token: accessToken, refresh_token: refreshToken });
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
30
|
+
res.end(getErrorHtml('Missing tokens. Please try again.'));
|
|
31
|
+
resultReject(new Error('Callback missing tokens'));
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
36
|
+
res.end('Not found');
|
|
37
|
+
});
|
|
38
|
+
server.listen(0, '127.0.0.1', () => {
|
|
39
|
+
const addr = server.address();
|
|
40
|
+
const timeout = setTimeout(() => {
|
|
41
|
+
resultReject(new Error('Authentication timed out. Please try again.'));
|
|
42
|
+
server.close();
|
|
43
|
+
}, CONFIG.AUTH_TIMEOUT_MS);
|
|
44
|
+
resultPromise.finally(() => {
|
|
45
|
+
clearTimeout(timeout);
|
|
46
|
+
setTimeout(() => server.close(), 500);
|
|
47
|
+
});
|
|
48
|
+
resolveServer({
|
|
49
|
+
port: addr.port,
|
|
50
|
+
result: resultPromise,
|
|
51
|
+
close: () => {
|
|
52
|
+
clearTimeout(timeout);
|
|
53
|
+
server.close();
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
server.on('error', (err) => {
|
|
58
|
+
rejectServer(err);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=login-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-server.js","sourceRoot":"","sources":["../../src/auth/login-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AuthProvider, ODTokens } from '../lib/types.js';
|
|
2
|
+
export interface CallbackResult {
|
|
3
|
+
provider: AuthProvider;
|
|
4
|
+
/** For Google: the Google access_token; For Apple: not used */
|
|
5
|
+
googleAccessToken?: string;
|
|
6
|
+
/** For Apple: OD tokens come directly in the callback */
|
|
7
|
+
odTokens?: ODTokens;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Start a local HTTP server to receive the OAuth callback.
|
|
11
|
+
* Returns a promise that resolves with the callback result.
|
|
12
|
+
*/
|
|
13
|
+
export declare function startCallbackServer(provider: AuthProvider): Promise<{
|
|
14
|
+
port: number;
|
|
15
|
+
result: Promise<CallbackResult>;
|
|
16
|
+
close: () => void;
|
|
17
|
+
}>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { URL } from 'node:url';
|
|
3
|
+
import { CONFIG } from '../lib/config.js';
|
|
4
|
+
import { getGoogleCallbackHtml, getSuccessHtml, getErrorHtml } from '../html/callback.js';
|
|
5
|
+
function handleRequest(ctx, req, res) {
|
|
6
|
+
const url = new URL(req.url || '/', `http://localhost:${ctx.port}`);
|
|
7
|
+
// Google: /callback serves HTML that extracts token from hash
|
|
8
|
+
if (url.pathname === '/callback' && ctx.provider === 'google') {
|
|
9
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
10
|
+
res.end(getGoogleCallbackHtml(ctx.port));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
// Google: /token receives the token POSTed by the callback HTML page
|
|
14
|
+
if (url.pathname === '/token' && req.method === 'POST') {
|
|
15
|
+
let body = '';
|
|
16
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
17
|
+
req.on('end', () => {
|
|
18
|
+
try {
|
|
19
|
+
const data = JSON.parse(body);
|
|
20
|
+
const accessToken = data.access_token;
|
|
21
|
+
if (!accessToken) {
|
|
22
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
23
|
+
res.end(JSON.stringify({ error: 'Missing access_token' }));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
27
|
+
res.end(JSON.stringify({ ok: true }));
|
|
28
|
+
ctx.resolve({ provider: 'google', googleAccessToken: accessToken });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
32
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Apple: /callback receives OD tokens as query params
|
|
38
|
+
if (url.pathname === '/callback' && ctx.provider === 'apple') {
|
|
39
|
+
const accessToken = url.searchParams.get('access_token');
|
|
40
|
+
const refreshToken = url.searchParams.get('refresh_token');
|
|
41
|
+
if (!accessToken || !refreshToken) {
|
|
42
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
43
|
+
res.end(getErrorHtml('Missing tokens in callback. Please try again.'));
|
|
44
|
+
ctx.reject(new Error('Apple callback missing tokens'));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
48
|
+
res.end(getSuccessHtml());
|
|
49
|
+
ctx.resolve({
|
|
50
|
+
provider: 'apple',
|
|
51
|
+
odTokens: { access_token: accessToken, refresh_token: refreshToken },
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Fallback
|
|
56
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
57
|
+
res.end('Not found');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Start a local HTTP server to receive the OAuth callback.
|
|
61
|
+
* Returns a promise that resolves with the callback result.
|
|
62
|
+
*/
|
|
63
|
+
export function startCallbackServer(provider) {
|
|
64
|
+
return new Promise((resolveServer, rejectServer) => {
|
|
65
|
+
let resultResolve;
|
|
66
|
+
let resultReject;
|
|
67
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
68
|
+
resultResolve = resolve;
|
|
69
|
+
resultReject = reject;
|
|
70
|
+
});
|
|
71
|
+
const server = createServer((req, res) => {
|
|
72
|
+
handleRequest({ port: server.address().port, provider, resolve: resultResolve, reject: resultReject }, req, res);
|
|
73
|
+
});
|
|
74
|
+
// Bind to 127.0.0.1 only for security, port 0 = system assigns
|
|
75
|
+
server.listen(0, '127.0.0.1', () => {
|
|
76
|
+
const addr = server.address();
|
|
77
|
+
// Set up timeout
|
|
78
|
+
const timeout = setTimeout(() => {
|
|
79
|
+
resultReject(new Error('Authentication timed out. Please try again.'));
|
|
80
|
+
server.close();
|
|
81
|
+
}, CONFIG.AUTH_TIMEOUT_MS);
|
|
82
|
+
// When result resolves or rejects, clean up
|
|
83
|
+
resultPromise.finally(() => {
|
|
84
|
+
clearTimeout(timeout);
|
|
85
|
+
// Give the browser a moment to receive the response
|
|
86
|
+
setTimeout(() => server.close(), 500);
|
|
87
|
+
});
|
|
88
|
+
resolveServer({
|
|
89
|
+
port: addr.port,
|
|
90
|
+
result: resultPromise,
|
|
91
|
+
close: () => {
|
|
92
|
+
clearTimeout(timeout);
|
|
93
|
+
server.close();
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
server.on('error', (err) => {
|
|
98
|
+
rejectServer(err);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/auth/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function creditCommand(): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import open from 'open';
|
|
3
|
+
import { loadCredentials } from '../lib/credentials.js';
|
|
4
|
+
import { CONFIG } from '../lib/config.js';
|
|
5
|
+
import { ensureValidToken } from './helpers/ensure-token.js';
|
|
6
|
+
export async function creditCommand() {
|
|
7
|
+
const credentials = await loadCredentials();
|
|
8
|
+
if (!credentials) {
|
|
9
|
+
console.log(chalk.yellow('Not logged in. Run `claw login` first.'));
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const token = await ensureValidToken(credentials);
|
|
13
|
+
if (!token) {
|
|
14
|
+
console.log(chalk.red('Session expired. Please run `claw login` again.'));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const url = `${CONFIG.CLAW_WEB_BASE}/credit?access_token=${encodeURIComponent(token.access_token)}&refresh_token=${encodeURIComponent(token.refresh_token)}`;
|
|
18
|
+
console.log(chalk.gray('Opening credit recharge page...'));
|
|
19
|
+
try {
|
|
20
|
+
await open(url);
|
|
21
|
+
console.log(chalk.green('Page opened in your browser.'));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
console.log(chalk.yellow('Could not open browser automatically.'));
|
|
25
|
+
console.log(chalk.gray(`Please open this URL manually:\n${url}`));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=credit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credit.js","sourceRoot":"","sources":["../../src/commands/credit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,WAAW,GAAG,MAAM,eAAe,EAAE,CAAC;IAE5C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,wCAAwC,CAAC,CAAC,CAAC;QACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAClD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC,CAAC;QAC1E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,aAAa,wBAAwB,kBAAkB,CAAC,KAAK,CAAC,YAAY,CAAC,kBAAkB,kBAAkB,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;IAE7J,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC,CAAC;IAE3D,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,uCAAuC,CAAC,CAAC,CAAC;QACnE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,mCAAmC,GAAG,EAAE,CAAC,CAAC,CAAC;IACpE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Credentials } from '../../lib/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validates the current token, refreshes if expired.
|
|
4
|
+
* Returns updated credentials or null if session is invalid.
|
|
5
|
+
*/
|
|
6
|
+
export declare function ensureValidToken(credentials: Credentials): Promise<Credentials | null>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { apiGet, apiPost } from '../../lib/api.js';
|
|
2
|
+
import { saveCredentials } from '../../lib/credentials.js';
|
|
3
|
+
import { CONFIG } from '../../lib/config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Validates the current token, refreshes if expired.
|
|
6
|
+
* Returns updated credentials or null if session is invalid.
|
|
7
|
+
*/
|
|
8
|
+
export async function ensureValidToken(credentials) {
|
|
9
|
+
// Try current token
|
|
10
|
+
const res = await apiGet(CONFIG.CLAW_ME, credentials.access_token);
|
|
11
|
+
if (res.ok)
|
|
12
|
+
return credentials;
|
|
13
|
+
// Token expired — try refresh
|
|
14
|
+
if (res.status === 401) {
|
|
15
|
+
const refreshRes = await apiPost(CONFIG.CLAW_REFRESH, { refresh_token: credentials.refresh_token });
|
|
16
|
+
if (!refreshRes.ok)
|
|
17
|
+
return null;
|
|
18
|
+
const newTokens = refreshRes.data.data;
|
|
19
|
+
const updated = {
|
|
20
|
+
...credentials,
|
|
21
|
+
access_token: newTokens.access_token,
|
|
22
|
+
refresh_token: newTokens.refresh_token,
|
|
23
|
+
};
|
|
24
|
+
await saveCredentials(updated);
|
|
25
|
+
return updated;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=ensure-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ensure-token.js","sourceRoot":"","sources":["../../../src/commands/helpers/ensure-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAwB,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAG7C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,WAAwB;IAExB,oBAAoB;IACpB,MAAM,GAAG,GAAG,MAAM,MAAM,CACtB,MAAM,CAAC,OAAO,EACd,WAAW,CAAC,YAAY,CACzB,CAAC;IAEF,IAAI,GAAG,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC;IAE/B,8BAA8B;IAC9B,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACvB,MAAM,UAAU,GAAG,MAAM,OAAO,CAC9B,MAAM,CAAC,YAAY,EACnB,EAAE,aAAa,EAAE,WAAW,CAAC,aAAa,EAAE,CAC7C,CAAC;QAEF,IAAI,CAAC,UAAU,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAEhC,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QACvC,MAAM,OAAO,GAAgB;YAC3B,GAAG,WAAW;YACd,YAAY,EAAE,SAAS,CAAC,YAAY;YACpC,aAAa,EAAE,SAAS,CAAC,aAAa;SACvC,CAAC;QAEF,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;QAC/B,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function loginCommand(): Promise<void>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import open from 'open';
|
|
4
|
+
import { loadCredentials, saveCredentials } from '../lib/credentials.js';
|
|
5
|
+
import { apiGet } from '../lib/api.js';
|
|
6
|
+
import { CONFIG } from '../lib/config.js';
|
|
7
|
+
import { startLoginCallbackServer } from '../auth/login-server.js';
|
|
8
|
+
import { ensureValidToken } from './helpers/ensure-token.js';
|
|
9
|
+
export async function loginCommand() {
|
|
10
|
+
// Check if already logged in, refresh token if needed
|
|
11
|
+
const existing = await loadCredentials();
|
|
12
|
+
if (existing) {
|
|
13
|
+
try {
|
|
14
|
+
const validated = await ensureValidToken(existing);
|
|
15
|
+
if (validated) {
|
|
16
|
+
const res = await apiGet(CONFIG.CLAW_ME, validated.access_token);
|
|
17
|
+
if (res.ok) {
|
|
18
|
+
const user = res.data.data;
|
|
19
|
+
console.log(chalk.green(`Already logged in as ${chalk.bold(user.email || user.name || 'user')}.`));
|
|
20
|
+
console.log(chalk.gray('Session refreshed. Run `claw logout` first to switch accounts.'));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Token validation/refresh failed, continue with browser login
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Start local callback server to receive tokens from web
|
|
30
|
+
const { port, result, close } = await startLoginCallbackServer();
|
|
31
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
32
|
+
// Open web login page with callback
|
|
33
|
+
const loginUrl = `${CONFIG.CLAW_WEB_BASE}/login?callback=${encodeURIComponent(callbackUrl)}`;
|
|
34
|
+
console.log(chalk.gray('\nOpening browser for login...'));
|
|
35
|
+
console.log(chalk.gray(`If the browser doesn't open, visit:\n${loginUrl}\n`));
|
|
36
|
+
try {
|
|
37
|
+
await open(loginUrl);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
console.log(chalk.yellow('Could not open browser automatically.'));
|
|
41
|
+
console.log(chalk.yellow('Please open the URL above manually.'));
|
|
42
|
+
}
|
|
43
|
+
const spinner = ora('Waiting for authentication...').start();
|
|
44
|
+
try {
|
|
45
|
+
const tokens = await result;
|
|
46
|
+
// Save credentials
|
|
47
|
+
await saveCredentials({
|
|
48
|
+
provider: 'google', // web login handles provider selection
|
|
49
|
+
access_token: tokens.access_token,
|
|
50
|
+
refresh_token: tokens.refresh_token,
|
|
51
|
+
logged_in_at: new Date().toISOString(),
|
|
52
|
+
});
|
|
53
|
+
spinner.text = 'Fetching user info...';
|
|
54
|
+
const userRes = await apiGet(CONFIG.CLAW_ME, tokens.access_token);
|
|
55
|
+
spinner.stop();
|
|
56
|
+
if (userRes.ok) {
|
|
57
|
+
const user = userRes.data.data;
|
|
58
|
+
const name = user.name || user.email || 'user';
|
|
59
|
+
console.log(chalk.green(`\nLogged in as ${chalk.bold(name)}`));
|
|
60
|
+
if (user.email) {
|
|
61
|
+
console.log(chalk.gray(`Email: ${user.email}`));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log(chalk.green('\nLogin successful!'));
|
|
66
|
+
console.log(chalk.gray('Run `claw whoami` to see your account info.'));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
spinner.stop();
|
|
71
|
+
close();
|
|
72
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
73
|
+
console.error(chalk.red(`\nLogin failed: ${message}`));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function logoutCommand(): Promise<void>;
|