@clinebot/core 0.0.22 → 0.0.23
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/dist/ClineCore.d.ts +110 -0
- package/dist/ClineCore.d.ts.map +1 -0
- package/dist/account/cline-account-service.d.ts +2 -1
- package/dist/account/cline-account-service.d.ts.map +1 -1
- package/dist/account/index.d.ts +1 -1
- package/dist/account/index.d.ts.map +1 -1
- package/dist/account/rpc.d.ts +3 -1
- package/dist/account/rpc.d.ts.map +1 -1
- package/dist/account/types.d.ts +3 -0
- package/dist/account/types.d.ts.map +1 -1
- package/dist/agents/plugin-loader.d.ts.map +1 -1
- package/dist/agents/plugin-sandbox-bootstrap.js +17 -17
- package/dist/auth/client.d.ts +1 -1
- package/dist/auth/client.d.ts.map +1 -1
- package/dist/auth/cline.d.ts +1 -1
- package/dist/auth/cline.d.ts.map +1 -1
- package/dist/auth/codex.d.ts +1 -1
- package/dist/auth/codex.d.ts.map +1 -1
- package/dist/auth/oca.d.ts +1 -1
- package/dist/auth/oca.d.ts.map +1 -1
- package/dist/auth/utils.d.ts +2 -2
- package/dist/auth/utils.d.ts.map +1 -1
- package/dist/index.d.ts +50 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +949 -0
- package/dist/providers/local-provider-service.d.ts +4 -4
- package/dist/providers/local-provider-service.d.ts.map +1 -1
- package/dist/runtime/runtime-builder.d.ts +1 -0
- package/dist/runtime/runtime-builder.d.ts.map +1 -1
- package/dist/runtime/session-runtime.d.ts +2 -1
- package/dist/runtime/session-runtime.d.ts.map +1 -1
- package/dist/runtime/team-runtime-registry.d.ts +13 -0
- package/dist/runtime/team-runtime-registry.d.ts.map +1 -0
- package/dist/session/default-session-manager.d.ts +2 -2
- package/dist/session/default-session-manager.d.ts.map +1 -1
- package/dist/session/rpc-runtime-ensure.d.ts +53 -0
- package/dist/session/rpc-runtime-ensure.d.ts.map +1 -0
- package/dist/session/session-config-builder.d.ts +2 -3
- package/dist/session/session-config-builder.d.ts.map +1 -1
- package/dist/session/session-host.d.ts +8 -18
- package/dist/session/session-host.d.ts.map +1 -1
- package/dist/session/session-manager.d.ts +1 -1
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manifest.d.ts +1 -2
- package/dist/session/session-manifest.d.ts.map +1 -1
- package/dist/session/unified-session-persistence-service.d.ts +2 -2
- package/dist/session/unified-session-persistence-service.d.ts.map +1 -1
- package/dist/session/utils/helpers.d.ts +1 -1
- package/dist/session/utils/helpers.d.ts.map +1 -1
- package/dist/session/utils/types.d.ts +1 -1
- package/dist/session/utils/types.d.ts.map +1 -1
- package/dist/storage/provider-settings-legacy-migration.d.ts.map +1 -1
- package/dist/telemetry/OpenTelemetryProvider.d.ts.map +1 -1
- package/dist/telemetry/distinct-id.d.ts +2 -0
- package/dist/telemetry/distinct-id.d.ts.map +1 -0
- package/dist/telemetry/{opentelemetry.d.ts → index.d.ts} +1 -1
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +28 -0
- package/dist/tools/constants.d.ts +1 -1
- package/dist/tools/constants.d.ts.map +1 -1
- package/dist/tools/definitions.d.ts +3 -3
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/executors/apply-patch.d.ts +1 -1
- package/dist/tools/executors/apply-patch.d.ts.map +1 -1
- package/dist/tools/executors/bash.d.ts +1 -1
- package/dist/tools/executors/bash.d.ts.map +1 -1
- package/dist/tools/executors/editor.d.ts +1 -1
- package/dist/tools/executors/editor.d.ts.map +1 -1
- package/dist/tools/executors/file-read.d.ts +1 -1
- package/dist/tools/executors/file-read.d.ts.map +1 -1
- package/dist/tools/executors/index.d.ts +14 -14
- package/dist/tools/executors/index.d.ts.map +1 -1
- package/dist/tools/executors/search.d.ts +1 -1
- package/dist/tools/executors/search.d.ts.map +1 -1
- package/dist/tools/executors/web-fetch.d.ts +1 -1
- package/dist/tools/executors/web-fetch.d.ts.map +1 -1
- package/dist/tools/helpers.d.ts +1 -1
- package/dist/tools/helpers.d.ts.map +1 -1
- package/dist/tools/index.d.ts +10 -10
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/model-tool-routing.d.ts +1 -1
- package/dist/tools/model-tool-routing.d.ts.map +1 -1
- package/dist/tools/presets.d.ts +1 -1
- package/dist/tools/presets.d.ts.map +1 -1
- package/dist/types/common.d.ts +17 -8
- package/dist/types/common.d.ts.map +1 -1
- package/dist/types/config.d.ts +4 -3
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/provider-settings.d.ts +1 -1
- package/dist/types/provider-settings.d.ts.map +1 -1
- package/dist/types.d.ts +5 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +44 -38
- package/src/ClineCore.ts +137 -0
- package/src/account/cline-account-service.test.ts +101 -0
- package/src/account/cline-account-service.ts +300 -0
- package/src/account/featurebase-token.test.ts +175 -0
- package/src/account/index.ts +23 -0
- package/src/account/rpc.test.ts +63 -0
- package/src/account/rpc.ts +185 -0
- package/src/account/types.ts +102 -0
- package/src/agents/agent-config-loader.test.ts +236 -0
- package/src/agents/agent-config-loader.ts +108 -0
- package/src/agents/agent-config-parser.ts +198 -0
- package/src/agents/hooks-config-loader.test.ts +20 -0
- package/src/agents/hooks-config-loader.ts +118 -0
- package/src/agents/index.ts +85 -0
- package/src/agents/plugin-config-loader.test.ts +140 -0
- package/src/agents/plugin-config-loader.ts +97 -0
- package/src/agents/plugin-loader.test.ts +210 -0
- package/src/agents/plugin-loader.ts +175 -0
- package/src/agents/plugin-sandbox-bootstrap.ts +448 -0
- package/src/agents/plugin-sandbox.test.ts +296 -0
- package/src/agents/plugin-sandbox.ts +341 -0
- package/src/agents/unified-config-file-watcher.test.ts +196 -0
- package/src/agents/unified-config-file-watcher.ts +483 -0
- package/src/agents/user-instruction-config-loader.test.ts +158 -0
- package/src/agents/user-instruction-config-loader.ts +438 -0
- package/src/auth/client.test.ts +40 -0
- package/src/auth/client.ts +25 -0
- package/src/auth/cline.test.ts +130 -0
- package/src/auth/cline.ts +420 -0
- package/src/auth/codex.test.ts +170 -0
- package/src/auth/codex.ts +491 -0
- package/src/auth/oca.test.ts +215 -0
- package/src/auth/oca.ts +573 -0
- package/src/auth/server.ts +216 -0
- package/src/auth/types.ts +81 -0
- package/src/auth/utils.test.ts +128 -0
- package/src/auth/utils.ts +247 -0
- package/src/chat/chat-schema.ts +82 -0
- package/src/index.ts +479 -0
- package/src/input/file-indexer.d.ts +11 -0
- package/src/input/file-indexer.test.ts +127 -0
- package/src/input/file-indexer.ts +327 -0
- package/src/input/index.ts +7 -0
- package/src/input/mention-enricher.test.ts +85 -0
- package/src/input/mention-enricher.ts +122 -0
- package/src/mcp/config-loader.test.ts +238 -0
- package/src/mcp/config-loader.ts +219 -0
- package/src/mcp/index.ts +26 -0
- package/src/mcp/manager.test.ts +106 -0
- package/src/mcp/manager.ts +262 -0
- package/src/mcp/types.ts +88 -0
- package/src/providers/local-provider-registry.ts +232 -0
- package/src/providers/local-provider-service.test.ts +783 -0
- package/src/providers/local-provider-service.ts +471 -0
- package/src/runtime/commands.test.ts +98 -0
- package/src/runtime/commands.ts +83 -0
- package/src/runtime/hook-file-hooks.test.ts +237 -0
- package/src/runtime/hook-file-hooks.ts +859 -0
- package/src/runtime/index.ts +37 -0
- package/src/runtime/rules.ts +34 -0
- package/src/runtime/runtime-builder.team-persistence.test.ts +245 -0
- package/src/runtime/runtime-builder.test.ts +371 -0
- package/src/runtime/runtime-builder.ts +631 -0
- package/src/runtime/runtime-parity.test.ts +143 -0
- package/src/runtime/sandbox/subprocess-sandbox.ts +231 -0
- package/src/runtime/session-runtime.ts +49 -0
- package/src/runtime/skills.ts +44 -0
- package/src/runtime/team-runtime-registry.ts +46 -0
- package/src/runtime/tool-approval.ts +104 -0
- package/src/runtime/workflows.test.ts +119 -0
- package/src/runtime/workflows.ts +45 -0
- package/src/session/default-session-manager.e2e.test.ts +384 -0
- package/src/session/default-session-manager.test.ts +1931 -0
- package/src/session/default-session-manager.ts +1422 -0
- package/src/session/file-session-service.ts +280 -0
- package/src/session/index.ts +45 -0
- package/src/session/rpc-runtime-ensure.ts +521 -0
- package/src/session/rpc-session-service.ts +107 -0
- package/src/session/rpc-spawn-lease.test.ts +49 -0
- package/src/session/rpc-spawn-lease.ts +122 -0
- package/src/session/runtime-oauth-token-manager.test.ts +137 -0
- package/src/session/runtime-oauth-token-manager.ts +272 -0
- package/src/session/session-agent-events.ts +248 -0
- package/src/session/session-artifacts.ts +106 -0
- package/src/session/session-config-builder.ts +113 -0
- package/src/session/session-graph.ts +92 -0
- package/src/session/session-host.test.ts +89 -0
- package/src/session/session-host.ts +205 -0
- package/src/session/session-manager.ts +69 -0
- package/src/session/session-manifest.ts +29 -0
- package/src/session/session-service.team-persistence.test.ts +48 -0
- package/src/session/session-service.ts +673 -0
- package/src/session/session-team-coordination.ts +229 -0
- package/src/session/session-telemetry.ts +100 -0
- package/src/session/sqlite-rpc-session-backend.ts +303 -0
- package/src/session/unified-session-persistence-service.test.ts +85 -0
- package/src/session/unified-session-persistence-service.ts +994 -0
- package/src/session/utils/helpers.ts +139 -0
- package/src/session/utils/types.ts +57 -0
- package/src/session/utils/usage.ts +32 -0
- package/src/session/workspace-manager.ts +98 -0
- package/src/session/workspace-manifest.ts +100 -0
- package/src/storage/artifact-store.ts +1 -0
- package/src/storage/file-team-store.ts +257 -0
- package/src/storage/index.ts +11 -0
- package/src/storage/provider-settings-legacy-migration.test.ts +424 -0
- package/src/storage/provider-settings-legacy-migration.ts +826 -0
- package/src/storage/provider-settings-manager.test.ts +191 -0
- package/src/storage/provider-settings-manager.ts +152 -0
- package/src/storage/session-store.ts +1 -0
- package/src/storage/sqlite-session-store.ts +275 -0
- package/src/storage/sqlite-team-store.ts +454 -0
- package/src/storage/team-store.ts +40 -0
- package/src/team/index.ts +4 -0
- package/src/team/projections.ts +285 -0
- package/src/telemetry/ITelemetryAdapter.ts +94 -0
- package/src/telemetry/LoggerTelemetryAdapter.test.ts +42 -0
- package/src/telemetry/LoggerTelemetryAdapter.ts +114 -0
- package/src/telemetry/OpenTelemetryAdapter.test.ts +157 -0
- package/src/telemetry/OpenTelemetryAdapter.ts +348 -0
- package/src/telemetry/OpenTelemetryProvider.test.ts +113 -0
- package/src/telemetry/OpenTelemetryProvider.ts +325 -0
- package/src/telemetry/TelemetryService.test.ts +134 -0
- package/src/telemetry/TelemetryService.ts +141 -0
- package/src/telemetry/core-events.ts +400 -0
- package/src/telemetry/distinct-id.test.ts +57 -0
- package/src/telemetry/distinct-id.ts +58 -0
- package/src/telemetry/index.ts +20 -0
- package/src/tools/constants.ts +35 -0
- package/src/tools/definitions.test.ts +704 -0
- package/src/tools/definitions.ts +709 -0
- package/src/tools/executors/apply-patch-parser.ts +520 -0
- package/src/tools/executors/apply-patch.ts +359 -0
- package/src/tools/executors/bash.test.ts +87 -0
- package/src/tools/executors/bash.ts +207 -0
- package/src/tools/executors/editor.test.ts +35 -0
- package/src/tools/executors/editor.ts +219 -0
- package/src/tools/executors/file-read.test.ts +49 -0
- package/src/tools/executors/file-read.ts +110 -0
- package/src/tools/executors/index.ts +87 -0
- package/src/tools/executors/search.ts +278 -0
- package/src/tools/executors/web-fetch.ts +259 -0
- package/src/tools/helpers.ts +130 -0
- package/src/tools/index.ts +169 -0
- package/src/tools/model-tool-routing.test.ts +86 -0
- package/src/tools/model-tool-routing.ts +132 -0
- package/src/tools/presets.test.ts +62 -0
- package/src/tools/presets.ts +168 -0
- package/src/tools/schemas.ts +327 -0
- package/src/tools/types.ts +329 -0
- package/src/types/common.ts +26 -0
- package/src/types/config.ts +86 -0
- package/src/types/events.ts +74 -0
- package/src/types/index.ts +24 -0
- package/src/types/provider-settings.ts +43 -0
- package/src/types/sessions.ts +16 -0
- package/src/types/storage.ts +64 -0
- package/src/types/workspace.ts +7 -0
- package/src/types.ts +132 -0
- package/src/version.ts +3 -0
- package/dist/index.node.d.ts +0 -47
- package/dist/index.node.d.ts.map +0 -1
- package/dist/index.node.js +0 -948
- package/dist/telemetry/opentelemetry.d.ts.map +0 -1
- package/dist/telemetry/opentelemetry.js +0 -27
package/src/ClineCore.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentConfig,
|
|
3
|
+
ToolApprovalRequest,
|
|
4
|
+
ToolApprovalResult,
|
|
5
|
+
} from "@clinebot/agents";
|
|
6
|
+
import type { ITelemetryService } from "@clinebot/shared";
|
|
7
|
+
import {
|
|
8
|
+
createSessionHost,
|
|
9
|
+
type SessionBackend,
|
|
10
|
+
type SessionHost,
|
|
11
|
+
} from "./session/session-host";
|
|
12
|
+
import type { ToolExecutors } from "./tools";
|
|
13
|
+
|
|
14
|
+
/** Advanced options for connecting to or spawning the Cline RPC server. */
|
|
15
|
+
export interface RpcOptions {
|
|
16
|
+
/**
|
|
17
|
+
* The address of the Cline RPC server to connect to.
|
|
18
|
+
* Defaults to the `CLINE_RPC_ADDRESS` env var, or the SDK default address if unset.
|
|
19
|
+
*/
|
|
20
|
+
address?: string;
|
|
21
|
+
/**
|
|
22
|
+
* When `true` (default), automatically spawns a background RPC server process if one is
|
|
23
|
+
* not already running.
|
|
24
|
+
*/
|
|
25
|
+
autoStart?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Number of connection attempts made to the RPC server after it is spawned.
|
|
28
|
+
* Defaults to `5`.
|
|
29
|
+
*/
|
|
30
|
+
connectAttempts?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Milliseconds to wait between RPC connection attempts. Defaults to `100`.
|
|
33
|
+
*/
|
|
34
|
+
connectDelayMs?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ClineCoreOptions {
|
|
38
|
+
/**
|
|
39
|
+
* A human-readable name for this SDK client (e.g. `"my-app"`, `"acme-bot"`).
|
|
40
|
+
* Used to identify the consumer in telemetry and logs.
|
|
41
|
+
*/
|
|
42
|
+
clientName?: string;
|
|
43
|
+
/**
|
|
44
|
+
* A stable identifier for this machine or user, used for telemetry attribution.
|
|
45
|
+
* Defaults to the system machine ID, falling back to a generated `cl-<nanoid>` persisted
|
|
46
|
+
* at `~/.cline/data/machine-id`.
|
|
47
|
+
*/
|
|
48
|
+
distinctId?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Controls how the session backend is selected:
|
|
51
|
+
* - `"auto"` (default) — connects to a running RPC server if available, starts one in the
|
|
52
|
+
* background if `rpc.autoStart` is true, and falls back to local SQLite/file storage.
|
|
53
|
+
* - `"rpc"` — requires an RPC server; throws if one is not reachable.
|
|
54
|
+
* - `"local"` — always uses local SQLite (or file-based) storage; never touches RPC.
|
|
55
|
+
*/
|
|
56
|
+
backendMode?: "auto" | "rpc" | "local";
|
|
57
|
+
/**
|
|
58
|
+
* RPC server connection options. Only relevant when `backendMode` is `"auto"` or `"rpc"`.
|
|
59
|
+
*/
|
|
60
|
+
rpc?: RpcOptions;
|
|
61
|
+
/**
|
|
62
|
+
* Override one or more default tool executors (e.g. file I/O, shell, browser).
|
|
63
|
+
* Partial — only the keys you supply are replaced; the rest use built-in implementations.
|
|
64
|
+
*/
|
|
65
|
+
defaultToolExecutors?: Partial<ToolExecutors>;
|
|
66
|
+
/**
|
|
67
|
+
* Telemetry service instance to use for capturing events and usage.
|
|
68
|
+
* If omitted, telemetry is a no-op.
|
|
69
|
+
*/
|
|
70
|
+
telemetry?: ITelemetryService;
|
|
71
|
+
/**
|
|
72
|
+
* Per-tool approval policies that control whether a tool runs automatically,
|
|
73
|
+
* requires user confirmation, or is blocked entirely.
|
|
74
|
+
*/
|
|
75
|
+
toolPolicies?: AgentConfig["toolPolicies"];
|
|
76
|
+
/**
|
|
77
|
+
* Called before any tool is executed that requires explicit user approval.
|
|
78
|
+
* Return `{ approved: true }` to allow or `{ approved: false }` to deny.
|
|
79
|
+
* If omitted, all approval-required tools are auto-denied.
|
|
80
|
+
*/
|
|
81
|
+
requestToolApproval?: (
|
|
82
|
+
request: ToolApprovalRequest,
|
|
83
|
+
) => Promise<ToolApprovalResult>;
|
|
84
|
+
/**
|
|
85
|
+
* An already-constructed session backend to use instead of resolving one automatically.
|
|
86
|
+
* Intended for testing or embedding a custom persistence layer.
|
|
87
|
+
* @internal
|
|
88
|
+
*/
|
|
89
|
+
sessionService?: SessionBackend;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The primary entry point for the Cline Core SDK.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* import { ClineCore } from "@clinebot/core";
|
|
98
|
+
*
|
|
99
|
+
* const cline = await ClineCore.create({ clientName: "my-app" });
|
|
100
|
+
* const session = await cline.start({ ... });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export class ClineCore implements SessionHost {
|
|
104
|
+
readonly clientName: string | undefined;
|
|
105
|
+
private readonly host: SessionHost;
|
|
106
|
+
|
|
107
|
+
private constructor(host: SessionHost, clientName: string | undefined) {
|
|
108
|
+
this.clientName = clientName;
|
|
109
|
+
this.host = host;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static async create(options: ClineCoreOptions = {}): Promise<ClineCore> {
|
|
113
|
+
const host = await createSessionHost(options);
|
|
114
|
+
return new ClineCore(host, options.clientName);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
start: SessionHost["start"] = (...args) => this.host.start(...args);
|
|
118
|
+
send: SessionHost["send"] = (...args) => this.host.send(...args);
|
|
119
|
+
getAccumulatedUsage: SessionHost["getAccumulatedUsage"] = (...args) =>
|
|
120
|
+
this.host.getAccumulatedUsage(...args);
|
|
121
|
+
abort: SessionHost["abort"] = (...args) => this.host.abort(...args);
|
|
122
|
+
stop: SessionHost["stop"] = (...args) => this.host.stop(...args);
|
|
123
|
+
dispose: SessionHost["dispose"] = (...args) => this.host.dispose(...args);
|
|
124
|
+
get: SessionHost["get"] = (...args) => this.host.get(...args);
|
|
125
|
+
list: SessionHost["list"] = (...args) => this.host.list(...args);
|
|
126
|
+
delete: SessionHost["delete"] = (...args) => this.host.delete(...args);
|
|
127
|
+
readMessages: SessionHost["readMessages"] = (...args) =>
|
|
128
|
+
this.host.readMessages(...args);
|
|
129
|
+
readTranscript: SessionHost["readTranscript"] = (...args) =>
|
|
130
|
+
this.host.readTranscript(...args);
|
|
131
|
+
readHooks: SessionHost["readHooks"] = (...args) =>
|
|
132
|
+
this.host.readHooks(...args);
|
|
133
|
+
subscribe: SessionHost["subscribe"] = (...args) =>
|
|
134
|
+
this.host.subscribe(...args);
|
|
135
|
+
updateSessionModel: SessionHost["updateSessionModel"] = (...args) =>
|
|
136
|
+
this.host.updateSessionModel?.(...args) ?? Promise.resolve();
|
|
137
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { ClineAccountService } from "./cline-account-service";
|
|
3
|
+
|
|
4
|
+
describe("ClineAccountService", () => {
|
|
5
|
+
it("fetches current user balance and sends auth header", async () => {
|
|
6
|
+
const fetchImpl = vi.fn(async (input: unknown, init?: RequestInit) => {
|
|
7
|
+
expect(String(input)).toBe(
|
|
8
|
+
"https://api.cline.bot/api/v1/users/user-1/balance",
|
|
9
|
+
);
|
|
10
|
+
expect(init?.headers).toMatchObject({
|
|
11
|
+
Authorization: "Bearer workos:token-123",
|
|
12
|
+
});
|
|
13
|
+
return new Response(
|
|
14
|
+
JSON.stringify({
|
|
15
|
+
success: true,
|
|
16
|
+
data: { balance: 5, userId: "user-1" },
|
|
17
|
+
}),
|
|
18
|
+
{ status: 200, headers: { "Content-Type": "application/json" } },
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const service = new ClineAccountService({
|
|
23
|
+
apiBaseUrl: "https://api.cline.bot",
|
|
24
|
+
getAuthToken: async () => "workos:token-123",
|
|
25
|
+
getCurrentUserId: () => "user-1",
|
|
26
|
+
fetchImpl: fetchImpl as unknown as typeof fetch,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const balance = await service.fetchBalance();
|
|
30
|
+
expect(balance).toEqual({ balance: 5, userId: "user-1" });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("resolves organization member id from /users/me when not provided", async () => {
|
|
34
|
+
const fetchImpl = vi
|
|
35
|
+
.fn()
|
|
36
|
+
.mockResolvedValueOnce(
|
|
37
|
+
new Response(
|
|
38
|
+
JSON.stringify({
|
|
39
|
+
success: true,
|
|
40
|
+
data: {
|
|
41
|
+
id: "u-1",
|
|
42
|
+
email: "u@example.com",
|
|
43
|
+
displayName: "User",
|
|
44
|
+
photoUrl: "",
|
|
45
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
46
|
+
updatedAt: "2025-01-01T00:00:00Z",
|
|
47
|
+
organizations: [
|
|
48
|
+
{
|
|
49
|
+
active: true,
|
|
50
|
+
memberId: "member-9",
|
|
51
|
+
name: "Org",
|
|
52
|
+
organizationId: "org-1",
|
|
53
|
+
roles: ["member"],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
{ status: 200 },
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
.mockResolvedValueOnce(
|
|
62
|
+
new Response(
|
|
63
|
+
JSON.stringify({ success: true, data: { items: [{ id: "tx-1" }] } }),
|
|
64
|
+
{ status: 200 },
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const service = new ClineAccountService({
|
|
69
|
+
apiBaseUrl: "https://api.cline.bot",
|
|
70
|
+
getAuthToken: async () => "workos:token-123",
|
|
71
|
+
fetchImpl: fetchImpl as unknown as typeof fetch,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const transactions = await service.fetchOrganizationUsageTransactions({
|
|
75
|
+
organizationId: "org-1",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(fetchImpl).toHaveBeenCalledTimes(2);
|
|
79
|
+
expect(String(fetchImpl.mock.calls[1][0])).toBe(
|
|
80
|
+
"https://api.cline.bot/api/v1/organizations/org-1/members/member-9/usages",
|
|
81
|
+
);
|
|
82
|
+
expect(transactions).toEqual([{ id: "tx-1" }]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("switchAccount sends null org id for personal account", async () => {
|
|
86
|
+
const fetchImpl = vi.fn(async (_input: unknown, init?: RequestInit) => {
|
|
87
|
+
expect(init?.method).toBe("PUT");
|
|
88
|
+
expect(init?.body).toBe(JSON.stringify({ organizationId: null }));
|
|
89
|
+
return new Response(null, { status: 204 });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const service = new ClineAccountService({
|
|
93
|
+
apiBaseUrl: "https://api.cline.bot",
|
|
94
|
+
getAuthToken: async () => "workos:token-123",
|
|
95
|
+
fetchImpl: fetchImpl as unknown as typeof fetch,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await service.switchAccount(undefined);
|
|
99
|
+
expect(fetchImpl).toHaveBeenCalledTimes(1);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ClineAccountBalance,
|
|
3
|
+
ClineAccountOrganization,
|
|
4
|
+
ClineAccountOrganizationBalance,
|
|
5
|
+
ClineAccountOrganizationUsageTransaction,
|
|
6
|
+
ClineAccountPaymentTransaction,
|
|
7
|
+
ClineAccountUsageTransaction,
|
|
8
|
+
ClineAccountUser,
|
|
9
|
+
ClineOrganization,
|
|
10
|
+
FeaturebaseTokenResponse,
|
|
11
|
+
UserRemoteConfigResponse,
|
|
12
|
+
} from "./types";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
15
|
+
|
|
16
|
+
interface ClineApiEnvelope<T> {
|
|
17
|
+
success?: boolean;
|
|
18
|
+
error?: string;
|
|
19
|
+
data?: T;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ClineAccountServiceOptions {
|
|
23
|
+
apiBaseUrl: string;
|
|
24
|
+
getAuthToken: () => Promise<string | undefined | null>;
|
|
25
|
+
getCurrentUserId?: () =>
|
|
26
|
+
| Promise<string | undefined | null>
|
|
27
|
+
| string
|
|
28
|
+
| undefined
|
|
29
|
+
| null;
|
|
30
|
+
getOrganizationMemberId?: (
|
|
31
|
+
organizationId: string,
|
|
32
|
+
) => Promise<string | undefined | null> | string | undefined | null;
|
|
33
|
+
getHeaders?: () =>
|
|
34
|
+
| Promise<Record<string, string> | undefined>
|
|
35
|
+
| Record<string, string>
|
|
36
|
+
| undefined;
|
|
37
|
+
requestTimeoutMs?: number;
|
|
38
|
+
fetchImpl?: typeof fetch;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class ClineAccountService {
|
|
42
|
+
private readonly apiBaseUrl: string;
|
|
43
|
+
private readonly getAuthTokenFn: ClineAccountServiceOptions["getAuthToken"];
|
|
44
|
+
private readonly getCurrentUserIdFn: ClineAccountServiceOptions["getCurrentUserId"];
|
|
45
|
+
private readonly getOrganizationMemberIdFn: ClineAccountServiceOptions["getOrganizationMemberId"];
|
|
46
|
+
private readonly getHeadersFn: ClineAccountServiceOptions["getHeaders"];
|
|
47
|
+
private readonly requestTimeoutMs: number;
|
|
48
|
+
private readonly fetchImpl: typeof fetch;
|
|
49
|
+
|
|
50
|
+
constructor(options: ClineAccountServiceOptions) {
|
|
51
|
+
const apiBaseUrl = options.apiBaseUrl.trim();
|
|
52
|
+
if (!apiBaseUrl) {
|
|
53
|
+
throw new Error("apiBaseUrl is required");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.apiBaseUrl = apiBaseUrl;
|
|
57
|
+
this.getAuthTokenFn = options.getAuthToken;
|
|
58
|
+
this.getCurrentUserIdFn = options.getCurrentUserId;
|
|
59
|
+
this.getOrganizationMemberIdFn = options.getOrganizationMemberId;
|
|
60
|
+
this.getHeadersFn = options.getHeaders;
|
|
61
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
62
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public async fetchMe(): Promise<ClineAccountUser> {
|
|
66
|
+
return this.request<ClineAccountUser>("/api/v1/users/me");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public async fetchRemoteConfig(): Promise<UserRemoteConfigResponse> {
|
|
70
|
+
return this.request<UserRemoteConfigResponse>(
|
|
71
|
+
"/api/v1/users/me/remote-config",
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public async fetchFeaturebaseToken(): Promise<
|
|
76
|
+
FeaturebaseTokenResponse | undefined
|
|
77
|
+
> {
|
|
78
|
+
try {
|
|
79
|
+
return await this.request<FeaturebaseTokenResponse>(
|
|
80
|
+
"/api/v1/users/me/featurebase-token",
|
|
81
|
+
);
|
|
82
|
+
} catch {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public async fetchBalance(userId?: string): Promise<ClineAccountBalance> {
|
|
88
|
+
const resolvedUserId = await this.resolveUserId(userId);
|
|
89
|
+
return this.request<ClineAccountBalance>(
|
|
90
|
+
`/api/v1/users/${encodeURIComponent(resolvedUserId)}/balance`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public async fetchUsageTransactions(
|
|
95
|
+
userId?: string,
|
|
96
|
+
): Promise<ClineAccountUsageTransaction[]> {
|
|
97
|
+
const resolvedUserId = await this.resolveUserId(userId);
|
|
98
|
+
const response = await this.request<{
|
|
99
|
+
items: ClineAccountUsageTransaction[];
|
|
100
|
+
}>(`/api/v1/users/${encodeURIComponent(resolvedUserId)}/usages`);
|
|
101
|
+
return response.items ?? [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public async fetchPaymentTransactions(
|
|
105
|
+
userId?: string,
|
|
106
|
+
): Promise<ClineAccountPaymentTransaction[]> {
|
|
107
|
+
const resolvedUserId = await this.resolveUserId(userId);
|
|
108
|
+
const response = await this.request<{
|
|
109
|
+
paymentTransactions: ClineAccountPaymentTransaction[];
|
|
110
|
+
}>(`/api/v1/users/${encodeURIComponent(resolvedUserId)}/payments`);
|
|
111
|
+
return response.paymentTransactions ?? [];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public async fetchUserOrganizations(): Promise<ClineAccountOrganization[]> {
|
|
115
|
+
const me = await this.fetchMe();
|
|
116
|
+
return me.organizations ?? [];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public async fetchOrganization(
|
|
120
|
+
organizationId: string,
|
|
121
|
+
): Promise<ClineOrganization> {
|
|
122
|
+
const orgId = organizationId.trim();
|
|
123
|
+
if (!orgId) {
|
|
124
|
+
throw new Error("organizationId is required");
|
|
125
|
+
}
|
|
126
|
+
return this.request<ClineOrganization>(
|
|
127
|
+
`/api/v1/organizations/${encodeURIComponent(orgId)}`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public async fetchOrganizationBalance(
|
|
132
|
+
organizationId: string,
|
|
133
|
+
): Promise<ClineAccountOrganizationBalance> {
|
|
134
|
+
const orgId = organizationId.trim();
|
|
135
|
+
if (!orgId) {
|
|
136
|
+
throw new Error("organizationId is required");
|
|
137
|
+
}
|
|
138
|
+
return this.request<ClineAccountOrganizationBalance>(
|
|
139
|
+
`/api/v1/organizations/${encodeURIComponent(orgId)}/balance`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public async fetchOrganizationUsageTransactions(input: {
|
|
144
|
+
organizationId: string;
|
|
145
|
+
memberId?: string;
|
|
146
|
+
}): Promise<ClineAccountOrganizationUsageTransaction[]> {
|
|
147
|
+
const organizationId = input.organizationId.trim();
|
|
148
|
+
if (!organizationId) {
|
|
149
|
+
throw new Error("organizationId is required");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const memberId = await this.resolveOrganizationMemberId(
|
|
153
|
+
organizationId,
|
|
154
|
+
input.memberId,
|
|
155
|
+
);
|
|
156
|
+
const response = await this.request<{
|
|
157
|
+
items: ClineAccountOrganizationUsageTransaction[];
|
|
158
|
+
}>(
|
|
159
|
+
`/api/v1/organizations/${encodeURIComponent(organizationId)}/members/${encodeURIComponent(memberId)}/usages`,
|
|
160
|
+
);
|
|
161
|
+
return response.items ?? [];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public async switchAccount(organizationId?: string | null): Promise<void> {
|
|
165
|
+
await this.request<void>("/api/v1/users/active-account", {
|
|
166
|
+
method: "PUT",
|
|
167
|
+
body: {
|
|
168
|
+
organizationId: organizationId?.trim() || null,
|
|
169
|
+
},
|
|
170
|
+
expectNoContent: true,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private async resolveUserId(userId?: string): Promise<string> {
|
|
175
|
+
const explicit = userId?.trim();
|
|
176
|
+
if (explicit) {
|
|
177
|
+
return explicit;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const fromProvider = this.getCurrentUserIdFn
|
|
181
|
+
? await this.getCurrentUserIdFn()
|
|
182
|
+
: undefined;
|
|
183
|
+
const provided = fromProvider?.trim();
|
|
184
|
+
if (provided) {
|
|
185
|
+
return provided;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const me = await this.fetchMe();
|
|
189
|
+
if (!me.id?.trim()) {
|
|
190
|
+
throw new Error("Unable to resolve current user id");
|
|
191
|
+
}
|
|
192
|
+
return me.id;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private async resolveOrganizationMemberId(
|
|
196
|
+
organizationId: string,
|
|
197
|
+
memberId?: string,
|
|
198
|
+
): Promise<string> {
|
|
199
|
+
const explicit = memberId?.trim();
|
|
200
|
+
if (explicit) {
|
|
201
|
+
return explicit;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const fromProvider = this.getOrganizationMemberIdFn
|
|
205
|
+
? await this.getOrganizationMemberIdFn(organizationId)
|
|
206
|
+
: undefined;
|
|
207
|
+
const provided = fromProvider?.trim();
|
|
208
|
+
if (provided) {
|
|
209
|
+
return provided;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const organizations = await this.fetchUserOrganizations();
|
|
213
|
+
const resolved = organizations.find(
|
|
214
|
+
(org) => org.organizationId === organizationId,
|
|
215
|
+
)?.memberId;
|
|
216
|
+
if (!resolved?.trim()) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Unable to resolve memberId for organization ${organizationId}`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return resolved;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private async request<T>(
|
|
225
|
+
endpoint: string,
|
|
226
|
+
input?: {
|
|
227
|
+
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
228
|
+
body?: unknown;
|
|
229
|
+
expectNoContent?: boolean;
|
|
230
|
+
},
|
|
231
|
+
): Promise<T> {
|
|
232
|
+
const token = (await this.getAuthTokenFn())?.trim();
|
|
233
|
+
if (!token) {
|
|
234
|
+
throw new Error("No Cline account auth token found");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const extraHeaders = this.getHeadersFn ? await this.getHeadersFn() : {};
|
|
238
|
+
const controller = new AbortController();
|
|
239
|
+
const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const response = await this.fetchImpl(
|
|
243
|
+
new URL(endpoint, this.apiBaseUrl),
|
|
244
|
+
{
|
|
245
|
+
method: input?.method ?? "GET",
|
|
246
|
+
headers: {
|
|
247
|
+
Authorization: `Bearer ${token}`,
|
|
248
|
+
"Content-Type": "application/json",
|
|
249
|
+
...(extraHeaders ?? {}),
|
|
250
|
+
},
|
|
251
|
+
body:
|
|
252
|
+
input?.body !== undefined ? JSON.stringify(input.body) : undefined,
|
|
253
|
+
signal: controller.signal,
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
if (response.status === 204 || input?.expectNoContent) {
|
|
258
|
+
if (!response.ok) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Cline account request failed with status ${response.status}`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
return undefined as T;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const text = await response.text();
|
|
267
|
+
let parsed: unknown;
|
|
268
|
+
if (text.trim()) {
|
|
269
|
+
parsed = JSON.parse(text);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!response.ok) {
|
|
273
|
+
const message =
|
|
274
|
+
typeof parsed === "object" && parsed !== null && "error" in parsed
|
|
275
|
+
? String((parsed as { error: unknown }).error)
|
|
276
|
+
: `Cline account request failed with status ${response.status}`;
|
|
277
|
+
throw new Error(message);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
281
|
+
const envelope = parsed as ClineApiEnvelope<T>;
|
|
282
|
+
if (typeof envelope.success === "boolean") {
|
|
283
|
+
if (!envelope.success) {
|
|
284
|
+
throw new Error(envelope.error || "Cline account request failed");
|
|
285
|
+
}
|
|
286
|
+
if (envelope.data !== undefined) {
|
|
287
|
+
return envelope.data;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (parsed === undefined || parsed === null) {
|
|
293
|
+
throw new Error("Cline account response payload was empty");
|
|
294
|
+
}
|
|
295
|
+
return parsed as T;
|
|
296
|
+
} finally {
|
|
297
|
+
clearTimeout(timeout);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|