@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.
Files changed (53) hide show
  1. package/README.md +100 -0
  2. package/dist/__tests__/credentialStore.test.d.ts +1 -0
  3. package/dist/__tests__/credentialStore.test.js +34 -0
  4. package/dist/__tests__/credentialStore.test.js.map +1 -0
  5. package/dist/__tests__/init.command.test.d.ts +1 -0
  6. package/dist/__tests__/init.command.test.js +69 -0
  7. package/dist/__tests__/init.command.test.js.map +1 -0
  8. package/dist/__tests__/localCallbackServer.test.d.ts +1 -0
  9. package/dist/__tests__/localCallbackServer.test.js +60 -0
  10. package/dist/__tests__/localCallbackServer.test.js.map +1 -0
  11. package/dist/cli.d.ts +6 -0
  12. package/dist/cli.js +58 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/commands/init.command.d.ts +8 -0
  15. package/dist/commands/init.command.js +43 -0
  16. package/dist/commands/init.command.js.map +1 -0
  17. package/dist/commands/login.command.d.ts +6 -0
  18. package/dist/commands/login.command.js +19 -0
  19. package/dist/commands/login.command.js.map +1 -0
  20. package/dist/commands/logout.command.d.ts +1 -0
  21. package/dist/commands/logout.command.js +6 -0
  22. package/dist/commands/logout.command.js.map +1 -0
  23. package/dist/commands/mcp.command.d.ts +2 -0
  24. package/dist/commands/mcp.command.js +23 -0
  25. package/dist/commands/mcp.command.js.map +1 -0
  26. package/dist/commands/status.command.d.ts +1 -0
  27. package/dist/commands/status.command.js +22 -0
  28. package/dist/commands/status.command.js.map +1 -0
  29. package/dist/constants.d.ts +10 -0
  30. package/dist/constants.js +11 -0
  31. package/dist/constants.js.map +1 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.js +12 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/services/browserOpener.service.d.ts +5 -0
  36. package/dist/services/browserOpener.service.js +16 -0
  37. package/dist/services/browserOpener.service.js.map +1 -0
  38. package/dist/services/credentialStore.service.d.ts +26 -0
  39. package/dist/services/credentialStore.service.js +67 -0
  40. package/dist/services/credentialStore.service.js.map +1 -0
  41. package/dist/services/localCallbackServer.service.d.ts +15 -0
  42. package/dist/services/localCallbackServer.service.js +131 -0
  43. package/dist/services/localCallbackServer.service.js.map +1 -0
  44. package/dist/services/mcpLauncher.service.d.ts +15 -0
  45. package/dist/services/mcpLauncher.service.js +58 -0
  46. package/dist/services/mcpLauncher.service.js.map +1 -0
  47. package/dist/services/oauthLogin.service.d.ts +14 -0
  48. package/dist/services/oauthLogin.service.js +91 -0
  49. package/dist/services/oauthLogin.service.js.map +1 -0
  50. package/dist/types.d.ts +14 -0
  51. package/dist/types.js +2 -0
  52. package/dist/types.js.map +1 -0
  53. 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
