@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
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
createUserInstructionConfigWatcher,
|
|
7
|
+
parseRuleConfigFromMarkdown,
|
|
8
|
+
parseSkillConfigFromMarkdown,
|
|
9
|
+
parseWorkflowConfigFromMarkdown,
|
|
10
|
+
resolveRulesConfigSearchPaths,
|
|
11
|
+
resolveSkillsConfigSearchPaths,
|
|
12
|
+
resolveWorkflowsConfigSearchPaths,
|
|
13
|
+
type UserInstructionConfigWatcherEvent,
|
|
14
|
+
} from "./user-instruction-config-loader";
|
|
15
|
+
|
|
16
|
+
const WAIT_TIMEOUT_MS = 4_000;
|
|
17
|
+
const WAIT_INTERVAL_MS = 25;
|
|
18
|
+
|
|
19
|
+
async function waitForEvent(
|
|
20
|
+
events: Array<UserInstructionConfigWatcherEvent>,
|
|
21
|
+
predicate: (event: UserInstructionConfigWatcherEvent) => boolean,
|
|
22
|
+
timeoutMs = WAIT_TIMEOUT_MS,
|
|
23
|
+
): Promise<UserInstructionConfigWatcherEvent> {
|
|
24
|
+
const startedAt = Date.now();
|
|
25
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
26
|
+
const match = events.find(predicate);
|
|
27
|
+
if (match) {
|
|
28
|
+
return match;
|
|
29
|
+
}
|
|
30
|
+
await new Promise((resolve) => setTimeout(resolve, WAIT_INTERVAL_MS));
|
|
31
|
+
}
|
|
32
|
+
throw new Error("Timed out waiting for watcher event.");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("user instruction config loader", () => {
|
|
36
|
+
const tempRoots: string[] = [];
|
|
37
|
+
|
|
38
|
+
afterEach(async () => {
|
|
39
|
+
await Promise.all(
|
|
40
|
+
tempRoots.map((dir) => rm(dir, { recursive: true, force: true })),
|
|
41
|
+
);
|
|
42
|
+
tempRoots.length = 0;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("resolves legacy-compatible default search paths", () => {
|
|
46
|
+
const workspacePath = "/repo/demo";
|
|
47
|
+
expect(resolveSkillsConfigSearchPaths(workspacePath)).toEqual(
|
|
48
|
+
expect.arrayContaining([
|
|
49
|
+
join(workspacePath, ".clinerules", "skills"),
|
|
50
|
+
join(workspacePath, ".cline", "skills"),
|
|
51
|
+
join(workspacePath, ".claude", "skills"),
|
|
52
|
+
join(workspacePath, ".agents", "skills"),
|
|
53
|
+
]),
|
|
54
|
+
);
|
|
55
|
+
expect(resolveRulesConfigSearchPaths(workspacePath)).toEqual(
|
|
56
|
+
expect.arrayContaining([join(workspacePath, ".clinerules")]),
|
|
57
|
+
);
|
|
58
|
+
expect(resolveWorkflowsConfigSearchPaths(workspacePath)).toEqual(
|
|
59
|
+
expect.arrayContaining([join(workspacePath, ".clinerules", "workflows")]),
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("parses markdown frontmatter for skill, rule, and workflow configs", () => {
|
|
64
|
+
const skill = parseSkillConfigFromMarkdown(
|
|
65
|
+
`---
|
|
66
|
+
name: debugging
|
|
67
|
+
description: Use structured debugging
|
|
68
|
+
disabled: true
|
|
69
|
+
---
|
|
70
|
+
Follow the debugging checklist.`,
|
|
71
|
+
"fallback",
|
|
72
|
+
);
|
|
73
|
+
expect(skill.name).toBe("debugging");
|
|
74
|
+
expect(skill.description).toBe("Use structured debugging");
|
|
75
|
+
expect(skill.disabled).toBe(true);
|
|
76
|
+
expect(skill.instructions).toBe("Follow the debugging checklist.");
|
|
77
|
+
|
|
78
|
+
const rule = parseRuleConfigFromMarkdown(
|
|
79
|
+
`---
|
|
80
|
+
name: rule-a
|
|
81
|
+
disabled: true
|
|
82
|
+
---
|
|
83
|
+
Always run tests before merge.`,
|
|
84
|
+
"rule-a",
|
|
85
|
+
);
|
|
86
|
+
expect(rule.name).toBe("rule-a");
|
|
87
|
+
expect(rule.disabled).toBe(true);
|
|
88
|
+
|
|
89
|
+
const workflow = parseWorkflowConfigFromMarkdown(
|
|
90
|
+
`---
|
|
91
|
+
name: release
|
|
92
|
+
disabled: true
|
|
93
|
+
---
|
|
94
|
+
Document rollout and rollback steps.`,
|
|
95
|
+
"release",
|
|
96
|
+
);
|
|
97
|
+
expect(workflow.name).toBe("release");
|
|
98
|
+
expect(workflow.disabled).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("emits typed events for skills, rules, and workflows in one watcher", async () => {
|
|
102
|
+
const tempRoot = await mkdtemp(
|
|
103
|
+
join(tmpdir(), "core-user-instructions-loader-"),
|
|
104
|
+
);
|
|
105
|
+
tempRoots.push(tempRoot);
|
|
106
|
+
const skillsDir = join(tempRoot, "skills");
|
|
107
|
+
const rulesDir = join(tempRoot, "rules");
|
|
108
|
+
const workflowsDir = join(tempRoot, "workflows");
|
|
109
|
+
await mkdir(join(skillsDir, "incident-response"), { recursive: true });
|
|
110
|
+
await mkdir(rulesDir, { recursive: true });
|
|
111
|
+
await mkdir(workflowsDir, { recursive: true });
|
|
112
|
+
|
|
113
|
+
await writeFile(
|
|
114
|
+
join(skillsDir, "incident-response", "SKILL.md"),
|
|
115
|
+
`---
|
|
116
|
+
name: incident-response
|
|
117
|
+
description: Handle incidents fast
|
|
118
|
+
---
|
|
119
|
+
Escalation runbook`,
|
|
120
|
+
);
|
|
121
|
+
await writeFile(
|
|
122
|
+
join(rulesDir, "default.md"),
|
|
123
|
+
"Keep changes minimal and tested.",
|
|
124
|
+
);
|
|
125
|
+
await writeFile(
|
|
126
|
+
join(workflowsDir, "release.md"),
|
|
127
|
+
"Ship with release checklist.",
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const watcher = createUserInstructionConfigWatcher({
|
|
131
|
+
skills: { directories: [skillsDir] },
|
|
132
|
+
rules: { directories: [rulesDir] },
|
|
133
|
+
workflows: { directories: [workflowsDir] },
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const events: Array<UserInstructionConfigWatcherEvent> = [];
|
|
137
|
+
const unsubscribe = watcher.subscribe((event) => events.push(event));
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
await watcher.start();
|
|
141
|
+
await waitForEvent(
|
|
142
|
+
events,
|
|
143
|
+
(event) => event.kind === "upsert" && event.record.type === "skill",
|
|
144
|
+
);
|
|
145
|
+
await waitForEvent(
|
|
146
|
+
events,
|
|
147
|
+
(event) => event.kind === "upsert" && event.record.type === "rule",
|
|
148
|
+
);
|
|
149
|
+
await waitForEvent(
|
|
150
|
+
events,
|
|
151
|
+
(event) => event.kind === "upsert" && event.record.type === "workflow",
|
|
152
|
+
);
|
|
153
|
+
} finally {
|
|
154
|
+
unsubscribe();
|
|
155
|
+
watcher.stop();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, extname, join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
RULES_CONFIG_DIRECTORY_NAME,
|
|
5
|
+
resolveDocumentsRulesDirectoryPath,
|
|
6
|
+
resolveDocumentsWorkflowsDirectoryPath,
|
|
7
|
+
resolveRulesConfigSearchPaths as resolveRulesConfigSearchPathsFromShared,
|
|
8
|
+
resolveSkillsConfigSearchPaths as resolveSkillsConfigSearchPathsFromShared,
|
|
9
|
+
resolveWorkflowsConfigSearchPaths as resolveWorkflowsConfigSearchPathsFromShared,
|
|
10
|
+
SKILLS_CONFIG_DIRECTORY_NAME,
|
|
11
|
+
WORKFLOWS_CONFIG_DIRECTORY_NAME,
|
|
12
|
+
} from "@clinebot/shared/storage";
|
|
13
|
+
import YAML from "yaml";
|
|
14
|
+
import {
|
|
15
|
+
type UnifiedConfigDefinition,
|
|
16
|
+
type UnifiedConfigFileCandidate,
|
|
17
|
+
UnifiedConfigFileWatcher,
|
|
18
|
+
type UnifiedConfigWatcherEvent,
|
|
19
|
+
} from "./unified-config-file-watcher";
|
|
20
|
+
|
|
21
|
+
const SKILL_FILE_NAME = "SKILL.md";
|
|
22
|
+
|
|
23
|
+
const MARKDOWN_EXTENSIONS = new Set([".md", ".markdown", ".txt"]);
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
RULES_CONFIG_DIRECTORY_NAME,
|
|
27
|
+
resolveDocumentsRulesDirectoryPath,
|
|
28
|
+
resolveDocumentsWorkflowsDirectoryPath,
|
|
29
|
+
SKILLS_CONFIG_DIRECTORY_NAME,
|
|
30
|
+
WORKFLOWS_CONFIG_DIRECTORY_NAME,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export interface ParseMarkdownFrontmatterResult {
|
|
34
|
+
data: Record<string, unknown>;
|
|
35
|
+
body: string;
|
|
36
|
+
hadFrontmatter: boolean;
|
|
37
|
+
parseError?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SkillConfig {
|
|
41
|
+
name: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
instructions: string;
|
|
45
|
+
frontmatter: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface RuleConfig {
|
|
49
|
+
name: string;
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
instructions: string;
|
|
52
|
+
frontmatter: Record<string, unknown>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface WorkflowConfig {
|
|
56
|
+
name: string;
|
|
57
|
+
disabled?: boolean;
|
|
58
|
+
instructions: string;
|
|
59
|
+
frontmatter: Record<string, unknown>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type UserInstructionConfigType = "skill" | "rule" | "workflow";
|
|
63
|
+
|
|
64
|
+
export type UserInstructionConfig = SkillConfig | RuleConfig | WorkflowConfig;
|
|
65
|
+
|
|
66
|
+
export type UserInstructionConfigWatcher = UnifiedConfigFileWatcher<
|
|
67
|
+
UserInstructionConfigType,
|
|
68
|
+
UserInstructionConfig
|
|
69
|
+
>;
|
|
70
|
+
|
|
71
|
+
export type UserInstructionConfigWatcherEvent = UnifiedConfigWatcherEvent<
|
|
72
|
+
UserInstructionConfigType,
|
|
73
|
+
UserInstructionConfig
|
|
74
|
+
>;
|
|
75
|
+
|
|
76
|
+
export interface CreateInstructionWatcherOptions {
|
|
77
|
+
debounceMs?: number;
|
|
78
|
+
emitParseErrors?: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface CreateSkillsConfigDefinitionOptions {
|
|
82
|
+
directories?: ReadonlyArray<string>;
|
|
83
|
+
workspacePath?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface CreateRulesConfigDefinitionOptions {
|
|
87
|
+
directories?: ReadonlyArray<string>;
|
|
88
|
+
workspacePath?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface CreateWorkflowsConfigDefinitionOptions {
|
|
92
|
+
directories?: ReadonlyArray<string>;
|
|
93
|
+
workspacePath?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalizeName(name: string): string {
|
|
97
|
+
return name.trim().toLowerCase();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isMarkdownFile(fileName: string): boolean {
|
|
101
|
+
return MARKDOWN_EXTENSIONS.has(extname(fileName).toLowerCase());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseMarkdownFrontmatter(
|
|
105
|
+
content: string,
|
|
106
|
+
): ParseMarkdownFrontmatterResult {
|
|
107
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
108
|
+
const match = content.match(frontmatterRegex);
|
|
109
|
+
if (!match) {
|
|
110
|
+
return { data: {}, body: content, hadFrontmatter: false };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const [, yamlContent, body] = match;
|
|
114
|
+
try {
|
|
115
|
+
const parsed = YAML.parse(yamlContent);
|
|
116
|
+
const data =
|
|
117
|
+
parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
118
|
+
? (parsed as Record<string, unknown>)
|
|
119
|
+
: {};
|
|
120
|
+
return { data, body, hadFrontmatter: true };
|
|
121
|
+
} catch (error) {
|
|
122
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
123
|
+
return {
|
|
124
|
+
data: {},
|
|
125
|
+
body: content,
|
|
126
|
+
hadFrontmatter: true,
|
|
127
|
+
parseError: message,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function parseStringField(
|
|
133
|
+
value: unknown,
|
|
134
|
+
fieldName: string,
|
|
135
|
+
isRequired: boolean,
|
|
136
|
+
): string | undefined {
|
|
137
|
+
if (value === undefined || value === null) {
|
|
138
|
+
if (isRequired) {
|
|
139
|
+
throw new Error(`Missing required frontmatter field '${fieldName}'.`);
|
|
140
|
+
}
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
if (typeof value !== "string") {
|
|
144
|
+
throw new Error(`Frontmatter field '${fieldName}' must be a string.`);
|
|
145
|
+
}
|
|
146
|
+
const normalized = value.trim();
|
|
147
|
+
if (!normalized && isRequired) {
|
|
148
|
+
throw new Error(`Frontmatter field '${fieldName}' cannot be empty.`);
|
|
149
|
+
}
|
|
150
|
+
return normalized || undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function parseBooleanField(
|
|
154
|
+
value: unknown,
|
|
155
|
+
fieldName: string,
|
|
156
|
+
): boolean | undefined {
|
|
157
|
+
if (value === undefined || value === null) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
if (typeof value !== "boolean") {
|
|
161
|
+
throw new Error(`Frontmatter field '${fieldName}' must be a boolean.`);
|
|
162
|
+
}
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function parseSkillConfigFromMarkdown(
|
|
167
|
+
content: string,
|
|
168
|
+
fallbackName: string,
|
|
169
|
+
): SkillConfig {
|
|
170
|
+
const { data, body, parseError } = parseMarkdownFrontmatter(content);
|
|
171
|
+
if (parseError) {
|
|
172
|
+
throw new Error(`Failed to parse YAML frontmatter: ${parseError}`);
|
|
173
|
+
}
|
|
174
|
+
const instructions = body.trim();
|
|
175
|
+
if (!instructions) {
|
|
176
|
+
throw new Error("Missing instructions body in skill file.");
|
|
177
|
+
}
|
|
178
|
+
const parsedName = parseStringField(data.name, "name", false);
|
|
179
|
+
const name = parsedName ?? fallbackName.trim();
|
|
180
|
+
if (!name) {
|
|
181
|
+
throw new Error("Missing skill name.");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
name,
|
|
186
|
+
description: parseStringField(data.description, "description", false),
|
|
187
|
+
disabled:
|
|
188
|
+
parseBooleanField(data.disabled, "disabled") ??
|
|
189
|
+
(parseBooleanField(data.enabled, "enabled") === false ? true : undefined),
|
|
190
|
+
instructions,
|
|
191
|
+
frontmatter: data,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function parseRuleConfigFromMarkdown(
|
|
196
|
+
content: string,
|
|
197
|
+
fallbackName: string,
|
|
198
|
+
): RuleConfig {
|
|
199
|
+
const { data, body, parseError } = parseMarkdownFrontmatter(content);
|
|
200
|
+
if (parseError) {
|
|
201
|
+
throw new Error(`Failed to parse YAML frontmatter: ${parseError}`);
|
|
202
|
+
}
|
|
203
|
+
const instructions = body.trim();
|
|
204
|
+
if (!instructions) {
|
|
205
|
+
throw new Error("Missing instructions body in rule file.");
|
|
206
|
+
}
|
|
207
|
+
const name =
|
|
208
|
+
parseStringField(data.name, "name", false) ?? fallbackName.trim();
|
|
209
|
+
if (!name) {
|
|
210
|
+
throw new Error("Missing rule name.");
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
name,
|
|
214
|
+
disabled:
|
|
215
|
+
parseBooleanField(data.disabled, "disabled") ??
|
|
216
|
+
(parseBooleanField(data.enabled, "enabled") === false ? true : undefined),
|
|
217
|
+
instructions,
|
|
218
|
+
frontmatter: data,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function parseWorkflowConfigFromMarkdown(
|
|
223
|
+
content: string,
|
|
224
|
+
fallbackName: string,
|
|
225
|
+
): WorkflowConfig {
|
|
226
|
+
const { data, body, parseError } = parseMarkdownFrontmatter(content);
|
|
227
|
+
if (parseError) {
|
|
228
|
+
throw new Error(`Failed to parse YAML frontmatter: ${parseError}`);
|
|
229
|
+
}
|
|
230
|
+
const instructions = body.trim();
|
|
231
|
+
if (!instructions) {
|
|
232
|
+
throw new Error("Missing instructions body in workflow file.");
|
|
233
|
+
}
|
|
234
|
+
const name =
|
|
235
|
+
parseStringField(data.name, "name", false) ?? fallbackName.trim();
|
|
236
|
+
if (!name) {
|
|
237
|
+
throw new Error("Missing workflow name.");
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
name,
|
|
241
|
+
disabled:
|
|
242
|
+
parseBooleanField(data.disabled, "disabled") ??
|
|
243
|
+
(parseBooleanField(data.enabled, "enabled") === false ? true : undefined),
|
|
244
|
+
instructions,
|
|
245
|
+
frontmatter: data,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function resolveSkillsConfigSearchPaths(
|
|
250
|
+
workspacePath?: string,
|
|
251
|
+
): string[] {
|
|
252
|
+
return resolveSkillsConfigSearchPathsFromShared(workspacePath);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function resolveRulesConfigSearchPaths(
|
|
256
|
+
workspacePath?: string,
|
|
257
|
+
): string[] {
|
|
258
|
+
return resolveRulesConfigSearchPathsFromShared(workspacePath);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function resolveWorkflowsConfigSearchPaths(
|
|
262
|
+
workspacePath?: string,
|
|
263
|
+
): string[] {
|
|
264
|
+
return resolveWorkflowsConfigSearchPathsFromShared(workspacePath);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function discoverSkillFiles(
|
|
268
|
+
directoryPath: string,
|
|
269
|
+
): Promise<ReadonlyArray<UnifiedConfigFileCandidate>> {
|
|
270
|
+
try {
|
|
271
|
+
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
272
|
+
const candidates: UnifiedConfigFileCandidate[] = [];
|
|
273
|
+
for (const entry of entries) {
|
|
274
|
+
if (entry.isFile() && entry.name === SKILL_FILE_NAME) {
|
|
275
|
+
candidates.push({
|
|
276
|
+
directoryPath,
|
|
277
|
+
fileName: entry.name,
|
|
278
|
+
filePath: join(directoryPath, entry.name),
|
|
279
|
+
});
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (entry.isDirectory()) {
|
|
283
|
+
candidates.push({
|
|
284
|
+
directoryPath: join(directoryPath, entry.name),
|
|
285
|
+
fileName: SKILL_FILE_NAME,
|
|
286
|
+
filePath: join(directoryPath, entry.name, SKILL_FILE_NAME),
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return candidates;
|
|
291
|
+
} catch (error) {
|
|
292
|
+
const nodeError = error as NodeJS.ErrnoException;
|
|
293
|
+
if (nodeError.code === "ENOENT") {
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function discoverRulesLikeFiles(
|
|
301
|
+
directoryPath: string,
|
|
302
|
+
): Promise<ReadonlyArray<UnifiedConfigFileCandidate>> {
|
|
303
|
+
try {
|
|
304
|
+
const entryStat = await stat(directoryPath);
|
|
305
|
+
if (entryStat.isFile()) {
|
|
306
|
+
return [
|
|
307
|
+
{
|
|
308
|
+
directoryPath: dirname(directoryPath),
|
|
309
|
+
fileName: basename(directoryPath),
|
|
310
|
+
filePath: directoryPath,
|
|
311
|
+
},
|
|
312
|
+
];
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
const nodeError = error as NodeJS.ErrnoException;
|
|
316
|
+
if (nodeError.code !== "ENOENT") {
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
323
|
+
return entries
|
|
324
|
+
.filter((entry) => entry.isFile() && isMarkdownFile(entry.name))
|
|
325
|
+
.map((entry) => ({
|
|
326
|
+
directoryPath,
|
|
327
|
+
fileName: entry.name,
|
|
328
|
+
filePath: join(directoryPath, entry.name),
|
|
329
|
+
}));
|
|
330
|
+
} catch (error) {
|
|
331
|
+
const nodeError = error as NodeJS.ErrnoException;
|
|
332
|
+
if (nodeError.code === "ENOENT") {
|
|
333
|
+
return [];
|
|
334
|
+
}
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function createSkillsConfigDefinition(
|
|
340
|
+
options?: CreateSkillsConfigDefinitionOptions,
|
|
341
|
+
): UnifiedConfigDefinition<"skill", SkillConfig> {
|
|
342
|
+
const directories =
|
|
343
|
+
options?.directories ??
|
|
344
|
+
resolveSkillsConfigSearchPaths(options?.workspacePath);
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
type: "skill",
|
|
348
|
+
directories,
|
|
349
|
+
discoverFiles: discoverSkillFiles,
|
|
350
|
+
includeFile: (fileName) => fileName === SKILL_FILE_NAME,
|
|
351
|
+
parseFile: (context) =>
|
|
352
|
+
parseSkillConfigFromMarkdown(
|
|
353
|
+
context.content,
|
|
354
|
+
basename(context.directoryPath),
|
|
355
|
+
),
|
|
356
|
+
resolveId: (skill) => normalizeName(skill.name),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function createRulesConfigDefinition(
|
|
361
|
+
options?: CreateRulesConfigDefinitionOptions,
|
|
362
|
+
): UnifiedConfigDefinition<"rule", RuleConfig> {
|
|
363
|
+
const directories =
|
|
364
|
+
options?.directories ??
|
|
365
|
+
resolveRulesConfigSearchPaths(options?.workspacePath);
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
type: "rule",
|
|
369
|
+
directories,
|
|
370
|
+
discoverFiles: discoverRulesLikeFiles,
|
|
371
|
+
includeFile: (fileName, filePath) =>
|
|
372
|
+
fileName === ".clinerules" ||
|
|
373
|
+
isMarkdownFile(fileName) ||
|
|
374
|
+
isMarkdownFile(filePath),
|
|
375
|
+
parseFile: (context) =>
|
|
376
|
+
parseRuleConfigFromMarkdown(
|
|
377
|
+
context.content,
|
|
378
|
+
basename(context.filePath, extname(context.filePath)),
|
|
379
|
+
),
|
|
380
|
+
resolveId: (rule) => normalizeName(rule.name),
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export function createWorkflowsConfigDefinition(
|
|
385
|
+
options?: CreateWorkflowsConfigDefinitionOptions,
|
|
386
|
+
): UnifiedConfigDefinition<"workflow", WorkflowConfig> {
|
|
387
|
+
const directories =
|
|
388
|
+
options?.directories ??
|
|
389
|
+
resolveWorkflowsConfigSearchPaths(options?.workspacePath);
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
type: "workflow",
|
|
393
|
+
directories,
|
|
394
|
+
discoverFiles: discoverRulesLikeFiles,
|
|
395
|
+
includeFile: (fileName) => isMarkdownFile(fileName),
|
|
396
|
+
parseFile: (context) =>
|
|
397
|
+
parseWorkflowConfigFromMarkdown(
|
|
398
|
+
context.content,
|
|
399
|
+
basename(context.filePath, extname(context.filePath)),
|
|
400
|
+
),
|
|
401
|
+
resolveId: (workflow) => normalizeName(workflow.name),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export interface CreateUserInstructionConfigWatcherOptions
|
|
406
|
+
extends CreateInstructionWatcherOptions {
|
|
407
|
+
skills?: CreateSkillsConfigDefinitionOptions;
|
|
408
|
+
rules?: CreateRulesConfigDefinitionOptions;
|
|
409
|
+
workflows?: CreateWorkflowsConfigDefinitionOptions;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function createUserInstructionConfigWatcher(
|
|
413
|
+
options?: CreateUserInstructionConfigWatcherOptions,
|
|
414
|
+
): UserInstructionConfigWatcher {
|
|
415
|
+
const definitions: ReadonlyArray<
|
|
416
|
+
UnifiedConfigDefinition<UserInstructionConfigType, UserInstructionConfig>
|
|
417
|
+
> = [
|
|
418
|
+
createSkillsConfigDefinition(options?.skills) as UnifiedConfigDefinition<
|
|
419
|
+
UserInstructionConfigType,
|
|
420
|
+
UserInstructionConfig
|
|
421
|
+
>,
|
|
422
|
+
createRulesConfigDefinition(options?.rules) as UnifiedConfigDefinition<
|
|
423
|
+
UserInstructionConfigType,
|
|
424
|
+
UserInstructionConfig
|
|
425
|
+
>,
|
|
426
|
+
createWorkflowsConfigDefinition(
|
|
427
|
+
options?.workflows,
|
|
428
|
+
) as UnifiedConfigDefinition<
|
|
429
|
+
UserInstructionConfigType,
|
|
430
|
+
UserInstructionConfig
|
|
431
|
+
>,
|
|
432
|
+
];
|
|
433
|
+
|
|
434
|
+
return new UnifiedConfigFileWatcher(definitions, {
|
|
435
|
+
debounceMs: options?.debounceMs,
|
|
436
|
+
emitParseErrors: options?.emitParseErrors,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createOAuthClientCallbacks } from "./client";
|
|
3
|
+
|
|
4
|
+
describe("auth/client createOAuthClientCallbacks", () => {
|
|
5
|
+
it("emits instructions and URL and forwards prompts", async () => {
|
|
6
|
+
const onOutput = vi.fn();
|
|
7
|
+
const onPrompt = vi.fn().mockResolvedValue("value");
|
|
8
|
+
const callbacks = createOAuthClientCallbacks({ onOutput, onPrompt });
|
|
9
|
+
|
|
10
|
+
callbacks.onAuth({
|
|
11
|
+
url: "https://example.com/auth",
|
|
12
|
+
instructions: "Open your browser",
|
|
13
|
+
});
|
|
14
|
+
const answer = await callbacks.onPrompt({ message: "Enter code" });
|
|
15
|
+
|
|
16
|
+
expect(answer).toBe("value");
|
|
17
|
+
expect(onPrompt).toHaveBeenCalledWith({ message: "Enter code" });
|
|
18
|
+
expect(onOutput).toHaveBeenNthCalledWith(1, "Open your browser");
|
|
19
|
+
expect(onOutput).toHaveBeenNthCalledWith(2, "https://example.com/auth");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("tries opening URL and reports opener errors", async () => {
|
|
23
|
+
const openUrl = vi.fn().mockRejectedValue(new Error("failed"));
|
|
24
|
+
const onOpenUrlError = vi.fn();
|
|
25
|
+
const callbacks = createOAuthClientCallbacks({
|
|
26
|
+
onPrompt: vi.fn().mockResolvedValue(""),
|
|
27
|
+
openUrl,
|
|
28
|
+
onOpenUrlError,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
callbacks.onAuth({ url: "https://example.com/auth" });
|
|
32
|
+
await Promise.resolve();
|
|
33
|
+
|
|
34
|
+
expect(openUrl).toHaveBeenCalledWith("https://example.com/auth");
|
|
35
|
+
expect(onOpenUrlError).toHaveBeenCalledTimes(1);
|
|
36
|
+
expect(onOpenUrlError.mock.calls[0]?.[0]).toMatchObject({
|
|
37
|
+
url: "https://example.com/auth",
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { OAuthLoginCallbacks, OAuthPrompt } from "./types";
|
|
2
|
+
|
|
3
|
+
export interface OAuthClientCallbacksOptions {
|
|
4
|
+
onPrompt: (prompt: OAuthPrompt) => Promise<string>;
|
|
5
|
+
onOutput?: (message: string) => void;
|
|
6
|
+
openUrl?: (url: string) => void | Promise<void>;
|
|
7
|
+
onOpenUrlError?: (context: { url: string; error: unknown }) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function createOAuthClientCallbacks(
|
|
11
|
+
options: OAuthClientCallbacksOptions,
|
|
12
|
+
): OAuthLoginCallbacks {
|
|
13
|
+
return {
|
|
14
|
+
onAuth: ({ url, instructions }) => {
|
|
15
|
+
options.onOutput?.(instructions ?? "Complete sign-in in your browser.");
|
|
16
|
+
if (options.openUrl) {
|
|
17
|
+
void Promise.resolve(options.openUrl(url)).catch((error) => {
|
|
18
|
+
options.onOpenUrlError?.({ url, error });
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
options.onOutput?.(url);
|
|
22
|
+
},
|
|
23
|
+
onPrompt: options.onPrompt,
|
|
24
|
+
};
|
|
25
|
+
}
|