@clinebot/core 0.0.15 → 0.0.18

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.
@@ -13,6 +13,7 @@ export declare class SqliteSessionStore implements SessionStore {
13
13
  ensureSessionsDir(): string;
14
14
  sessionDbPath(): string;
15
15
  getRawDb(): SqliteDb;
16
+ close(): void;
16
17
  run(sql: string, params?: unknown[]): {
17
18
  changes?: number;
18
19
  };
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@clinebot/core",
3
- "version": "0.0.15",
3
+ "version": "0.0.18",
4
4
  "main": "./dist/index.node.js",
5
5
  "dependencies": {
6
- "@clinebot/agents": "0.0.15",
7
- "@clinebot/llms": "0.0.15",
8
- "@clinebot/shared": "0.0.15",
6
+ "@clinebot/agents": "0.0.17",
7
+ "@clinebot/llms": "0.0.17",
8
+ "@clinebot/shared": "0.0.17",
9
9
  "@opentelemetry/api": "^1.9.0",
10
10
  "@opentelemetry/api-logs": "^0.56.0",
11
11
  "@opentelemetry/exporter-logs-otlp-http": "^0.56.0",
@@ -6,6 +6,7 @@ import type {
6
6
  ClineAccountPaymentTransaction,
7
7
  ClineAccountUsageTransaction,
8
8
  ClineAccountUser,
9
+ ClineOrganization,
9
10
  UserRemoteConfigResponse,
10
11
  } from "./types";
11
12
 
@@ -102,6 +103,18 @@ export class ClineAccountService {
102
103
  return me.organizations ?? [];
103
104
  }
104
105
 
106
+ public async fetchOrganization(
107
+ organizationId: string,
108
+ ): Promise<ClineOrganization> {
109
+ const orgId = organizationId.trim();
110
+ if (!orgId) {
111
+ throw new Error("organizationId is required");
112
+ }
113
+ return this.request<ClineOrganization>(
114
+ `/api/v1/organizations/${encodeURIComponent(orgId)}`,
115
+ );
116
+ }
117
+
105
118
  public async fetchOrganizationBalance(
106
119
  organizationId: string,
107
120
  ): Promise<ClineAccountOrganizationBalance> {
@@ -17,4 +17,6 @@ export type {
17
17
  ClineAccountPaymentTransaction,
18
18
  ClineAccountUsageTransaction,
19
19
  ClineAccountUser,
20
+ ClineOrganization,
21
+ UserRemoteConfigResponse,
20
22
  } from "./types";
@@ -56,6 +56,18 @@ export interface ClineAccountPaymentTransaction {
56
56
  credits: number;
57
57
  }
58
58
 
59
+ export interface ClineOrganization {
60
+ createdAt: string;
61
+ defaultRemoteConfig?: string;
62
+ deletedAt?: string;
63
+ externalOrganizationId?: string;
64
+ id: string;
65
+ memberCount?: number;
66
+ name: string;
67
+ remoteConfigEnabled: boolean;
68
+ updatedAt: string;
69
+ }
70
+
59
71
  export interface ClineAccountOrganizationBalance {
60
72
  balance: number;
61
73
  organizationId: string;
@@ -39,7 +39,7 @@ describe("agent config YAML loader", () => {
39
39
  it("resolves default agents settings directory from CLINE_DATA_DIR", () => {
40
40
  process.env.CLINE_DATA_DIR = "/tmp/cline-data";
41
41
  expect(resolveAgentsConfigDirPath()).toBe(
42
- `/tmp/cline-data/settings/${AGENT_CONFIG_DIRECTORY_NAME}`,
42
+ join("/tmp/cline-data", "settings", AGENT_CONFIG_DIRECTORY_NAME),
43
43
  );
44
44
  });
45
45
 
@@ -47,7 +47,7 @@ describe("agent config YAML loader", () => {
47
47
  process.env.CLINE_DATA_DIR = "/tmp/cline-data";
48
48
  expect(resolveAgentConfigSearchPaths()).toEqual([
49
49
  resolveDocumentsAgentConfigDirectoryPath(),
50
- `/tmp/cline-data/settings/${AGENT_CONFIG_DIRECTORY_NAME}`,
50
+ join("/tmp/cline-data", "settings", AGENT_CONFIG_DIRECTORY_NAME),
51
51
  ]);
52
52
  });
53
53
 
@@ -57,7 +57,7 @@ describe("agent config YAML loader", () => {
57
57
  expect(definition.type).toBe("agent");
58
58
  expect(definition.directories).toEqual([
59
59
  resolveDocumentsAgentConfigDirectoryPath(),
60
- `/tmp/cline-data/settings/${AGENT_CONFIG_DIRECTORY_NAME}`,
60
+ join("/tmp/cline-data", "settings", AGENT_CONFIG_DIRECTORY_NAME),
61
61
  ]);
62
62
  expect(definition.includeFile?.("agent.yaml", "/tmp/agent.yaml")).toBe(
63
63
  true,
@@ -0,0 +1,20 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import {
3
+ HookConfigFileName,
4
+ toHookConfigFileName,
5
+ } from "./hooks-config-loader";
6
+
7
+ describe("hooks config loader", () => {
8
+ afterEach(() => {
9
+ delete process.env.CLINE_DATA_DIR;
10
+ });
11
+
12
+ it("recognizes PowerShell hook files", () => {
13
+ expect(toHookConfigFileName("PreToolUse.ps1")).toBe(
14
+ HookConfigFileName.PreToolUse,
15
+ );
16
+ expect(toHookConfigFileName("TaskError.ps1")).toBe(
17
+ HookConfigFileName.TaskError,
18
+ );
19
+ });
20
+ });
@@ -59,6 +59,7 @@ const SUPPORTED_HOOK_FILE_EXTENSIONS = new Set([
59
59
  ".mts",
60
60
  ".cts",
61
61
  ".py",
62
+ ".ps1",
62
63
  ]);
63
64
 
64
65
  export function toHookConfigFileName(
@@ -46,17 +46,17 @@ describe("user instruction config loader", () => {
46
46
  const workspacePath = "/repo/demo";
47
47
  expect(resolveSkillsConfigSearchPaths(workspacePath)).toEqual(
48
48
  expect.arrayContaining([
49
- "/repo/demo/.clinerules/skills",
50
- "/repo/demo/.cline/skills",
51
- "/repo/demo/.claude/skills",
52
- "/repo/demo/.agents/skills",
49
+ join(workspacePath, ".clinerules", "skills"),
50
+ join(workspacePath, ".cline", "skills"),
51
+ join(workspacePath, ".claude", "skills"),
52
+ join(workspacePath, ".agents", "skills"),
53
53
  ]),
54
54
  );
55
55
  expect(resolveRulesConfigSearchPaths(workspacePath)).toEqual(
56
- expect.arrayContaining(["/repo/demo/.clinerules"]),
56
+ expect.arrayContaining([join(workspacePath, ".clinerules")]),
57
57
  );
58
58
  expect(resolveWorkflowsConfigSearchPaths(workspacePath)).toEqual(
59
- expect.arrayContaining(["/repo/demo/.clinerules/workflows"]),
59
+ expect.arrayContaining([join(workspacePath, ".clinerules", "workflows")]),
60
60
  );
61
61
  });
62
62
 
package/src/index.node.ts CHANGED
@@ -114,10 +114,12 @@ export {
114
114
  type ClineAccountServiceOptions,
115
115
  type ClineAccountUsageTransaction,
116
116
  type ClineAccountUser,
117
+ type ClineOrganization,
117
118
  executeRpcClineAccountAction,
118
119
  isRpcClineAccountActionRequest,
119
120
  RpcClineAccountService,
120
121
  type RpcProviderActionExecutor,
122
+ type UserRemoteConfigResponse,
121
123
  } from "./account";
122
124
  export { startLocalOAuthServer } from "./auth/server";
123
125
  export type {
package/src/index.ts CHANGED
@@ -76,6 +76,7 @@ export {
76
76
  type ClineAccountServiceOptions,
77
77
  type ClineAccountUsageTransaction,
78
78
  type ClineAccountUser,
79
+ type ClineOrganization,
79
80
  executeRpcClineAccountAction,
80
81
  isRpcClineAccountActionRequest,
81
82
  RpcClineAccountService,
@@ -172,6 +172,40 @@ describe("createHookConfigFileHooks", () => {
172
172
  }
173
173
  });
174
174
 
175
+ it.skipIf(process.platform !== "win32")(
176
+ "executes PowerShell hook files on Windows",
177
+ async () => {
178
+ const { workspace } = await createWorkspaceWithHook(
179
+ "PreToolUse.ps1",
180
+ 'Write-Output \'HOOK_CONTROL\t{"cancel": false, "context": "powershell-ok"}\'\n',
181
+ );
182
+ try {
183
+ const hooks = createHookConfigFileHooks({
184
+ cwd: workspace,
185
+ workspacePath: workspace,
186
+ });
187
+ expect(hooks?.onToolCallStart).toBeTypeOf("function");
188
+ const control = await hooks?.onToolCallStart?.({
189
+ agentId: "agent_1",
190
+ conversationId: "conv_1",
191
+ parentAgentId: null,
192
+ iteration: 1,
193
+ call: {
194
+ id: "call_1",
195
+ name: "read_file",
196
+ input: { path: "README.md" },
197
+ },
198
+ });
199
+ expect(control).toMatchObject({
200
+ cancel: false,
201
+ context: "powershell-ok",
202
+ });
203
+ } finally {
204
+ await rm(workspace, { recursive: true, force: true });
205
+ }
206
+ },
207
+ );
208
+
175
209
  it("maps TaskError hook files to agent_error stop events", async () => {
176
210
  const outputPath = join(tmpdir(), `hooks-task-error-${Date.now()}.json`);
177
211
  const { workspace } = await createWorkspaceWithHook(
@@ -345,10 +345,33 @@ function parseShebangCommand(path: string): string[] | undefined {
345
345
  }
346
346
  }
347
347
 
348
+ function normalizeHookInterpreter(tokens: string[]): string[] | undefined {
349
+ if (tokens.length === 0) {
350
+ return undefined;
351
+ }
352
+ const [rawCommand, ...rest] = tokens;
353
+ const normalizedCommand = rawCommand.replace(/\\/g, "/").toLowerCase();
354
+ const commandName = normalizedCommand.split("/").at(-1) ?? normalizedCommand;
355
+
356
+ if (commandName === "env") {
357
+ return normalizeHookInterpreter(rest);
358
+ }
359
+
360
+ if (commandName === "bash" || commandName === "sh" || commandName === "zsh") {
361
+ return [commandName, ...rest];
362
+ }
363
+
364
+ if (commandName === "python3" || commandName === "python") {
365
+ return [process.platform === "win32" ? "python" : commandName, ...rest];
366
+ }
367
+
368
+ return tokens;
369
+ }
370
+
348
371
  function inferHookCommand(path: string): string[] {
349
372
  const shebang = parseShebangCommand(path);
350
373
  if (shebang && shebang.length > 0) {
351
- return [...shebang, path];
374
+ return [...(normalizeHookInterpreter(shebang) ?? shebang), path];
352
375
  }
353
376
  const lowered = path.toLowerCase();
354
377
  if (
@@ -356,7 +379,7 @@ function inferHookCommand(path: string): string[] {
356
379
  lowered.endsWith(".bash") ||
357
380
  lowered.endsWith(".zsh")
358
381
  ) {
359
- return ["/bin/bash", path];
382
+ return ["bash", path];
360
383
  }
361
384
  if (
362
385
  lowered.endsWith(".js") ||
@@ -373,10 +396,17 @@ function inferHookCommand(path: string): string[] {
373
396
  return ["bun", "run", path];
374
397
  }
375
398
  if (lowered.endsWith(".py")) {
376
- return ["python3", path];
399
+ return [process.platform === "win32" ? "python" : "python3", path];
400
+ }
401
+ if (lowered.endsWith(".ps1")) {
402
+ return [
403
+ process.platform === "win32" ? "powershell" : "pwsh",
404
+ "-File",
405
+ path,
406
+ ];
377
407
  }
378
408
  // Default to bash for legacy hook files with no extension/shebang.
379
- return ["/bin/bash", path];
409
+ return ["bash", path];
380
410
  }
381
411
 
382
412
  function createHookCommandMap(workspacePath: string): HookCommandMap {
@@ -8,8 +8,12 @@ import { CoreSessionService } from "./session-service";
8
8
 
9
9
  describe("UnifiedSessionPersistenceService", () => {
10
10
  const tempDirs: string[] = [];
11
+ const stores: Array<SqliteSessionStore> = [];
11
12
 
12
13
  afterEach(() => {
14
+ for (const store of stores.splice(0)) {
15
+ store.close();
16
+ }
13
17
  for (const dir of tempDirs.splice(0)) {
14
18
  rmSync(dir, { recursive: true, force: true });
15
19
  }
@@ -19,9 +23,9 @@ describe("UnifiedSessionPersistenceService", () => {
19
23
  const sessionsDir = mkdtempSync(join(tmpdir(), "stale-session-reconcile-"));
20
24
  tempDirs.push(sessionsDir);
21
25
 
22
- const service = new CoreSessionService(
23
- new SqliteSessionStore({ sessionsDir }),
24
- );
26
+ const store = new SqliteSessionStore({ sessionsDir });
27
+ stores.push(store);
28
+ const service = new CoreSessionService(store);
25
29
  const sessionId = "stale-root-session";
26
30
  const artifacts = await service.createRootSessionWithArtifacts({
27
31
  sessionId,
@@ -57,6 +57,11 @@ export class SqliteSessionStore implements SessionStore {
57
57
  return db;
58
58
  }
59
59
 
60
+ close(): void {
61
+ this.db?.close?.();
62
+ this.db = undefined;
63
+ }
64
+
60
65
  run(sql: string, params: unknown[] = []): { changes?: number } {
61
66
  return this.getRawDb()
62
67
  .prepare(sql)