@@ -0,0 +1,6 @@
1
+ import { Command } from 'commander';
2
+ interface CreateCliParams {
3
+ version: string;
4
+ }
5
+ export declare function createCli(params: CreateCliParams): Command;
6
+ export {};
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
@@ -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,8 @@
1
+ interface RunInitCommandParams {
2
+ clientId: string;
3
+ clientSecret: string;
4
+ apiBaseUrl?: string;
5
+ configFilePath?: string;
6
+ }
7
+ export declare function runInitCommand(params: RunInitCommandParams): Promise<void>;
8
+ export {};
@@ -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,6 @@
1
+ interface RunLoginCommandParams {
2
+ callbackPort?: number;
3
+ openBrowserAutomatically?: boolean;
4
+ }
5
+ export declare function runLoginCommand(params?: RunLoginCommandParams): Promise<void>;
6
+ export {};
@@ -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,2 @@
1
+ export declare function runMcpStartCommand(): Promise<void>;
2
+ export declare function runMcpPrintConfigCommand(): Promise<void>;
@@ -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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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,5 @@
1
+ interface OpenBrowserParams {
2
+ url: string;
3
+ }
4
+ export declare function openBrowser(params: OpenBrowserParams): Promise<void>;
5
+ export {};
@@ -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":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gDAAgD,CAAC;AAElF,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AA+B5E,SAAS,kBAAkB;IACzB,OAAO,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC;AASD,SAAS,yBAAyB,CAAC,MAAuC;IACxE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,0BAA0B,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IACnE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAClD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IACxD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5C,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAA2B;IAC7D,MAAM,EACJ,WAAW,EACX,YAAY,GAAG,qBAAqB,EACpC,YAAY,GAAG,qBAAqB,EACpC,YAAY,GAAG,qBAAqB,EACpC,wBAAwB,GAAG,IAAI,EAC/B,mBAAmB,GAAG,KAAK,GAC5B,GAAG,MAAM,CAAC;IACX,MAAM,UAAU,GAAG,kBAAkB,EAAE,CAAC;IACxC,MAAM,cAAc,GAAG,MAAM,wBAAwB,CAAC;QACpD,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,YAAY;QAClB,YAAY;QACZ,aAAa,EAAE,UAAU;QACzB,SAAS,EAAE,sBAAsB;KAClC,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,yBAAyB,CAAC;YAC5C,UAAU,EAAE,WAAW,CAAC,UAAU;YAClC,QAAQ,EAAE,WAAW,CAAC,QAAQ;YAC9B,WAAW,EAAE,cAAc,CAAC,WAAW;YACvC,KAAK,EAAE,UAAU;SAClB,CAAC,CAAC;QAEH,MAAM,gBAAgB,GAAG,MAAM,mBAAmB,CAAC,WAAW,EAAE;YAC9D,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAC;QACH,MAAM,YAAY,GAAG,CAAC,MAAM,gBAAgB,CAAC,IAAI,EAAE,CAA8B,CAAC;QAElF,IAAI,CAAC,gBAAgB,CAAC,EAAE,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,OAAO,IAAI,gCAAgC,gBAAgB,CAAC,MAAM,IAAI,CAAC,CAAC;QACvG,CAAC;QAED,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,EAAE,WAAW,CAAC;QAClD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QACjE,CAAC;QAED,IAAI,wBAAwB,EAAE,CAAC;YAC7B,MAAM,WAAW,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC;QACzC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8CAA8C,UAAU,IAAI,CAAC,CAAC;QACrF,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACxD,MAAM,cAAc,GAAG,MAAM,cAAc,CAAC,eAAe,EAAE,CAAC;QAE9D,MAAM,aAAa,GAAG,IAAI,gBAAgB,CAAC;YACzC,MAAM,EAAE;gBACN,UAAU,EAAE,WAAW,CAAC,UAAU;gBAClC,QAAQ,EAAE,WAAW,CAAC,QAAQ;gBAC9B,YAAY,EAAE,WAAW,CAAC,YAAY;gBACtC,gBAAgB,EAAE,WAAW,CAAC,gBAAgB;aAC/C;YACD,mBAAmB;SACpB,CAAC,CAAC;QAEH,MAAM,gBAAgB,GAAG,MAAM,aAAa,CAAC,OAAO,CAAwB;YAC1E,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,8BAA8B;YACpC,IAAI,EAAE;gBACJ,SAAS,EAAE,oBAAoB;gBAC/B,IAAI,EAAE,cAAc,CAAC,IAAI;gBACzB,KAAK,EAAE,cAAc,CAAC,KAAK;aAC5B;YACD,gBAAgB,EAAE,KAAK;YACvB,mBAAmB,EAAE,KAAK;SAC3B,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC;QAC/C,IAAI,CAAC,QAAQ,EAAE,WAAW,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC;YACrD,MAAM,IAAI,KAAK,CAAC,wEAAwE,CAAC,CAAC;QAC5F,CAAC;QAED,OAAO;YACL,WAAW,EAAE;gBACX,GAAG,WAAW;gBACd,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,YAAY,EAAE,QAAQ,CAAC,YAAY;gBACnC,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,aAAa,EAAE,QAAQ,CAAC,aAAa;aACtC;SACF,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,cAAc,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC;AACH,CAAC"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -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
+ }