@cueloop/client 0.1.0-alpha.18 → 0.1.0-alpha.19
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/package.json +5 -5
- package/src/integrations.test.ts +29 -0
- package/src/integrations.ts +28 -0
- package/src/session-controller.ts +8 -14
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cueloop/client",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.19",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.ts"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@cueloop/daemon": "0.1.0-alpha.
|
|
11
|
-
"@cueloop/extension-api": "0.1.0-alpha.
|
|
12
|
-
"@cueloop/integration-obsidian": "0.1.0-alpha.
|
|
13
|
-
"@cueloop/schema": "0.1.0-alpha.
|
|
10
|
+
"@cueloop/daemon": "0.1.0-alpha.19",
|
|
11
|
+
"@cueloop/extension-api": "0.1.0-alpha.19",
|
|
12
|
+
"@cueloop/integration-obsidian": "0.1.0-alpha.19",
|
|
13
|
+
"@cueloop/schema": "0.1.0-alpha.19",
|
|
14
14
|
"@opentui/core": "^0.5.1",
|
|
15
15
|
"@opentui/keymap": "0.5.1",
|
|
16
16
|
"@opentui/react": "^0.5.1",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { OBSIDIAN_DEFAULTS } from "@cueloop/integration-obsidian";
|
|
3
|
+
import { loadBundledExporters } from "./integrations";
|
|
4
|
+
import type { IntegrationsConfig } from "./config";
|
|
5
|
+
|
|
6
|
+
function integrations(overrides: Partial<typeof OBSIDIAN_DEFAULTS> = {}): IntegrationsConfig {
|
|
7
|
+
return { obsidian: { ...OBSIDIAN_DEFAULTS, ...overrides } };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe("loadBundledExporters", () => {
|
|
11
|
+
test("composes the obsidian integration into one named exporter", async () => {
|
|
12
|
+
const exporters = await loadBundledExporters(integrations());
|
|
13
|
+
expect(exporters.map((exporter) => exporter.name)).toEqual(["obsidian"]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("runsOn mirrors the configured export policy", async () => {
|
|
17
|
+
const onApprove = (await loadBundledExporters(integrations({ exportOn: "approve" })))[0]!;
|
|
18
|
+
expect(onApprove.runsOn("approve")).toBeTrue();
|
|
19
|
+
expect(onApprove.runsOn("request_changes")).toBeFalse();
|
|
20
|
+
|
|
21
|
+
const onResolve = (await loadBundledExporters(integrations({ exportOn: "resolve" })))[0]!;
|
|
22
|
+
expect(onResolve.runsOn("approve")).toBeTrue();
|
|
23
|
+
expect(onResolve.runsOn("request_changes")).toBeTrue();
|
|
24
|
+
|
|
25
|
+
const manual = (await loadBundledExporters(integrations({ exportOn: "manual" })))[0]!;
|
|
26
|
+
expect(manual.runsOn("approve")).toBeFalse();
|
|
27
|
+
expect(manual.runsOn("request_changes")).toBeFalse();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundled integration wiring: composes the configured integrations into
|
|
3
|
+
* generic exporters plus their run policy, so the session controller depends
|
|
4
|
+
* only on the extension seam, never on an integration's own config type. This
|
|
5
|
+
* and config.ts are the only client modules that name a concrete integration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Registry, type Exporter } from "@cueloop/extension-api";
|
|
9
|
+
import { createObsidianExtension, shouldExport } from "@cueloop/integration-obsidian";
|
|
10
|
+
import type { VerdictKind } from "@cueloop/schema";
|
|
11
|
+
import type { IntegrationsConfig } from "./config";
|
|
12
|
+
|
|
13
|
+
/** An exporter plus the policy deciding which verdicts trigger it. */
|
|
14
|
+
export interface BundledExporter {
|
|
15
|
+
name: string;
|
|
16
|
+
run: Exporter;
|
|
17
|
+
runsOn(verdict: VerdictKind): boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Load the bundled integrations from config as generic exporters. */
|
|
21
|
+
export async function loadBundledExporters(integrations: IntegrationsConfig): Promise<BundledExporter[]> {
|
|
22
|
+
const registry = new Registry();
|
|
23
|
+
const obsidian = integrations.obsidian;
|
|
24
|
+
const record = await registry.load("obsidian", createObsidianExtension(obsidian));
|
|
25
|
+
const run = record.exporters.get("obsidian");
|
|
26
|
+
if (!run) return [];
|
|
27
|
+
return [{ name: "obsidian", run, runsOn: (verdict) => shouldExport(obsidian.exportOn, verdict) }];
|
|
28
|
+
}
|
|
@@ -22,8 +22,7 @@ import {
|
|
|
22
22
|
type ReviewSession,
|
|
23
23
|
type VerdictKind,
|
|
24
24
|
} from "@cueloop/schema";
|
|
25
|
-
import {
|
|
26
|
-
import { createObsidianExtension, shouldExport, type ObsidianConfig } from "@cueloop/integration-obsidian";
|
|
25
|
+
import { loadBundledExporters, type BundledExporter } from "./integrations";
|
|
27
26
|
import { buildDisplay, nextWorkBlock, type DisplayBlock } from "./view-plan";
|
|
28
27
|
import { diffRowAnchor, diffRows, type DiffRow } from "./view-diff";
|
|
29
28
|
import { firstUnviewedIndex, walkFiles, type WalkFile } from "./walk";
|
|
@@ -141,8 +140,7 @@ class Controller implements ReviewController {
|
|
|
141
140
|
};
|
|
142
141
|
private listeners = new Set<() => void>();
|
|
143
142
|
private autoClose: AutoClose = "off";
|
|
144
|
-
private
|
|
145
|
-
private exporters = new Map<string, Exporter>();
|
|
143
|
+
private exporters: BundledExporter[] = [];
|
|
146
144
|
private readonly clock: Clock;
|
|
147
145
|
private countdown: TimerHandle | undefined;
|
|
148
146
|
/** Projections keyed by session identity so renders reuse one computation. */
|
|
@@ -207,11 +205,8 @@ class Controller implements ReviewController {
|
|
|
207
205
|
|
|
208
206
|
applyConfig(config: CueloopConfig): void {
|
|
209
207
|
this.autoClose = config.ui.autoClose;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const registry = new Registry();
|
|
213
|
-
void registry.load("obsidian", createObsidianExtension(config.integrations.obsidian)).then((record) => {
|
|
214
|
-
this.exporters = record.exporters;
|
|
208
|
+
void loadBundledExporters(config.integrations).then((exporters) => {
|
|
209
|
+
this.exporters = exporters;
|
|
215
210
|
});
|
|
216
211
|
}
|
|
217
212
|
|
|
@@ -448,11 +443,10 @@ class Controller implements ReviewController {
|
|
|
448
443
|
// The completion overlay heading already states the verdict, so the
|
|
449
444
|
// status line stays empty here - only export/error messages fill it.
|
|
450
445
|
this.update({ session: resolved, status: "" });
|
|
451
|
-
// notes-vault export: guarded by
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
void exporter(resolved).then((exportResult) => {
|
|
446
|
+
// notes-vault export: guarded by each exporter's policy (default manual = no-op)
|
|
447
|
+
for (const exporter of this.exporters) {
|
|
448
|
+
if (!exporter.runsOn(verdict)) continue;
|
|
449
|
+
void exporter.run(resolved).then((exportResult) => {
|
|
456
450
|
this.setStatus(exportResult.success && exportResult.path ? `exported to ${exportResult.path}` : `export failed: ${exportResult.error ?? "unknown"}`);
|
|
457
451
|
});
|
|
458
452
|
}
|