@countfinancial/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/README.md +100 -0
- package/dist/__tests__/credentialStore.test.d.ts +1 -0
- package/dist/__tests__/credentialStore.test.js +34 -0
- package/dist/__tests__/credentialStore.test.js.map +1 -0
- package/dist/__tests__/init.command.test.d.ts +1 -0
- package/dist/__tests__/init.command.test.js +69 -0
- package/dist/__tests__/init.command.test.js.map +1 -0
- package/dist/__tests__/localCallbackServer.test.d.ts +1 -0
- package/dist/__tests__/localCallbackServer.test.js +60 -0
- package/dist/__tests__/localCallbackServer.test.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +58 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/init.command.d.ts +8 -0
- package/dist/commands/init.command.js +43 -0
- package/dist/commands/init.command.js.map +1 -0
- package/dist/commands/login.command.d.ts +6 -0
- package/dist/commands/login.command.js +19 -0
- package/dist/commands/login.command.js.map +1 -0
- package/dist/commands/logout.command.d.ts +1 -0
- package/dist/commands/logout.command.js +6 -0
- package/dist/commands/logout.command.js.map +1 -0
- package/dist/commands/mcp.command.d.ts +2 -0
- package/dist/commands/mcp.command.js +23 -0
- package/dist/commands/mcp.command.js.map +1 -0
- package/dist/commands/status.command.d.ts +1 -0
- package/dist/commands/status.command.js +22 -0
- package/dist/commands/status.command.js.map +1 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +11 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/services/browserOpener.service.d.ts +5 -0
- package/dist/services/browserOpener.service.js +16 -0
- package/dist/services/browserOpener.service.js.map +1 -0
- package/dist/services/credentialStore.service.d.ts +26 -0
- package/dist/services/credentialStore.service.js +67 -0
- package/dist/services/credentialStore.service.js.map +1 -0
- package/dist/services/localCallbackServer.service.d.ts +15 -0
- package/dist/services/localCallbackServer.service.js +131 -0
- package/dist/services/localCallbackServer.service.js.map +1 -0
- package/dist/services/mcpLauncher.service.d.ts +15 -0
- package/dist/services/mcpLauncher.service.js +58 -0
- package/dist/services/mcpLauncher.service.js.map +1 -0
- package/dist/services/oauthLogin.service.d.ts +14 -0
- package/dist/services/oauthLogin.service.js +91 -0
- package/dist/services/oauthLogin.service.js.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# COUNT CLI (`@count/cli`)
|
|
2
|
+
|
|
3
|
+
Command-line tool for partner integrations that need **low-friction OAuth login** and a **local MCP server** for Claude Code, Cursor, and other agent runtimes.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
When `@count/partner-mcp` is published to npm:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
npm run build
|
|
12
|
+
npm link
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Until then, link `@count/partner-mcp` from the [count-api](https://github.com/NotAllTalk/count-api) monorepo:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd /path/to/count-api
|
|
19
|
+
npm install
|
|
20
|
+
npm run mcp:count:build
|
|
21
|
+
npm link --workspace @count/partner-mcp
|
|
22
|
+
|
|
23
|
+
cd /path/to/count-cli
|
|
24
|
+
npm link @count/partner-mcp
|
|
25
|
+
npm install
|
|
26
|
+
npm run build
|
|
27
|
+
npm link
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
When published to npm:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install -g @count/cli
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
### 1. Create a partner app
|
|
39
|
+
|
|
40
|
+
Open [COUNT Partners](https://app.getcount.com/count-partners), create an app, and add this redirect URI:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
http://127.0.0.1:17845/callback
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Save credentials
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
count init \
|
|
50
|
+
--client-id "<your-client-id>" \
|
|
51
|
+
--client-secret "<your-client-secret>"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Sign in
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
count login
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This opens `partner-signin`, lets you pick a workspace, stores access/refresh tokens in `~/.count/credentials.json`, and returns you to the terminal.
|
|
61
|
+
|
|
62
|
+
### 4. Run MCP locally
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
count mcp
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Or print Claude Code configuration:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
count mcp print-config
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Commands
|
|
75
|
+
|
|
76
|
+
| Command | Description |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| `count init` | Save `client_id` / `client_secret` |
|
|
79
|
+
| `count login` | Browser OAuth login + token storage |
|
|
80
|
+
| `count logout` | Delete `~/.count/credentials.json` |
|
|
81
|
+
| `count status` | Show whether credentials/tokens are present |
|
|
82
|
+
| `count mcp` | Start the local `@count/partner-mcp` stdio server |
|
|
83
|
+
| `count mcp print-config` | Emit MCP JSON for Claude Code / Cursor |
|
|
84
|
+
|
|
85
|
+
## Environment
|
|
86
|
+
|
|
87
|
+
The CLI reads the API host from `count init --api-url` (default `https://api.getcount.com`).
|
|
88
|
+
|
|
89
|
+
Stored credentials live at:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
~/.count/credentials.json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
File mode is `600`.
|
|
96
|
+
|
|
97
|
+
## Docs
|
|
98
|
+
|
|
99
|
+
- Full guide: [`docs/count-cli.md`](./docs/count-cli.md)
|
|
100
|
+
- Partner API reference: [developers.getcount.com](https://developers.getcount.com)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { describe, it } from 'node:test';
|
|
6
|
+
import { buildDefaultCredentials, deleteCredentials, getConfigFilePath, loadCredentials, saveCredentials, } from '../services/credentialStore.service.js';
|
|
7
|
+
describe('credentialStore', () => {
|
|
8
|
+
it('saves and reloads credentials with restricted file permissions', async () => {
|
|
9
|
+
const temporaryHomeDirectory = await fs.mkdtemp(path.join(os.tmpdir(), 'count-cli-home-'));
|
|
10
|
+
const configFilePath = getConfigFilePath({ homeDirectory: temporaryHomeDirectory });
|
|
11
|
+
const credentials = buildDefaultCredentials({
|
|
12
|
+
clientId: 'client-id',
|
|
13
|
+
clientSecret: 'client-secret',
|
|
14
|
+
});
|
|
15
|
+
await saveCredentials({ credentials, configFilePath });
|
|
16
|
+
const reloadedCredentials = await loadCredentials({ configFilePath });
|
|
17
|
+
const fileStats = await fs.stat(configFilePath);
|
|
18
|
+
assert.deepEqual(reloadedCredentials, credentials);
|
|
19
|
+
assert.equal((fileStats.mode & 0o777).toString(8), '600');
|
|
20
|
+
});
|
|
21
|
+
it('deletes credentials when logging out', async () => {
|
|
22
|
+
const temporaryHomeDirectory = await fs.mkdtemp(path.join(os.tmpdir(), 'count-cli-home-'));
|
|
23
|
+
const configFilePath = getConfigFilePath({ homeDirectory: temporaryHomeDirectory });
|
|
24
|
+
const credentials = buildDefaultCredentials({
|
|
25
|
+
clientId: 'client-id',
|
|
26
|
+
clientSecret: 'client-secret',
|
|
27
|
+
});
|
|
28
|
+
await saveCredentials({ credentials, configFilePath });
|
|
29
|
+
await deleteCredentials({ configFilePath });
|
|
30
|
+
const reloadedCredentials = await loadCredentials({ configFilePath });
|
|
31
|
+
assert.equal(reloadedCredentials, null);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=credentialStore.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentialStore.test.js","sourceRoot":"","sources":["../../src/__tests__/credentialStore.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAChB,MAAM,wCAAwC,CAAC;AAEhD,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,MAAM,sBAAsB,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;QAC3F,MAAM,cAAc,GAAG,iBAAiB,CAAC,EAAE,aAAa,EAAE,sBAAsB,EAAE,CAAC,CAAC;QACpF,MAAM,WAAW,GAAG,uBAAuB,CAAC;YAC1C,QAAQ,EAAE,WAAW;YACrB,YAAY,EAAE,eAAe;SAC9B,CAAC,CAAC;QAEH,MAAM,eAAe,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC;QACvD,MAAM,mBAAmB,GAAG,MAAM,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;QACtE,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAEhD,MAAM,CAAC,SAAS,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAC;QACnD,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,sBAAsB,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;QAC3F,MAAM,cAAc,GAAG,iBAAiB,CAAC,EAAE,aAAa,EAAE,sBAAsB,EAAE,CAAC,CAAC;QACpF,MAAM,WAAW,GAAG,uBAAuB,CAAC;YAC1C,QAAQ,EAAE,WAAW;YACrB,YAAY,EAAE,eAAe;SAC9B,CAAC,CAAC;QAEH,MAAM,eAAe,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC;QACvD,MAAM,iBAAiB,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;QAE5C,MAAM,mBAAmB,GAAG,MAAM,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;QACtE,MAAM,CAAC,KAAK,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { describe, it } from 'node:test';
|
|
6
|
+
import { runInitCommand } from '../commands/init.command.js';
|
|
7
|
+
import { getConfigFilePath, loadCredentials, saveCredentials } from '../services/credentialStore.service.js';
|
|
8
|
+
describe('runInitCommand', () => {
|
|
9
|
+
it('preserves existing api URL and login tokens when re-initializing credentials', async () => {
|
|
10
|
+
const temporaryHomeDirectory = await fs.mkdtemp(path.join(os.tmpdir(), 'count-cli-init-home-'));
|
|
11
|
+
const configFilePath = getConfigFilePath({ homeDirectory: temporaryHomeDirectory });
|
|
12
|
+
await saveCredentials({
|
|
13
|
+
configFilePath,
|
|
14
|
+
credentials: {
|
|
15
|
+
apiBaseUrl: 'https://dev-api.getcount.com',
|
|
16
|
+
clientId: 'old-client-id',
|
|
17
|
+
clientSecret: 'old-client-secret',
|
|
18
|
+
accessToken: 'stored-access-token',
|
|
19
|
+
refreshToken: 'stored-refresh-token',
|
|
20
|
+
workspaceId: 'workspace-uuid',
|
|
21
|
+
workspaceName: 'Demo Workspace',
|
|
22
|
+
requestTimeoutMs: 30000,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
await runInitCommand({
|
|
26
|
+
clientId: 'new-client-id',
|
|
27
|
+
clientSecret: 'new-client-secret',
|
|
28
|
+
configFilePath,
|
|
29
|
+
});
|
|
30
|
+
const reloadedCredentials = await loadCredentials({ configFilePath });
|
|
31
|
+
assert.equal(reloadedCredentials?.apiBaseUrl, 'https://dev-api.getcount.com');
|
|
32
|
+
assert.equal(reloadedCredentials?.clientId, 'new-client-id');
|
|
33
|
+
assert.equal(reloadedCredentials?.clientSecret, 'new-client-secret');
|
|
34
|
+
assert.equal(reloadedCredentials?.accessToken, 'stored-access-token');
|
|
35
|
+
assert.equal(reloadedCredentials?.refreshToken, 'stored-refresh-token');
|
|
36
|
+
assert.equal(reloadedCredentials?.workspaceId, 'workspace-uuid');
|
|
37
|
+
assert.equal(reloadedCredentials?.workspaceName, 'Demo Workspace');
|
|
38
|
+
});
|
|
39
|
+
it('clears login tokens when the API URL changes', async () => {
|
|
40
|
+
const temporaryHomeDirectory = await fs.mkdtemp(path.join(os.tmpdir(), 'count-cli-init-home-'));
|
|
41
|
+
const configFilePath = getConfigFilePath({ homeDirectory: temporaryHomeDirectory });
|
|
42
|
+
await saveCredentials({
|
|
43
|
+
configFilePath,
|
|
44
|
+
credentials: {
|
|
45
|
+
apiBaseUrl: 'https://api.getcount.com',
|
|
46
|
+
clientId: 'client-id',
|
|
47
|
+
clientSecret: 'client-secret',
|
|
48
|
+
accessToken: 'stored-access-token',
|
|
49
|
+
refreshToken: 'stored-refresh-token',
|
|
50
|
+
workspaceId: 'workspace-uuid',
|
|
51
|
+
workspaceName: 'Demo Workspace',
|
|
52
|
+
requestTimeoutMs: 30000,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
await runInitCommand({
|
|
56
|
+
clientId: 'client-id',
|
|
57
|
+
clientSecret: 'client-secret',
|
|
58
|
+
apiBaseUrl: 'https://dev-api.getcount.com',
|
|
59
|
+
configFilePath,
|
|
60
|
+
});
|
|
61
|
+
const reloadedCredentials = await loadCredentials({ configFilePath });
|
|
62
|
+
assert.equal(reloadedCredentials?.apiBaseUrl, 'https://dev-api.getcount.com');
|
|
63
|
+
assert.equal(reloadedCredentials?.accessToken, undefined);
|
|
64
|
+
assert.equal(reloadedCredentials?.refreshToken, undefined);
|
|
65
|
+
assert.equal(reloadedCredentials?.workspaceId, undefined);
|
|
66
|
+
assert.equal(reloadedCredentials?.workspaceName, undefined);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
//# sourceMappingURL=init.command.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.command.test.js","sourceRoot":"","sources":["../../src/__tests__/init.command.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import { startLocalCallbackServer } from '../services/localCallbackServer.service.js';
|
|
4
|
+
describe('localCallbackServer', () => {
|
|
5
|
+
it('accepts a matching OAuth callback and returns code and state', async () => {
|
|
6
|
+
const callbackServer = await startLocalCallbackServer({
|
|
7
|
+
host: '127.0.0.1',
|
|
8
|
+
port: 0,
|
|
9
|
+
callbackPath: '/callback',
|
|
10
|
+
expectedState: 'expected-state',
|
|
11
|
+
timeoutMs: 5000,
|
|
12
|
+
});
|
|
13
|
+
try {
|
|
14
|
+
const waitPromise = callbackServer.waitForCallback();
|
|
15
|
+
const callbackUrl = new URL(callbackServer.redirectUri);
|
|
16
|
+
callbackUrl.searchParams.set('code', 'auth-code');
|
|
17
|
+
callbackUrl.searchParams.set('state', 'expected-state');
|
|
18
|
+
const response = await fetch(callbackUrl.toString());
|
|
19
|
+
assert.equal(response.status, 200);
|
|
20
|
+
const callbackResult = await waitPromise;
|
|
21
|
+
assert.deepEqual(callbackResult, {
|
|
22
|
+
code: 'auth-code',
|
|
23
|
+
state: 'expected-state',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
await callbackServer.close();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
it('ignores a malformed callback before accepting a valid redirect', async () => {
|
|
31
|
+
const callbackServer = await startLocalCallbackServer({
|
|
32
|
+
host: '127.0.0.1',
|
|
33
|
+
port: 0,
|
|
34
|
+
callbackPath: '/callback',
|
|
35
|
+
expectedState: 'expected-state',
|
|
36
|
+
timeoutMs: 5000,
|
|
37
|
+
});
|
|
38
|
+
try {
|
|
39
|
+
const waitPromise = callbackServer.waitForCallback();
|
|
40
|
+
const malformedCallbackUrl = new URL(callbackServer.redirectUri);
|
|
41
|
+
malformedCallbackUrl.searchParams.set('state', 'wrong-state');
|
|
42
|
+
const malformedResponse = await fetch(malformedCallbackUrl.toString());
|
|
43
|
+
assert.equal(malformedResponse.status, 400);
|
|
44
|
+
const validCallbackUrl = new URL(callbackServer.redirectUri);
|
|
45
|
+
validCallbackUrl.searchParams.set('code', 'auth-code');
|
|
46
|
+
validCallbackUrl.searchParams.set('state', 'expected-state');
|
|
47
|
+
const validResponse = await fetch(validCallbackUrl.toString());
|
|
48
|
+
assert.equal(validResponse.status, 200);
|
|
49
|
+
const callbackResult = await waitPromise;
|
|
50
|
+
assert.deepEqual(callbackResult, {
|
|
51
|
+
code: 'auth-code',
|
|
52
|
+
state: 'expected-state',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
await callbackServer.close();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=localCallbackServer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localCallbackServer.test.js","sourceRoot":"","sources":["../../src/__tests__/localCallbackServer.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,4CAA4C,CAAC;AAEtF,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,cAAc,GAAG,MAAM,wBAAwB,CAAC;YACpD,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,CAAC;YACP,YAAY,EAAE,WAAW;YACzB,aAAa,EAAE,gBAAgB;YAC/B,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QAEH,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,cAAc,CAAC,eAAe,EAAE,CAAC;YACrD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;YACxD,WAAW,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;YAClD,WAAW,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;YAExD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC;YACrD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAEnC,MAAM,cAAc,GAAG,MAAM,WAAW,CAAC;YACzC,MAAM,CAAC,SAAS,CAAC,cAAc,EAAE;gBAC/B,IAAI,EAAE,WAAW;gBACjB,KAAK,EAAE,gBAAgB;aACxB,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,MAAM,cAAc,CAAC,KAAK,EAAE,CAAC;QAC/B,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,MAAM,cAAc,GAAG,MAAM,wBAAwB,CAAC;YACpD,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,CAAC;YACP,YAAY,EAAE,WAAW;YACzB,aAAa,EAAE,gBAAgB;YAC/B,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QAEH,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,cAAc,CAAC,eAAe,EAAE,CAAC;YACrD,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;YACjE,oBAAoB,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;YAE9D,MAAM,iBAAiB,GAAG,MAAM,KAAK,CAAC,oBAAoB,CAAC,QAAQ,EAAE,CAAC,CAAC;YACvE,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAE5C,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;YAC7D,gBAAgB,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;YACvD,gBAAgB,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;YAE7D,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC/D,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAExC,MAAM,cAAc,GAAG,MAAM,WAAW,CAAC;YACzC,MAAM,CAAC,SAAS,CAAC,cAAc,EAAE;gBAC/B,IAAI,EAAE,WAAW;gBACjB,KAAK,EAAE,gBAAgB;aACxB,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,MAAM,cAAc,CAAC,KAAK,EAAE,CAAC;QAC/B,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { runInitCommand } from './commands/init.command.js';
|
|
3
|
+
import { runLoginCommand } from './commands/login.command.js';
|
|
4
|
+
import { runLogoutCommand } from './commands/logout.command.js';
|
|
5
|
+
import { runMcpPrintConfigCommand, runMcpStartCommand } from './commands/mcp.command.js';
|
|
6
|
+
import { runStatusCommand } from './commands/status.command.js';
|
|
7
|
+
export function createCli(params) {
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program
|
|
10
|
+
.name('count')
|
|
11
|
+
.description('COUNT Partner CLI — OAuth login and local MCP for Claude Code, Cursor, and agents.')
|
|
12
|
+
.version(params.version);
|
|
13
|
+
program
|
|
14
|
+
.command('init')
|
|
15
|
+
.description('Save partner client credentials from COUNT Partners')
|
|
16
|
+
.requiredOption('--client-id <clientId>', 'Partner client ID')
|
|
17
|
+
.requiredOption('--client-secret <clientSecret>', 'Partner client secret')
|
|
18
|
+
.option('--api-url <apiUrl>', 'COUNT API base URL (default: prod, or keep existing saved URL)')
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
await runInitCommand({
|
|
21
|
+
clientId: options.clientId,
|
|
22
|
+
clientSecret: options.clientSecret,
|
|
23
|
+
apiBaseUrl: options.apiUrl,
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
program
|
|
27
|
+
.command('login')
|
|
28
|
+
.description('Sign in through COUNT partner OAuth and store workspace tokens')
|
|
29
|
+
.option('--port <port>', 'Local OAuth callback port', (value) => Number.parseInt(value, 10))
|
|
30
|
+
.option('--no-open', 'Print the sign-in URL instead of opening a browser')
|
|
31
|
+
.action(async (options) => {
|
|
32
|
+
await runLoginCommand({
|
|
33
|
+
callbackPort: options.port,
|
|
34
|
+
openBrowserAutomatically: options.open,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
program.command('logout').description('Remove stored COUNT CLI credentials').action(async () => {
|
|
38
|
+
await runLogoutCommand();
|
|
39
|
+
});
|
|
40
|
+
program.command('status').description('Show stored credential state').action(async () => {
|
|
41
|
+
await runStatusCommand();
|
|
42
|
+
});
|
|
43
|
+
const mcpCommand = program.command('mcp').description('Local COUNT Partner MCP server');
|
|
44
|
+
mcpCommand
|
|
45
|
+
.command('start', { isDefault: true })
|
|
46
|
+
.description('Start the stdio MCP server using stored credentials')
|
|
47
|
+
.action(async () => {
|
|
48
|
+
await runMcpStartCommand();
|
|
49
|
+
});
|
|
50
|
+
mcpCommand
|
|
51
|
+
.command('print-config')
|
|
52
|
+
.description('Print Claude Code / Cursor MCP configuration JSON')
|
|
53
|
+
.action(async () => {
|
|
54
|
+
await runMcpPrintConfigCommand();
|
|
55
|
+
});
|
|
56
|
+
return program;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAKhE,MAAM,UAAU,SAAS,CAAC,MAAuB;IAC/C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,OAAO,CAAC;SACb,WAAW,CAAC,oFAAoF,CAAC;SACjG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAE3B,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,qDAAqD,CAAC;SAClE,cAAc,CAAC,wBAAwB,EAAE,mBAAmB,CAAC;SAC7D,cAAc,CAAC,gCAAgC,EAAE,uBAAuB,CAAC;SACzE,MAAM,CAAC,oBAAoB,EAAE,gEAAgE,CAAC;SAC9F,MAAM,CAAC,KAAK,EAAE,OAAoE,EAAE,EAAE;QACrF,MAAM,cAAc,CAAC;YACnB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,UAAU,EAAE,OAAO,CAAC,MAAM;SAC3B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,gEAAgE,CAAC;SAC7E,MAAM,CAAC,eAAe,EAAE,2BAA2B,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;SAC3F,MAAM,CAAC,WAAW,EAAE,oDAAoD,CAAC;SACzE,MAAM,CAAC,KAAK,EAAE,OAAyC,EAAE,EAAE;QAC1D,MAAM,eAAe,CAAC;YACpB,YAAY,EAAE,OAAO,CAAC,IAAI;YAC1B,wBAAwB,EAAE,OAAO,CAAC,IAAI;SACvC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,qCAAqC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;QAC7F,MAAM,gBAAgB,EAAE,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,8BAA8B,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;QACtF,MAAM,gBAAgB,EAAE,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,gCAAgC,CAAC,CAAC;IAExF,UAAU;SACP,OAAO,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;SACrC,WAAW,CAAC,qDAAqD,CAAC;SAClE,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,MAAM,kBAAkB,EAAE,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEL,UAAU;SACP,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CAAC,mDAAmD,CAAC;SAChE,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,MAAM,wBAAwB,EAAE,CAAC;IACnC,CAAC,CAAC,CAAC;IAEL,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { DEFAULT_API_BASE_URL, DEFAULT_CALLBACK_HOST, DEFAULT_CALLBACK_PATH, DEFAULT_CALLBACK_PORT, DEFAULT_REQUEST_TIMEOUT_MS, DEVELOPER_DOCS_URL, PARTNERS_PORTAL_URL, } from '../constants.js';
|
|
2
|
+
import { buildDefaultCredentials, getConfigFilePath, loadCredentials, saveCredentials, } from '../services/credentialStore.service.js';
|
|
3
|
+
function normalizeApiBaseUrl(apiBaseUrl) {
|
|
4
|
+
return apiBaseUrl.replace(/\/+$/, '');
|
|
5
|
+
}
|
|
6
|
+
export async function runInitCommand(params) {
|
|
7
|
+
const configFilePath = params.configFilePath ?? getConfigFilePath();
|
|
8
|
+
const existingCredentials = await loadCredentials({ configFilePath });
|
|
9
|
+
const resolvedApiBaseUrl = normalizeApiBaseUrl(params.apiBaseUrl ?? existingCredentials?.apiBaseUrl ?? DEFAULT_API_BASE_URL);
|
|
10
|
+
const apiBaseUrlChanged = Boolean(existingCredentials?.apiBaseUrl &&
|
|
11
|
+
normalizeApiBaseUrl(existingCredentials.apiBaseUrl) !== resolvedApiBaseUrl);
|
|
12
|
+
const shouldPreserveLoginTokens = !apiBaseUrlChanged && Boolean(existingCredentials?.accessToken && existingCredentials.refreshToken);
|
|
13
|
+
const credentials = {
|
|
14
|
+
...buildDefaultCredentials({
|
|
15
|
+
clientId: params.clientId,
|
|
16
|
+
clientSecret: params.clientSecret,
|
|
17
|
+
apiBaseUrl: resolvedApiBaseUrl,
|
|
18
|
+
}),
|
|
19
|
+
...(shouldPreserveLoginTokens
|
|
20
|
+
? {
|
|
21
|
+
accessToken: existingCredentials?.accessToken,
|
|
22
|
+
refreshToken: existingCredentials?.refreshToken,
|
|
23
|
+
workspaceId: existingCredentials?.workspaceId,
|
|
24
|
+
workspaceName: existingCredentials?.workspaceName,
|
|
25
|
+
}
|
|
26
|
+
: {}),
|
|
27
|
+
requestTimeoutMs: existingCredentials?.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
|
|
28
|
+
};
|
|
29
|
+
await saveCredentials({ credentials, configFilePath });
|
|
30
|
+
if (shouldPreserveLoginTokens) {
|
|
31
|
+
process.stdout.write('Existing login tokens were preserved. Run `count login` again if you changed client credentials.\n\n');
|
|
32
|
+
}
|
|
33
|
+
else if (apiBaseUrlChanged && existingCredentials?.accessToken) {
|
|
34
|
+
process.stdout.write('API URL changed — stored login tokens were cleared. Run `count login` for the new environment.\n\n');
|
|
35
|
+
}
|
|
36
|
+
const redirectUri = `http://${DEFAULT_CALLBACK_HOST}:${DEFAULT_CALLBACK_PORT}${DEFAULT_CALLBACK_PATH}`;
|
|
37
|
+
process.stdout.write(`Saved COUNT CLI credentials to ${configFilePath}\n\n`);
|
|
38
|
+
process.stdout.write('Before running `count login`, register this redirect URI on your partner app:\n');
|
|
39
|
+
process.stdout.write(` ${redirectUri}\n\n`);
|
|
40
|
+
process.stdout.write(`Partner apps: ${PARTNERS_PORTAL_URL}\n`);
|
|
41
|
+
process.stdout.write(`API docs: ${DEVELOPER_DOCS_URL}\n`);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=init.command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.command.js","sourceRoot":"","sources":["../../src/commands/init.command.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,0BAA0B,EAC1B,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAChB,MAAM,wCAAwC,CAAC;AAShD,SAAS,mBAAmB,CAAC,UAAkB;IAC7C,OAAO,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAA4B;IAC/D,MAAM,cAAc,GAAG,MAAM,CAAC,cAAc,IAAI,iBAAiB,EAAE,CAAC;IACpE,MAAM,mBAAmB,GAAG,MAAM,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;IACtE,MAAM,kBAAkB,GAAG,mBAAmB,CAC5C,MAAM,CAAC,UAAU,IAAI,mBAAmB,EAAE,UAAU,IAAI,oBAAoB,CAC7E,CAAC;IACF,MAAM,iBAAiB,GAAG,OAAO,CAC/B,mBAAmB,EAAE,UAAU;QAC7B,mBAAmB,CAAC,mBAAmB,CAAC,UAAU,CAAC,KAAK,kBAAkB,CAC7E,CAAC;IACF,MAAM,yBAAyB,GAC7B,CAAC,iBAAiB,IAAI,OAAO,CAAC,mBAAmB,EAAE,WAAW,IAAI,mBAAmB,CAAC,YAAY,CAAC,CAAC;IAEtG,MAAM,WAAW,GAAG;QAClB,GAAG,uBAAuB,CAAC;YACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,YAAY,EAAE,MAAM,CAAC,YAAY;YACjC,UAAU,EAAE,kBAAkB;SAC/B,CAAC;QACF,GAAG,CAAC,yBAAyB;YAC3B,CAAC,CAAC;gBACE,WAAW,EAAE,mBAAmB,EAAE,WAAW;gBAC7C,YAAY,EAAE,mBAAmB,EAAE,YAAY;gBAC/C,WAAW,EAAE,mBAAmB,EAAE,WAAW;gBAC7C,aAAa,EAAE,mBAAmB,EAAE,aAAa;aAClD;YACH,CAAC,CAAC,EAAE,CAAC;QACP,gBAAgB,EAAE,mBAAmB,EAAE,gBAAgB,IAAI,0BAA0B;KACtF,CAAC;IAEF,MAAM,eAAe,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC;IAEvD,IAAI,yBAAyB,EAAE,CAAC;QAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sGAAsG,CAAC,CAAC;IAC/H,CAAC;SAAM,IAAI,iBAAiB,IAAI,mBAAmB,EAAE,WAAW,EAAE,CAAC;QACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oGAAoG,CAAC,CAAC;IAC7H,CAAC;IAED,MAAM,WAAW,GAAG,UAAU,qBAAqB,IAAI,qBAAqB,GAAG,qBAAqB,EAAE,CAAC;IAEvG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,cAAc,MAAM,CAAC,CAAC;IAC7E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iFAAiF,CAAC,CAAC;IACxG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,WAAW,MAAM,CAAC,CAAC;IAC7C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,mBAAmB,IAAI,CAAC,CAAC;IAC/D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,kBAAkB,IAAI,CAAC,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { runOAuthLogin } from '../services/oauthLogin.service.js';
|
|
2
|
+
import { getConfigFilePath, loadCredentials, saveCredentials } from '../services/credentialStore.service.js';
|
|
3
|
+
export async function runLoginCommand(params = {}) {
|
|
4
|
+
const credentials = await loadCredentials();
|
|
5
|
+
if (!credentials?.clientId || !credentials.clientSecret) {
|
|
6
|
+
throw new Error('Partner credentials are not configured. Run `count init --client-id <id> --client-secret <secret>` first.');
|
|
7
|
+
}
|
|
8
|
+
const loginResult = await runOAuthLogin({
|
|
9
|
+
credentials,
|
|
10
|
+
callbackPort: params.callbackPort,
|
|
11
|
+
openBrowserAutomatically: params.openBrowserAutomatically,
|
|
12
|
+
});
|
|
13
|
+
await saveCredentials({ credentials: loginResult.credentials });
|
|
14
|
+
const workspaceLabel = loginResult.credentials.workspaceName ?? loginResult.credentials.workspaceId ?? 'workspace';
|
|
15
|
+
process.stdout.write(`Logged in to ${workspaceLabel}.\n`);
|
|
16
|
+
process.stdout.write(`Credentials saved to ${getConfigFilePath()}\n`);
|
|
17
|
+
process.stdout.write('Run `count mcp` to start the local MCP server or `count mcp print-config` for Claude Code.\n');
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=login.command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.command.js","sourceRoot":"","sources":["../../src/commands/login.command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAO7G,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,SAAgC,EAAE;IACtE,MAAM,WAAW,GAAG,MAAM,eAAe,EAAE,CAAC;IAE5C,IAAI,CAAC,WAAW,EAAE,QAAQ,IAAI,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CACb,2GAA2G,CAC5G,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,aAAa,CAAC;QACtC,WAAW;QACX,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,wBAAwB,EAAE,MAAM,CAAC,wBAAwB;KAC1D,CAAC,CAAC;IAEH,MAAM,eAAe,CAAC,EAAE,WAAW,EAAE,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC;IAEhE,MAAM,cAAc,GAAG,WAAW,CAAC,WAAW,CAAC,aAAa,IAAI,WAAW,CAAC,WAAW,CAAC,WAAW,IAAI,WAAW,CAAC;IACnH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,cAAc,KAAK,CAAC,CAAC;IAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,iBAAiB,EAAE,IAAI,CAAC,CAAC;IACtE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8FAA8F,CAAC,CAAC;AACvH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runLogoutCommand(): Promise<void>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { deleteCredentials, getConfigFilePath } from '../services/credentialStore.service.js';
|
|
2
|
+
export async function runLogoutCommand() {
|
|
3
|
+
await deleteCredentials();
|
|
4
|
+
process.stdout.write(`Removed stored credentials from ${getConfigFilePath()}\n`);
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=logout.command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.command.js","sourceRoot":"","sources":["../../src/commands/logout.command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAE9F,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,iBAAiB,EAAE,CAAC;IAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,iBAAiB,EAAE,IAAI,CAAC,CAAC;AACnF,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { buildClaudeCodeMcpConfig, launchPartnerMcpServer } from '../services/mcpLauncher.service.js';
|
|
2
|
+
import { loadCredentials } from '../services/credentialStore.service.js';
|
|
3
|
+
function assertLoggedInCredentials(credentials) {
|
|
4
|
+
if (!credentials?.clientId || !credentials.clientSecret) {
|
|
5
|
+
throw new Error('Partner credentials are not configured. Run `count init` first.');
|
|
6
|
+
}
|
|
7
|
+
if (!credentials.accessToken || !credentials.refreshToken) {
|
|
8
|
+
throw new Error('You are not logged in. Run `count login` first.');
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export async function runMcpStartCommand() {
|
|
12
|
+
const credentials = await loadCredentials();
|
|
13
|
+
assertLoggedInCredentials(credentials);
|
|
14
|
+
const exitCode = await launchPartnerMcpServer({ credentials });
|
|
15
|
+
process.exit(exitCode);
|
|
16
|
+
}
|
|
17
|
+
export async function runMcpPrintConfigCommand() {
|
|
18
|
+
const credentials = await loadCredentials();
|
|
19
|
+
assertLoggedInCredentials(credentials);
|
|
20
|
+
const configuration = buildClaudeCodeMcpConfig({ credentials });
|
|
21
|
+
process.stdout.write(`${JSON.stringify(configuration, null, 2)}\n`);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=mcp.command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.command.js","sourceRoot":"","sources":["../../src/commands/mcp.command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AACtG,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAEzE,SAAS,yBAAyB,CAChC,WAAwD;IAExD,IAAI,CAAC,WAAW,EAAE,QAAQ,IAAI,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACrF,CAAC;IAED,IAAI,CAAC,WAAW,CAAC,WAAW,IAAI,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACrE,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,WAAW,GAAG,MAAM,eAAe,EAAE,CAAC;IAC5C,yBAAyB,CAAC,WAAW,CAAC,CAAC;IAEvC,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;IAC/D,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC5C,MAAM,WAAW,GAAG,MAAM,eAAe,EAAE,CAAC;IAC5C,yBAAyB,CAAC,WAAW,CAAC,CAAC;IAEvC,MAAM,aAAa,GAAG,wBAAwB,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;IAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runStatusCommand(): Promise<void>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getConfigFilePath, loadCredentials } from '../services/credentialStore.service.js';
|
|
2
|
+
export async function runStatusCommand() {
|
|
3
|
+
const credentials = await loadCredentials();
|
|
4
|
+
const configFilePath = getConfigFilePath();
|
|
5
|
+
if (!credentials) {
|
|
6
|
+
process.stdout.write(`No credentials file at ${configFilePath}\n`);
|
|
7
|
+
process.stdout.write('Run `count init` then `count login`.\n');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const status = {
|
|
11
|
+
configFilePath,
|
|
12
|
+
apiBaseUrl: credentials.apiBaseUrl,
|
|
13
|
+
hasClientId: Boolean(credentials.clientId),
|
|
14
|
+
hasClientSecret: Boolean(credentials.clientSecret),
|
|
15
|
+
hasAccessToken: Boolean(credentials.accessToken),
|
|
16
|
+
hasRefreshToken: Boolean(credentials.refreshToken),
|
|
17
|
+
workspaceId: credentials.workspaceId ?? null,
|
|
18
|
+
workspaceName: credentials.workspaceName ?? null,
|
|
19
|
+
};
|
|
20
|
+
process.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=status.command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.command.js","sourceRoot":"","sources":["../../src/commands/status.command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAE5F,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,WAAW,GAAG,MAAM,eAAe,EAAE,CAAC;IAC5C,MAAM,cAAc,GAAG,iBAAiB,EAAE,CAAC;IAE3C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,cAAc,IAAI,CAAC,CAAC;QACnE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC/D,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG;QACb,cAAc;QACd,UAAU,EAAE,WAAW,CAAC,UAAU;QAClC,WAAW,EAAE,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC;QAC1C,eAAe,EAAE,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC;QAClD,cAAc,EAAE,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC;QAChD,eAAe,EAAE,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC;QAClD,WAAW,EAAE,WAAW,CAAC,WAAW,IAAI,IAAI;QAC5C,aAAa,EAAE,WAAW,CAAC,aAAa,IAAI,IAAI;KACjD,CAAC;IAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const DEFAULT_API_BASE_URL = "https://api.getcount.com";
|
|
2
|
+
export declare const DEFAULT_CALLBACK_HOST = "127.0.0.1";
|
|
3
|
+
export declare const DEFAULT_CALLBACK_PORT = 17845;
|
|
4
|
+
export declare const DEFAULT_CALLBACK_PATH = "/callback";
|
|
5
|
+
export declare const DEFAULT_CONFIG_DIRECTORY_NAME = ".count";
|
|
6
|
+
export declare const DEFAULT_CONFIG_FILE_NAME = "credentials.json";
|
|
7
|
+
export declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
8
|
+
export declare const OAUTH_LOGIN_TIMEOUT_MS = 300000;
|
|
9
|
+
export declare const PARTNERS_PORTAL_URL = "https://app.getcount.com/count-partners";
|
|
10
|
+
export declare const DEVELOPER_DOCS_URL = "https://developers.getcount.com";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const DEFAULT_API_BASE_URL = 'https://api.getcount.com';
|
|
2
|
+
export const DEFAULT_CALLBACK_HOST = '127.0.0.1';
|
|
3
|
+
export const DEFAULT_CALLBACK_PORT = 17845;
|
|
4
|
+
export const DEFAULT_CALLBACK_PATH = '/callback';
|
|
5
|
+
export const DEFAULT_CONFIG_DIRECTORY_NAME = '.count';
|
|
6
|
+
export const DEFAULT_CONFIG_FILE_NAME = 'credentials.json';
|
|
7
|
+
export const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
8
|
+
export const OAUTH_LOGIN_TIMEOUT_MS = 300000;
|
|
9
|
+
export const PARTNERS_PORTAL_URL = 'https://app.getcount.com/count-partners';
|
|
10
|
+
export const DEVELOPER_DOCS_URL = 'https://developers.getcount.com';
|
|
11
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAC/D,MAAM,CAAC,MAAM,qBAAqB,GAAG,WAAW,CAAC;AACjD,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC;AAC3C,MAAM,CAAC,MAAM,qBAAqB,GAAG,WAAW,CAAC;AACjD,MAAM,CAAC,MAAM,6BAA6B,GAAG,QAAQ,CAAC;AACtD,MAAM,CAAC,MAAM,wBAAwB,GAAG,kBAAkB,CAAC;AAC3D,MAAM,CAAC,MAAM,0BAA0B,GAAG,KAAK,CAAC;AAChD,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAC7C,MAAM,CAAC,MAAM,mBAAmB,GAAG,yCAAyC,CAAC;AAC7E,MAAM,CAAC,MAAM,kBAAkB,GAAG,iCAAiC,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createCli } from './cli.js';
|
|
3
|
+
async function main() {
|
|
4
|
+
const program = createCli({ version: '0.1.0' });
|
|
5
|
+
await program.parseAsync(process.argv);
|
|
6
|
+
}
|
|
7
|
+
main().catch((error) => {
|
|
8
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9
|
+
process.stderr.write(`count: ${message}\n`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
});
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAChD,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,IAAI,CAAC,CAAC;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
export async function openBrowser(params) {
|
|
5
|
+
const { url } = params;
|
|
6
|
+
if (process.platform === 'darwin') {
|
|
7
|
+
await execFileAsync('open', [url]);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (process.platform === 'win32') {
|
|
11
|
+
await execFileAsync('cmd', ['/c', 'start', '', url], { windowsHide: true });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
await execFileAsync('xdg-open', [url]);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=browserOpener.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browserOpener.service.js","sourceRoot":"","sources":["../../src/services/browserOpener.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAM1C,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAyB;IACzD,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;IAEvB,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,MAAM,aAAa,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACnC,OAAO;IACT,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5E,OAAO;IACT,CAAC;IAED,MAAM,aAAa,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CountCliCredentials } from '../types.js';
|
|
2
|
+
interface GetConfigFilePathParams {
|
|
3
|
+
homeDirectory?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function getConfigDirectoryPath(params?: GetConfigFilePathParams): string;
|
|
6
|
+
export declare function getConfigFilePath(params?: GetConfigFilePathParams): string;
|
|
7
|
+
interface LoadCredentialsParams {
|
|
8
|
+
configFilePath?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function loadCredentials(params?: LoadCredentialsParams): Promise<CountCliCredentials | null>;
|
|
11
|
+
interface SaveCredentialsParams {
|
|
12
|
+
credentials: CountCliCredentials;
|
|
13
|
+
configFilePath?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function saveCredentials(params: SaveCredentialsParams): Promise<void>;
|
|
16
|
+
interface DeleteCredentialsParams {
|
|
17
|
+
configFilePath?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function deleteCredentials(params?: DeleteCredentialsParams): Promise<void>;
|
|
20
|
+
interface BuildDefaultCredentialsParams {
|
|
21
|
+
clientId: string;
|
|
22
|
+
clientSecret: string;
|
|
23
|
+
apiBaseUrl?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function buildDefaultCredentials(params: BuildDefaultCredentialsParams): CountCliCredentials;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { DEFAULT_API_BASE_URL, DEFAULT_CONFIG_DIRECTORY_NAME, DEFAULT_CONFIG_FILE_NAME, DEFAULT_REQUEST_TIMEOUT_MS, } from '../constants.js';
|
|
6
|
+
const credentialsSchema = z.object({
|
|
7
|
+
apiBaseUrl: z.string().trim().min(1),
|
|
8
|
+
clientId: z.string().trim().min(1),
|
|
9
|
+
clientSecret: z.string().trim().min(1),
|
|
10
|
+
accessToken: z.string().trim().min(1).optional(),
|
|
11
|
+
refreshToken: z.string().trim().min(1).optional(),
|
|
12
|
+
workspaceId: z.string().trim().min(1).optional(),
|
|
13
|
+
workspaceName: z.string().trim().min(1).optional(),
|
|
14
|
+
requestTimeoutMs: z.number().int().positive(),
|
|
15
|
+
});
|
|
16
|
+
export function getConfigDirectoryPath(params = {}) {
|
|
17
|
+
const { homeDirectory = os.homedir() } = params;
|
|
18
|
+
return path.join(homeDirectory, DEFAULT_CONFIG_DIRECTORY_NAME);
|
|
19
|
+
}
|
|
20
|
+
export function getConfigFilePath(params = {}) {
|
|
21
|
+
return path.join(getConfigDirectoryPath(params), DEFAULT_CONFIG_FILE_NAME);
|
|
22
|
+
}
|
|
23
|
+
export async function loadCredentials(params = {}) {
|
|
24
|
+
const configFilePath = params.configFilePath ?? getConfigFilePath();
|
|
25
|
+
try {
|
|
26
|
+
const rawContents = await fs.readFile(configFilePath, 'utf8');
|
|
27
|
+
const parsedJson = JSON.parse(rawContents);
|
|
28
|
+
return credentialsSchema.parse(parsedJson);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function saveCredentials(params) {
|
|
38
|
+
const configFilePath = params.configFilePath ?? getConfigFilePath();
|
|
39
|
+
const configDirectoryPath = path.dirname(configFilePath);
|
|
40
|
+
const validatedCredentials = credentialsSchema.parse(params.credentials);
|
|
41
|
+
await fs.mkdir(configDirectoryPath, { recursive: true, mode: 0o700 });
|
|
42
|
+
await fs.writeFile(configFilePath, `${JSON.stringify(validatedCredentials, null, 2)}\n`, {
|
|
43
|
+
encoding: 'utf8',
|
|
44
|
+
mode: 0o600,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
export async function deleteCredentials(params = {}) {
|
|
48
|
+
const configFilePath = params.configFilePath ?? getConfigFilePath();
|
|
49
|
+
try {
|
|
50
|
+
await fs.unlink(configFilePath);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function buildDefaultCredentials(params) {
|
|
60
|
+
return {
|
|
61
|
+
apiBaseUrl: (params.apiBaseUrl ?? DEFAULT_API_BASE_URL).replace(/\/+$/, ''),
|
|
62
|
+
clientId: params.clientId,
|
|
63
|
+
clientSecret: params.clientSecret,
|
|
64
|
+
requestTimeoutMs: DEFAULT_REQUEST_TIMEOUT_MS,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=credentialStore.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentialStore.service.js","sourceRoot":"","sources":["../../src/services/credentialStore.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { OAuthCallbackResult } from '../types.js';
|
|
2
|
+
interface StartLocalCallbackServerParams {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
callbackPath: string;
|
|
6
|
+
expectedState: string;
|
|
7
|
+
timeoutMs: number;
|
|
8
|
+
}
|
|
9
|
+
interface StartLocalCallbackServerResult {
|
|
10
|
+
redirectUri: string;
|
|
11
|
+
waitForCallback: () => Promise<OAuthCallbackResult>;
|
|
12
|
+
close: () => Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export declare function startLocalCallbackServer(params: StartLocalCallbackServerParams): Promise<StartLocalCallbackServerResult>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
function parseCallbackQuery(params) {
|
|
3
|
+
const parsedUrl = new URL(params.requestUrl, 'http://localhost');
|
|
4
|
+
const code = parsedUrl.searchParams.get('code');
|
|
5
|
+
const state = parsedUrl.searchParams.get('state');
|
|
6
|
+
const error = parsedUrl.searchParams.get('error');
|
|
7
|
+
const errorDescription = parsedUrl.searchParams.get('error_description');
|
|
8
|
+
if (error) {
|
|
9
|
+
throw new Error(errorDescription ?? error);
|
|
10
|
+
}
|
|
11
|
+
if (!code || !state) {
|
|
12
|
+
throw new Error('OAuth callback is missing code or state.');
|
|
13
|
+
}
|
|
14
|
+
if (state !== params.expectedState) {
|
|
15
|
+
throw new Error('OAuth callback state does not match the value sent at login start.');
|
|
16
|
+
}
|
|
17
|
+
return { code, state };
|
|
18
|
+
}
|
|
19
|
+
function buildSuccessHtml() {
|
|
20
|
+
return `<!DOCTYPE html>
|
|
21
|
+
<html lang="en">
|
|
22
|
+
<head>
|
|
23
|
+
<meta charset="utf-8" />
|
|
24
|
+
<title>COUNT login complete</title>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<p>COUNT login complete. You can close this tab and return to your terminal.</p>
|
|
28
|
+
</body>
|
|
29
|
+
</html>`;
|
|
30
|
+
}
|
|
31
|
+
function buildErrorHtml(params) {
|
|
32
|
+
return `<!DOCTYPE html>
|
|
33
|
+
<html lang="en">
|
|
34
|
+
<head>
|
|
35
|
+
<meta charset="utf-8" />
|
|
36
|
+
<title>COUNT login failed</title>
|
|
37
|
+
</head>
|
|
38
|
+
<body>
|
|
39
|
+
<p>COUNT login failed: ${params.message}</p>
|
|
40
|
+
</body>
|
|
41
|
+
</html>`;
|
|
42
|
+
}
|
|
43
|
+
export async function startLocalCallbackServer(params) {
|
|
44
|
+
let resolveCallback;
|
|
45
|
+
let rejectCallback;
|
|
46
|
+
let timeoutHandle;
|
|
47
|
+
let callbackSettled = false;
|
|
48
|
+
const callbackPromise = new Promise((resolve, reject) => {
|
|
49
|
+
resolveCallback = resolve;
|
|
50
|
+
rejectCallback = reject;
|
|
51
|
+
});
|
|
52
|
+
const server = http.createServer((request, response) => {
|
|
53
|
+
if (!request.url) {
|
|
54
|
+
response.writeHead(400, { 'content-type': 'text/plain; charset=utf-8' });
|
|
55
|
+
response.end('Bad request');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const requestPath = new URL(request.url, 'http://localhost').pathname;
|
|
59
|
+
if (requestPath !== params.callbackPath) {
|
|
60
|
+
response.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
|
|
61
|
+
response.end('Not found');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const callbackResult = parseCallbackQuery({
|
|
66
|
+
requestUrl: request.url,
|
|
67
|
+
expectedState: params.expectedState,
|
|
68
|
+
});
|
|
69
|
+
response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
70
|
+
response.end(buildSuccessHtml());
|
|
71
|
+
if (!callbackSettled) {
|
|
72
|
+
callbackSettled = true;
|
|
73
|
+
resolveCallback?.(callbackResult);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
78
|
+
const parsedUrl = new URL(request.url, 'http://localhost');
|
|
79
|
+
const oauthProviderError = parsedUrl.searchParams.get('error');
|
|
80
|
+
response.writeHead(400, { 'content-type': 'text/html; charset=utf-8' });
|
|
81
|
+
response.end(buildErrorHtml({ message }));
|
|
82
|
+
// Ignore malformed probes (refresh, prefetch) so a later valid redirect can still succeed.
|
|
83
|
+
if (oauthProviderError && !callbackSettled) {
|
|
84
|
+
callbackSettled = true;
|
|
85
|
+
rejectCallback?.(error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
await new Promise((resolve, reject) => {
|
|
90
|
+
server.once('error', reject);
|
|
91
|
+
server.listen(params.port, params.host, () => resolve());
|
|
92
|
+
});
|
|
93
|
+
const address = server.address();
|
|
94
|
+
const redirectUri = `http://${address.address}:${address.port}${params.callbackPath}`;
|
|
95
|
+
timeoutHandle = setTimeout(() => {
|
|
96
|
+
if (!callbackSettled) {
|
|
97
|
+
callbackSettled = true;
|
|
98
|
+
rejectCallback?.(new Error(`OAuth login timed out after ${params.timeoutMs}ms.`));
|
|
99
|
+
}
|
|
100
|
+
}, params.timeoutMs);
|
|
101
|
+
const waitForCallback = async () => {
|
|
102
|
+
try {
|
|
103
|
+
return await callbackPromise;
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
if (timeoutHandle) {
|
|
107
|
+
clearTimeout(timeoutHandle);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const close = async () => {
|
|
112
|
+
if (timeoutHandle) {
|
|
113
|
+
clearTimeout(timeoutHandle);
|
|
114
|
+
}
|
|
115
|
+
await new Promise((resolve, reject) => {
|
|
116
|
+
server.close((error) => {
|
|
117
|
+
if (error) {
|
|
118
|
+
reject(error);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
resolve();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
redirectUri,
|
|
127
|
+
waitForCallback,
|
|
128
|
+
close,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=localCallbackServer.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localCallbackServer.service.js","sourceRoot":"","sources":["../../src/services/localCallbackServer.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CountCliCredentials } from '../types.js';
|
|
2
|
+
interface BuildMcpEnvironmentParams {
|
|
3
|
+
credentials: CountCliCredentials;
|
|
4
|
+
}
|
|
5
|
+
export declare function buildMcpEnvironment(params: BuildMcpEnvironmentParams): NodeJS.ProcessEnv;
|
|
6
|
+
export declare function resolvePartnerMcpEntryPath(): string;
|
|
7
|
+
interface LaunchPartnerMcpServerParams {
|
|
8
|
+
credentials: CountCliCredentials;
|
|
9
|
+
}
|
|
10
|
+
export declare function launchPartnerMcpServer(params: LaunchPartnerMcpServerParams): Promise<number>;
|
|
11
|
+
interface BuildClaudeCodeMcpConfigParams {
|
|
12
|
+
credentials: CountCliCredentials;
|
|
13
|
+
}
|
|
14
|
+
export declare function buildClaudeCodeMcpConfig(params: BuildClaudeCodeMcpConfigParams): Record<string, unknown>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
export function buildMcpEnvironment(params) {
|
|
5
|
+
const { credentials } = params;
|
|
6
|
+
return {
|
|
7
|
+
...process.env,
|
|
8
|
+
COUNT_API_URL: credentials.apiBaseUrl,
|
|
9
|
+
COUNT_CLIENT_ID: credentials.clientId,
|
|
10
|
+
COUNT_CLIENT_SECRET: credentials.clientSecret,
|
|
11
|
+
COUNT_ACCESS_TOKEN: credentials.accessToken ?? '',
|
|
12
|
+
COUNT_REFRESH_TOKEN: credentials.refreshToken ?? '',
|
|
13
|
+
COUNT_REQUEST_TIMEOUT_MS: String(credentials.requestTimeoutMs),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function resolvePartnerMcpEntryPath() {
|
|
17
|
+
// The package root export points at dist/index.js (the stdio MCP entry).
|
|
18
|
+
return require.resolve('@countfinancial/partner-mcp');
|
|
19
|
+
}
|
|
20
|
+
export async function launchPartnerMcpServer(params) {
|
|
21
|
+
const entryPath = resolvePartnerMcpEntryPath();
|
|
22
|
+
const environment = buildMcpEnvironment({ credentials: params.credentials });
|
|
23
|
+
return await new Promise((resolve, reject) => {
|
|
24
|
+
const childProcess = spawn(process.execPath, [entryPath], {
|
|
25
|
+
env: environment,
|
|
26
|
+
stdio: 'inherit',
|
|
27
|
+
});
|
|
28
|
+
childProcess.once('error', reject);
|
|
29
|
+
childProcess.once('exit', (exitCode, signal) => {
|
|
30
|
+
if (signal) {
|
|
31
|
+
reject(new Error(`COUNT MCP server exited due to signal ${signal}.`));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
resolve(exitCode ?? 0);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export function buildClaudeCodeMcpConfig(params) {
|
|
39
|
+
const entryPath = resolvePartnerMcpEntryPath();
|
|
40
|
+
const environment = buildMcpEnvironment({ credentials: params.credentials });
|
|
41
|
+
return {
|
|
42
|
+
mcpServers: {
|
|
43
|
+
count: {
|
|
44
|
+
command: process.execPath,
|
|
45
|
+
args: [entryPath],
|
|
46
|
+
env: {
|
|
47
|
+
COUNT_API_URL: environment.COUNT_API_URL,
|
|
48
|
+
COUNT_CLIENT_ID: environment.COUNT_CLIENT_ID,
|
|
49
|
+
COUNT_CLIENT_SECRET: environment.COUNT_CLIENT_SECRET,
|
|
50
|
+
COUNT_ACCESS_TOKEN: environment.COUNT_ACCESS_TOKEN,
|
|
51
|
+
COUNT_REFRESH_TOKEN: environment.COUNT_REFRESH_TOKEN,
|
|
52
|
+
COUNT_REQUEST_TIMEOUT_MS: environment.COUNT_REQUEST_TIMEOUT_MS,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=mcpLauncher.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpLauncher.service.js","sourceRoot":"","sources":["../../src/services/mcpLauncher.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAM/C,MAAM,UAAU,mBAAmB,CAAC,MAAiC;IACnE,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAE/B,OAAO;QACL,GAAG,OAAO,CAAC,GAAG;QACd,aAAa,EAAE,WAAW,CAAC,UAAU;QACrC,eAAe,EAAE,WAAW,CAAC,QAAQ;QACrC,mBAAmB,EAAE,WAAW,CAAC,YAAY;QAC7C,kBAAkB,EAAE,WAAW,CAAC,WAAW,IAAI,EAAE;QACjD,mBAAmB,EAAE,WAAW,CAAC,YAAY,IAAI,EAAE;QACnD,wBAAwB,EAAE,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC;KAC/D,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,0BAA0B;IACxC,yEAAyE;IACzE,OAAO,OAAO,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC;AACxD,CAAC;AAMD,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,MAAoC;IAC/E,MAAM,SAAS,GAAG,0BAA0B,EAAE,CAAC;IAC/C,MAAM,WAAW,GAAG,mBAAmB,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAE7E,OAAO,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnD,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,EAAE;YACxD,GAAG,EAAE,WAAW;YAChB,KAAK,EAAE,SAAS;SACjB,CAAC,CAAC;QAEH,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACnC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;YAC7C,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,CAAC,IAAI,KAAK,CAAC,yCAAyC,MAAM,GAAG,CAAC,CAAC,CAAC;gBACtE,OAAO;YACT,CAAC;YAED,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAMD,MAAM,UAAU,wBAAwB,CAAC,MAAsC;IAC7E,MAAM,SAAS,GAAG,0BAA0B,EAAE,CAAC;IAC/C,MAAM,WAAW,GAAG,mBAAmB,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAE7E,OAAO;QACL,UAAU,EAAE;YACV,KAAK,EAAE;gBACL,OAAO,EAAE,OAAO,CAAC,QAAQ;gBACzB,IAAI,EAAE,CAAC,SAAS,CAAC;gBACjB,GAAG,EAAE;oBACH,aAAa,EAAE,WAAW,CAAC,aAAa;oBACxC,eAAe,EAAE,WAAW,CAAC,eAAe;oBAC5C,mBAAmB,EAAE,WAAW,CAAC,mBAAmB;oBACpD,kBAAkB,EAAE,WAAW,CAAC,kBAAkB;oBAClD,mBAAmB,EAAE,WAAW,CAAC,mBAAmB;oBACpD,wBAAwB,EAAE,WAAW,CAAC,wBAAwB;iBAC/D;aACF;SACF;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CountCliCredentials } from '../types.js';
|
|
2
|
+
interface RunOAuthLoginParams {
|
|
3
|
+
credentials: CountCliCredentials;
|
|
4
|
+
callbackHost?: string;
|
|
5
|
+
callbackPort?: number;
|
|
6
|
+
callbackPath?: string;
|
|
7
|
+
openBrowserAutomatically?: boolean;
|
|
8
|
+
fetchImplementation?: typeof fetch;
|
|
9
|
+
}
|
|
10
|
+
interface RunOAuthLoginResult {
|
|
11
|
+
credentials: CountCliCredentials;
|
|
12
|
+
}
|
|
13
|
+
export declare function runOAuthLogin(params: RunOAuthLoginParams): Promise<RunOAuthLoginResult>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { PartnerApiClient } from '@countfinancial/partner-mcp/partner-api-client';
|
|
3
|
+
import { DEFAULT_CALLBACK_HOST, DEFAULT_CALLBACK_PATH, DEFAULT_CALLBACK_PORT, OAUTH_LOGIN_TIMEOUT_MS, } from '../constants.js';
|
|
4
|
+
import { openBrowser } from './browserOpener.service.js';
|
|
5
|
+
import { startLocalCallbackServer } from './localCallbackServer.service.js';
|
|
6
|
+
function generateOAuthState() {
|
|
7
|
+
return crypto.randomBytes(24).toString('hex');
|
|
8
|
+
}
|
|
9
|
+
function buildAuthorizeInitiateUrl(params) {
|
|
10
|
+
const url = new URL('/auth2/authorize-intiate', params.apiBaseUrl);
|
|
11
|
+
url.searchParams.set('clientId', params.clientId);
|
|
12
|
+
url.searchParams.set('redirectUri', params.redirectUri);
|
|
13
|
+
url.searchParams.set('state', params.state);
|
|
14
|
+
return url.toString();
|
|
15
|
+
}
|
|
16
|
+
export async function runOAuthLogin(params) {
|
|
17
|
+
const { credentials, callbackHost = DEFAULT_CALLBACK_HOST, callbackPort = DEFAULT_CALLBACK_PORT, callbackPath = DEFAULT_CALLBACK_PATH, openBrowserAutomatically = true, fetchImplementation = fetch, } = params;
|
|
18
|
+
const oauthState = generateOAuthState();
|
|
19
|
+
const callbackServer = await startLocalCallbackServer({
|
|
20
|
+
host: callbackHost,
|
|
21
|
+
port: callbackPort,
|
|
22
|
+
callbackPath,
|
|
23
|
+
expectedState: oauthState,
|
|
24
|
+
timeoutMs: OAUTH_LOGIN_TIMEOUT_MS,
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
const initiateUrl = buildAuthorizeInitiateUrl({
|
|
28
|
+
apiBaseUrl: credentials.apiBaseUrl,
|
|
29
|
+
clientId: credentials.clientId,
|
|
30
|
+
redirectUri: callbackServer.redirectUri,
|
|
31
|
+
state: oauthState,
|
|
32
|
+
});
|
|
33
|
+
const initiateResponse = await fetchImplementation(initiateUrl, {
|
|
34
|
+
method: 'GET',
|
|
35
|
+
headers: { accept: 'application/json' },
|
|
36
|
+
});
|
|
37
|
+
const initiateBody = (await initiateResponse.json());
|
|
38
|
+
if (!initiateResponse.ok) {
|
|
39
|
+
throw new Error(initiateBody.message ?? `Failed to start OAuth login (${initiateResponse.status}).`);
|
|
40
|
+
}
|
|
41
|
+
const browserUrl = initiateBody.data?.redirectUri;
|
|
42
|
+
if (!browserUrl) {
|
|
43
|
+
throw new Error('COUNT did not return a partner sign-in URL.');
|
|
44
|
+
}
|
|
45
|
+
if (openBrowserAutomatically) {
|
|
46
|
+
await openBrowser({ url: browserUrl });
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
process.stdout.write(`Open this URL in your browser to sign in:\n${browserUrl}\n`);
|
|
50
|
+
}
|
|
51
|
+
process.stdout.write('Waiting for OAuth callback...\n');
|
|
52
|
+
const callbackResult = await callbackServer.waitForCallback();
|
|
53
|
+
const partnerClient = new PartnerApiClient({
|
|
54
|
+
config: {
|
|
55
|
+
apiBaseUrl: credentials.apiBaseUrl,
|
|
56
|
+
clientId: credentials.clientId,
|
|
57
|
+
clientSecret: credentials.clientSecret,
|
|
58
|
+
requestTimeoutMs: credentials.requestTimeoutMs,
|
|
59
|
+
},
|
|
60
|
+
fetchImplementation,
|
|
61
|
+
});
|
|
62
|
+
const exchangeResponse = await partnerClient.request({
|
|
63
|
+
method: 'POST',
|
|
64
|
+
path: '/partners/grant-access-token',
|
|
65
|
+
body: {
|
|
66
|
+
grantType: 'authorization_code',
|
|
67
|
+
code: callbackResult.code,
|
|
68
|
+
state: callbackResult.state,
|
|
69
|
+
},
|
|
70
|
+
requiresUserAuth: false,
|
|
71
|
+
retryOnUnauthorized: false,
|
|
72
|
+
});
|
|
73
|
+
const tokenSet = exchangeResponse.data?.result;
|
|
74
|
+
if (!tokenSet?.accessToken || !tokenSet.refreshToken) {
|
|
75
|
+
throw new Error('Token exchange succeeded but did not return access and refresh tokens.');
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
credentials: {
|
|
79
|
+
...credentials,
|
|
80
|
+
accessToken: tokenSet.accessToken,
|
|
81
|
+
refreshToken: tokenSet.refreshToken,
|
|
82
|
+
workspaceId: tokenSet.workspaceId,
|
|
83
|
+
workspaceName: tokenSet.workspaceName,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await callbackServer.close();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=oauthLogin.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthLogin.service.js","sourceRoot":"","sources":["../../src/services/oauthLogin.service.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CountCliCredentials {
|
|
2
|
+
apiBaseUrl: string;
|
|
3
|
+
clientId: string;
|
|
4
|
+
clientSecret: string;
|
|
5
|
+
accessToken?: string;
|
|
6
|
+
refreshToken?: string;
|
|
7
|
+
workspaceId?: string;
|
|
8
|
+
workspaceName?: string;
|
|
9
|
+
requestTimeoutMs: number;
|
|
10
|
+
}
|
|
11
|
+
export interface OAuthCallbackResult {
|
|
12
|
+
code: string;
|
|
13
|
+
state: string;
|
|
14
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@countfinancial/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "COUNT Partner CLI — OAuth login and local MCP server for Claude Code, Cursor, and agents.",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"count": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/getcount/count-cli.git"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/getcount/count-cli#readme",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/getcount/count-cli/issues"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"prepare": "npm run build",
|
|
25
|
+
"check:types": "tsc -p tsconfig.json --noEmit",
|
|
26
|
+
"test": "npm run build && node --test \"dist/__tests__/**/*.test.js\""
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@countfinancial/partner-mcp": "0.1.0",
|
|
33
|
+
"commander": "^14.0.3",
|
|
34
|
+
"zod": "^4.4.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^25.6.0",
|
|
38
|
+
"typescript": "^6.0.3"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20"
|
|
42
|
+
}
|
|
43
|
+
}
|