@mulmoclaude/core 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.
- package/assets/helps/billing-clients-worklog.md +215 -0
- package/assets/helps/billing-invoice.md +458 -0
- package/assets/helps/business.md +104 -0
- package/assets/helps/collection-skills.md +810 -0
- package/assets/helps/custom-view.md +433 -0
- package/assets/helps/feeds.md +114 -0
- package/assets/helps/gemini.md +57 -0
- package/assets/helps/github.md +23 -0
- package/assets/helps/guide.md +61 -0
- package/assets/helps/index.md +89 -0
- package/assets/helps/lessons-collection.md +400 -0
- package/assets/helps/mulmoscript.md +249 -0
- package/assets/helps/portfolio-tracker.md +211 -0
- package/assets/helps/presentation-deck.md +828 -0
- package/assets/helps/presenthtml.md +89 -0
- package/assets/helps/sandbox.md +97 -0
- package/assets/helps/spreadsheet.md +43 -0
- package/assets/helps/storyteller.md +101 -0
- package/assets/helps/telegram.md +136 -0
- package/assets/helps/todo-collection.md +140 -0
- package/assets/helps/vocabulary.md +109 -0
- package/assets/helps/wiki.md +168 -0
- package/assets/skills-preset/mc-cooking-coach/SKILL.md +217 -0
- package/assets/skills-preset/mc-library/SKILL.md +188 -0
- package/assets/skills-preset/mc-manage-automations/SKILL.md +119 -0
- package/assets/skills-preset/mc-manage-skills/SKILL.md +141 -0
- package/assets/skills-preset/mc-wiki-deep-lint/SKILL.md +108 -0
- package/assets/skills-preset/mc-wiki-health-check/SKILL.md +61 -0
- package/assets/skills-preset/mc-wiki-ingest/SKILL.md +182 -0
- package/assets/skills-preset/mc-wiki-promote/SKILL.md +175 -0
- package/assets/skills-preset/mc-zenn/SKILL.md +136 -0
- package/dist/chunk-CKQMccvm.cjs +28 -0
- package/dist/collection/core/actionVisible.d.ts +34 -0
- package/dist/collection/core/calendarGrid.d.ts +120 -0
- package/dist/collection/core/deriveAll.d.ts +38 -0
- package/dist/collection/core/derivedFormula.d.ts +18 -0
- package/dist/collection/core/draft.d.ts +18 -0
- package/dist/collection/core/enumColors.d.ts +33 -0
- package/dist/collection/core/errorMessage.d.ts +4 -0
- package/dist/collection/core/itemLabel.d.ts +12 -0
- package/dist/collection/core/presentCollection.d.ts +13 -0
- package/dist/collection/core/promptSafety.d.ts +1 -0
- package/dist/collection/core/schema.d.ts +355 -0
- package/dist/collection/core/shortHexId.d.ts +8 -0
- package/dist/collection/core/sortItems.d.ts +29 -0
- package/dist/collection/core/uiTypes.d.ts +106 -0
- package/dist/collection/index.cjs +793 -0
- package/dist/collection/index.cjs.map +1 -0
- package/dist/collection/index.d.ts +14 -0
- package/dist/collection/index.js +740 -0
- package/dist/collection/index.js.map +1 -0
- package/dist/collection/paths.cjs +44 -0
- package/dist/collection/paths.cjs.map +1 -0
- package/dist/collection/paths.js +41 -0
- package/dist/collection/paths.js.map +1 -0
- package/dist/collection/server/atomic.d.ts +1 -0
- package/dist/collection/server/delete.d.ts +38 -0
- package/dist/collection/server/derive.d.ts +8 -0
- package/dist/collection/server/discoveredCollection.d.ts +18 -0
- package/dist/collection/server/discovery.d.ts +227 -0
- package/dist/collection/server/host.d.ts +77 -0
- package/dist/collection/server/index.cjs +1721 -0
- package/dist/collection/server/index.cjs.map +1 -0
- package/dist/collection/server/index.d.ts +11 -0
- package/dist/collection/server/index.js +1671 -0
- package/dist/collection/server/index.js.map +1 -0
- package/dist/collection/server/io.d.ts +114 -0
- package/dist/collection/server/paths.d.ts +52 -0
- package/dist/collection/server/spawn.d.ts +55 -0
- package/dist/collection/server/templatePath.d.ts +25 -0
- package/dist/collection/server/util.d.ts +3 -0
- package/dist/collection/server/validate.d.ts +19 -0
- package/dist/collection/server/views.d.ts +20 -0
- package/dist/deriveAll-C15OpM3K.cjs +399 -0
- package/dist/deriveAll-C15OpM3K.cjs.map +1 -0
- package/dist/deriveAll-C6BYnpBL.js +364 -0
- package/dist/deriveAll-C6BYnpBL.js.map +1 -0
- package/dist/file-change/index.cjs +72 -0
- package/dist/file-change/index.cjs.map +1 -0
- package/dist/file-change/index.d.ts +43 -0
- package/dist/file-change/index.js +66 -0
- package/dist/file-change/index.js.map +1 -0
- package/dist/notifier/engine.d.ts +72 -0
- package/dist/notifier/index.cjs +484 -0
- package/dist/notifier/index.cjs.map +1 -0
- package/dist/notifier/index.d.ts +3 -0
- package/dist/notifier/index.js +464 -0
- package/dist/notifier/index.js.map +1 -0
- package/dist/notifier/store.d.ts +18 -0
- package/dist/notifier/types.d.ts +118 -0
- package/dist/notifier/validate.d.ts +17 -0
- package/dist/scheduler/adapter.d.ts +48 -0
- package/dist/scheduler/index.cjs +352 -0
- package/dist/scheduler/index.cjs.map +1 -0
- package/dist/scheduler/index.d.ts +2 -0
- package/dist/scheduler/index.js +343 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/task-manager.d.ts +51 -0
- package/dist/whisper/client.cjs +241 -0
- package/dist/whisper/client.cjs.map +1 -0
- package/dist/whisper/client.d.ts +35 -0
- package/dist/whisper/client.js +239 -0
- package/dist/whisper/client.js.map +1 -0
- package/dist/whisper/ffmpeg.d.ts +6 -0
- package/dist/whisper/index.cjs +433 -0
- package/dist/whisper/index.cjs.map +1 -0
- package/dist/whisper/index.d.ts +5 -0
- package/dist/whisper/index.js +425 -0
- package/dist/whisper/index.js.map +1 -0
- package/dist/whisper/internal.d.ts +11 -0
- package/dist/whisper/models.d.ts +49 -0
- package/dist/whisper/sidecar.d.ts +8 -0
- package/dist/whisper/whisper.d.ts +28 -0
- package/dist/workspace-setup/assets.d.ts +10 -0
- package/dist/workspace-setup/index.d.ts +3 -0
- package/dist/workspace-setup/index.js +556 -0
- package/dist/workspace-setup/index.js.map +1 -0
- package/dist/workspace-setup/slug.d.ts +6 -0
- package/dist/workspace-setup/slug.js +13 -0
- package/dist/workspace-setup/slug.js.map +1 -0
- package/dist/workspace-setup/sync.d.ts +94 -0
- package/package.json +95 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
//#region src/file-change/index.ts
|
|
4
|
+
var config = null;
|
|
5
|
+
/** Wire the publisher to a host. Call once at startup, before any write route. */
|
|
6
|
+
function configureFileChangePublisher(cfg) {
|
|
7
|
+
config = cfg;
|
|
8
|
+
}
|
|
9
|
+
/** Clear the binding — test-only. */
|
|
10
|
+
function resetFileChangePublisher() {
|
|
11
|
+
config = null;
|
|
12
|
+
}
|
|
13
|
+
/** The plugin-scoped live-refresh channel a View subscribes to. */
|
|
14
|
+
function pluginFileChannel(scope, posixPath) {
|
|
15
|
+
return `plugin:${scope}:file:${posixPath}`;
|
|
16
|
+
}
|
|
17
|
+
/** Publish a file-change for a workspace-relative path: the primary channel (if any)
|
|
18
|
+
* + every matching plugin scope, with the post-write mtime. No-op until configured. */
|
|
19
|
+
async function publishFileChange(relativePath) {
|
|
20
|
+
const cfg = config;
|
|
21
|
+
if (!cfg) return;
|
|
22
|
+
const root = path.resolve(cfg.workspaceRoot) + path.sep;
|
|
23
|
+
const absPath = path.join(root, relativePath);
|
|
24
|
+
if (!absPath.startsWith(root)) {
|
|
25
|
+
cfg.warn?.("ignoring file-change for path outside workspace", { path: relativePath });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
let mtimeMs;
|
|
29
|
+
try {
|
|
30
|
+
({mtimeMs} = await stat(absPath));
|
|
31
|
+
} catch (err) {
|
|
32
|
+
cfg.warn?.("stat failed; falling back to Date.now()", {
|
|
33
|
+
path: relativePath,
|
|
34
|
+
error: errMsg(err)
|
|
35
|
+
});
|
|
36
|
+
mtimeMs = Date.now();
|
|
37
|
+
}
|
|
38
|
+
const posixPath = cfg.toPosix(relativePath);
|
|
39
|
+
const payload = {
|
|
40
|
+
path: posixPath,
|
|
41
|
+
mtimeMs
|
|
42
|
+
};
|
|
43
|
+
if (cfg.primaryChannel) safePublish(cfg, cfg.primaryChannel(posixPath), payload, "primary publish failed");
|
|
44
|
+
for (const { scope, matches } of cfg.pluginScopes ?? []) {
|
|
45
|
+
if (!matches(posixPath)) continue;
|
|
46
|
+
safePublish(cfg, pluginFileChannel(scope, posixPath), payload, `${scope} plugin forward failed`);
|
|
47
|
+
}
|
|
48
|
+
cfg.onPublished?.(posixPath, payload);
|
|
49
|
+
}
|
|
50
|
+
function safePublish(cfg, channel, payload, failMsg) {
|
|
51
|
+
try {
|
|
52
|
+
cfg.publish(channel, payload);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
cfg.warn?.(`${failMsg}; subscribers will miss this event`, {
|
|
55
|
+
channel,
|
|
56
|
+
error: errMsg(err)
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function errMsg(err) {
|
|
61
|
+
return err instanceof Error ? err.message : String(err);
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { configureFileChangePublisher, pluginFileChannel, publishFileChange, resetFileChangePublisher };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/file-change/index.ts"],"sourcesContent":["// @mulmoclaude/core/file-change — shared \"this workspace file changed\"\n// broadcaster. A host write route calls `publishFileChange(relPath)` after a\n// successful write; subscribed UI tabs refetch. Extracted so MulmoClaude and\n// MulmoTerminal forward to the SAME plugin-scoped channels (the live-refresh\n// contract the markdown/html Views subscribe to) without duplicating the logic.\n//\n// Everything host-specific is INJECTED via `configureFileChangePublisher` (the\n// pubsub, the workspace root, the path→posix normaliser, the host's own primary\n// channel, the plugin-scope matchers, and any side-effect), so this package owns\n// only the orchestration + the `plugin:<scope>:file:<path>` channel format and\n// touches neither host's pubsub-channel config nor its frontend subscribers.\nimport { stat } from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/** Payload published on every file-change channel. `mtimeMs` is the post-write\n * modified time — monotonic, suitable for cache-busting (`?v=`) and out-of-order\n * drops. */\nexport interface FileChannelPayload {\n path: string;\n mtimeMs: number;\n}\n\n/** A plugin View that wants live-refresh: when `matches(posixPath)` is true, the\n * change is forwarded to `plugin:<scope>:file:<path>` (the channel the View's\n * runtime subscribes to). */\nexport interface FileChangeScope {\n scope: string;\n matches: (posixPath: string) => boolean;\n}\n\nexport interface FileChangePublisherConfig {\n /** Host pubsub publish. */\n publish: (channel: string, payload: FileChannelPayload) => void;\n /** Workspace root — joined with the relative path to stat the post-write mtime. */\n workspaceRoot: string;\n /** Normalise a workspace-relative path to POSIX (drives both `payload.path` and\n * every channel suffix, so they can't drift on mixed separators). */\n toPosix: (relativePath: string) => string;\n /** The host's primary file channel (e.g. a Files explorer's `file:<path>`).\n * Omit when the host has no general file subscriber (e.g. MulmoTerminal). */\n primaryChannel?: (posixPath: string) => string;\n /** Plugin Views to forward to. */\n pluginScopes?: FileChangeScope[];\n /** Optional side-effect after publishing (e.g. a topic-index regen). Receives the\n * posix path + payload; may itself call `publishFileChange` for derived files. */\n onPublished?: (posixPath: string, payload: FileChannelPayload) => void;\n /** Optional warn logger; a publish/stat failure logs but never throws (callers\n * fire-and-forget, so a throw would be an unhandled rejection). */\n warn?: (message: string, data?: Record<string, unknown>) => void;\n}\n\nlet config: FileChangePublisherConfig | null = null;\n\n/** Wire the publisher to a host. Call once at startup, before any write route. */\nexport function configureFileChangePublisher(cfg: FileChangePublisherConfig): void {\n config = cfg;\n}\n\n/** Clear the binding — test-only. */\nexport function resetFileChangePublisher(): void {\n config = null;\n}\n\n/** The plugin-scoped live-refresh channel a View subscribes to. */\nexport function pluginFileChannel(scope: string, posixPath: string): string {\n return `plugin:${scope}:file:${posixPath}`;\n}\n\n/** Publish a file-change for a workspace-relative path: the primary channel (if any)\n * + every matching plugin scope, with the post-write mtime. No-op until configured. */\nexport async function publishFileChange(relativePath: string): Promise<void> {\n const cfg = config;\n if (!cfg) return;\n // `relativePath` comes from the host's write routes. Contain it before doing\n // anything: a path that escapes the workspace is dropped entirely — we\n // neither stat an arbitrary file nor broadcast an out-of-workspace path to\n // subscribers or host side-effects. Defence-in-depth, since this is the\n // shared package both hosts use. `root` carries a trailing separator and the\n // guard is a single `startsWith` early-return — the canonical containment\n // shape (so the check is unambiguous for both readers and static analysis).\n const root = path.resolve(cfg.workspaceRoot) + path.sep;\n const absPath = path.join(root, relativePath);\n if (!absPath.startsWith(root)) {\n cfg.warn?.(\"ignoring file-change for path outside workspace\", { path: relativePath });\n return;\n }\n let mtimeMs: number;\n try {\n ({ mtimeMs } = await stat(absPath));\n } catch (err) {\n cfg.warn?.(\"stat failed; falling back to Date.now()\", { path: relativePath, error: errMsg(err) });\n mtimeMs = Date.now();\n }\n const posixPath = cfg.toPosix(relativePath);\n const payload: FileChannelPayload = { path: posixPath, mtimeMs };\n\n if (cfg.primaryChannel) {\n safePublish(cfg, cfg.primaryChannel(posixPath), payload, \"primary publish failed\");\n }\n for (const { scope, matches } of cfg.pluginScopes ?? []) {\n if (!matches(posixPath)) continue;\n safePublish(cfg, pluginFileChannel(scope, posixPath), payload, `${scope} plugin forward failed`);\n }\n cfg.onPublished?.(posixPath, payload);\n}\n\nfunction safePublish(cfg: FileChangePublisherConfig, channel: string, payload: FileChannelPayload, failMsg: string): void {\n try {\n cfg.publish(channel, payload);\n } catch (err) {\n cfg.warn?.(`${failMsg}; subscribers will miss this event`, { channel, error: errMsg(err) });\n }\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n"],"mappings":";;;AAmDA,IAAI,SAA2C;;AAG/C,SAAgB,6BAA6B,KAAsC;CACjF,SAAS;AACX;;AAGA,SAAgB,2BAAiC;CAC/C,SAAS;AACX;;AAGA,SAAgB,kBAAkB,OAAe,WAA2B;CAC1E,OAAO,UAAU,MAAM,QAAQ;AACjC;;;AAIA,eAAsB,kBAAkB,cAAqC;CAC3E,MAAM,MAAM;CACZ,IAAI,CAAC,KAAK;CAQV,MAAM,OAAO,KAAK,QAAQ,IAAI,aAAa,IAAI,KAAK;CACpD,MAAM,UAAU,KAAK,KAAK,MAAM,YAAY;CAC5C,IAAI,CAAC,QAAQ,WAAW,IAAI,GAAG;EAC7B,IAAI,OAAO,mDAAmD,EAAE,MAAM,aAAa,CAAC;EACpF;CACF;CACA,IAAI;CACJ,IAAI;EACF,CAAC,CAAE,WAAY,MAAM,KAAK,OAAO;CACnC,SAAS,KAAK;EACZ,IAAI,OAAO,2CAA2C;GAAE,MAAM;GAAc,OAAO,OAAO,GAAG;EAAE,CAAC;EAChG,UAAU,KAAK,IAAI;CACrB;CACA,MAAM,YAAY,IAAI,QAAQ,YAAY;CAC1C,MAAM,UAA8B;EAAE,MAAM;EAAW;CAAQ;CAE/D,IAAI,IAAI,gBACN,YAAY,KAAK,IAAI,eAAe,SAAS,GAAG,SAAS,wBAAwB;CAEnF,KAAK,MAAM,EAAE,OAAO,aAAa,IAAI,gBAAgB,CAAC,GAAG;EACvD,IAAI,CAAC,QAAQ,SAAS,GAAG;EACzB,YAAY,KAAK,kBAAkB,OAAO,SAAS,GAAG,SAAS,GAAG,MAAM,uBAAuB;CACjG;CACA,IAAI,cAAc,WAAW,OAAO;AACtC;AAEA,SAAS,YAAY,KAAgC,SAAiB,SAA6B,SAAuB;CACxH,IAAI;EACF,IAAI,QAAQ,SAAS,OAAO;CAC9B,SAAS,KAAK;EACZ,IAAI,OAAO,GAAG,QAAQ,qCAAqC;GAAE;GAAS,OAAO,OAAO,GAAG;EAAE,CAAC;CAC5F;AACF;AAEA,SAAS,OAAO,KAAsB;CACpC,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { WriteJson } from './store.js';
|
|
2
|
+
import { NotifierEntry, NotifierEvent, NotifierHistoryEntry, NotifierSeverity, PublishInput } from './types.js';
|
|
3
|
+
export { NOTIFIER_LIMITS, validatePublishInput } from './validate.js';
|
|
4
|
+
/** Minimal logger the engine needs. The host passes its structured
|
|
5
|
+
* logger; absent one, failures are swallowed (the engine never throws
|
|
6
|
+
* on a fan-out/persist-best-effort path). */
|
|
7
|
+
export interface NotifierLogger {
|
|
8
|
+
warn: (message: string, data?: Record<string, unknown>) => void;
|
|
9
|
+
error: (message: string, data?: Record<string, unknown>) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface NotifierConfig {
|
|
12
|
+
/** Atomic JSON writer (the host's `writeJsonAtomic`). */
|
|
13
|
+
writeJson: WriteJson;
|
|
14
|
+
/** Fan-out sink — the host binds this to `pubsub.publish(channel, event)`. */
|
|
15
|
+
publishEvent: (event: NotifierEvent) => void;
|
|
16
|
+
/** Optional logger. */
|
|
17
|
+
log?: NotifierLogger;
|
|
18
|
+
}
|
|
19
|
+
/** Wire the engine's I/O deps. Call once at startup, before the first
|
|
20
|
+
* mutation. Does NOT set file paths — those are set independently via
|
|
21
|
+
* `setNotifierFilePaths` so a host can bind production paths at module
|
|
22
|
+
* load and a test can override them without re-supplying the deps. */
|
|
23
|
+
export declare function configureNotifier(injected: NotifierConfig): void;
|
|
24
|
+
type NotifierEventListener = (event: NotifierEvent) => void;
|
|
25
|
+
/** Register an in-process listener for engine events. Returns an
|
|
26
|
+
* unsubscribe function the caller can use during teardown. */
|
|
27
|
+
export declare function onEvent(listener: NotifierEventListener): () => void;
|
|
28
|
+
/** Point the engine at its active/history files. Resets the write
|
|
29
|
+
* queue, so callers must not have in-flight mutations. The host calls
|
|
30
|
+
* this once with the workspace paths; tests call it per-case with temp
|
|
31
|
+
* files. */
|
|
32
|
+
export declare function setNotifierFilePaths(paths: {
|
|
33
|
+
active: string;
|
|
34
|
+
history: string;
|
|
35
|
+
}): void;
|
|
36
|
+
/** Test-only: clear config + queue so each suite starts clean. */
|
|
37
|
+
export declare function resetNotifier(): void;
|
|
38
|
+
export declare function publish<TPluginData = unknown>(input: PublishInput<TPluginData>): Promise<{
|
|
39
|
+
id: string;
|
|
40
|
+
}>;
|
|
41
|
+
export declare function clear(entryId: string): Promise<void>;
|
|
42
|
+
export declare function cancel(entryId: string): Promise<void>;
|
|
43
|
+
/** In-place update for an active entry. Only the fields present on
|
|
44
|
+
* `patch` are rewritten; `id`, `pluginPkg`, `lifecycle`, and
|
|
45
|
+
* `createdAt` stay fixed. Emits a single `"updated"` event with the
|
|
46
|
+
* post-mutation entry — no history record is written because the
|
|
47
|
+
* entry is still active, just with refreshed content.
|
|
48
|
+
*
|
|
49
|
+
* No-ops (no throw) when the id is unknown, the entry belongs to a
|
|
50
|
+
* different plugin, or the merged shape would violate
|
|
51
|
+
* `validatePublishInput`. The silent skip matches `clearForPlugin`'s
|
|
52
|
+
* isolation semantics; validation failures are logged for diagnosis. */
|
|
53
|
+
export declare function updateForPlugin<TPluginData = unknown>(pluginPkg: string, entryId: string, patch: {
|
|
54
|
+
severity?: NotifierSeverity;
|
|
55
|
+
title?: string;
|
|
56
|
+
body?: string;
|
|
57
|
+
navigateTarget?: string;
|
|
58
|
+
pluginData?: TPluginData;
|
|
59
|
+
}): Promise<void>;
|
|
60
|
+
/** Plugin-scoped point lookup. Returns the entry by id, but only if it
|
|
61
|
+
* belongs to the caller's plugin; otherwise undefined. Cross-plugin
|
|
62
|
+
* reads return undefined for isolation — same property as
|
|
63
|
+
* `clearForPlugin` / `updateForPlugin`. */
|
|
64
|
+
export declare function getForPlugin(pluginPkg: string, entryId: string): Promise<NotifierEntry | undefined>;
|
|
65
|
+
/** Plugin-scoped clear. Same as `clear` but no-ops if the entry's
|
|
66
|
+
* `pluginPkg` doesn't match the caller's, so a plugin can't dismiss
|
|
67
|
+
* another plugin's notification by guessing or scraping its id. */
|
|
68
|
+
export declare function clearForPlugin(pluginPkg: string, entryId: string): Promise<void>;
|
|
69
|
+
export declare function get(entryId: string): Promise<NotifierEntry | undefined>;
|
|
70
|
+
export declare function listFor(pluginPkg: string): Promise<NotifierEntry[]>;
|
|
71
|
+
export declare function listAll(): Promise<NotifierEntry[]>;
|
|
72
|
+
export declare function listHistory(): Promise<NotifierHistoryEntry[]>;
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("../chunk-CKQMccvm.cjs");
|
|
3
|
+
let node_fs = require("node:fs");
|
|
4
|
+
let node_crypto = require("node:crypto");
|
|
5
|
+
//#region src/notifier/types.ts
|
|
6
|
+
/** Two notification shapes, distinguished by who fires the close call:
|
|
7
|
+
*
|
|
8
|
+
* `fyi` — informational. The host (bell panel) clears it when the
|
|
9
|
+
* user dismisses the row. No deep-link target.
|
|
10
|
+
* `action` — pending obligation. The plugin clears it when the
|
|
11
|
+
* underlying domain state changes (the user paid the tax,
|
|
12
|
+
* viewed the digest, etc.). The bell row navigates to
|
|
13
|
+
* `navigateTarget` on click.
|
|
14
|
+
*
|
|
15
|
+
* The engine reads `lifecycle` only to enforce two publish-time rules
|
|
16
|
+
* (everything downstream — pubsub fan-out, persistence, history — is
|
|
17
|
+
* lifecycle-blind):
|
|
18
|
+
*
|
|
19
|
+
* 1. `action` requires a non-empty `navigateTarget`. Without one,
|
|
20
|
+
* clicking the row does nothing and the entry is a degraded fyi.
|
|
21
|
+
* 2. `action` cannot use `info` severity. A low-priority obligation
|
|
22
|
+
* is incoherent — fyi if it's a ping, `nudge`/`urgent` if it's a
|
|
23
|
+
* real obligation worth a landing page.
|
|
24
|
+
*
|
|
25
|
+
* Both rules are mirrored in the HTTP layer so plugin-runtime callers
|
|
26
|
+
* and HTTP callers hit the same wall. */
|
|
27
|
+
var NOTIFIER_LIFECYCLES = ["fyi", "action"];
|
|
28
|
+
/** Severity drives badge color (gray / amber / red, worst-wins) and
|
|
29
|
+
* in a future iteration channel routing. Mostly stored verbatim by
|
|
30
|
+
* the engine; the one engine-visible interaction is the rule that
|
|
31
|
+
* `action` lifecycle cannot pair with `info` severity (see
|
|
32
|
+
* `NotifierLifecycle` above). */
|
|
33
|
+
var NOTIFIER_SEVERITIES = [
|
|
34
|
+
"info",
|
|
35
|
+
"nudge",
|
|
36
|
+
"urgent"
|
|
37
|
+
];
|
|
38
|
+
/** History size cap. The bell popup's History section renders this
|
|
39
|
+
* many entries; older ones fall off when new terminations land. */
|
|
40
|
+
var HISTORY_CAP = 50;
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/notifier/store.ts
|
|
43
|
+
function isNotFoundError(err) {
|
|
44
|
+
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
45
|
+
}
|
|
46
|
+
/** Read the active-entries file. Returns an empty store when the file
|
|
47
|
+
* doesn't exist yet (first ever call on a fresh workspace). Any other
|
|
48
|
+
* read or parse failure throws — the caller has to decide whether to
|
|
49
|
+
* surface or recover, since silently treating "malformed file" as
|
|
50
|
+
* "no entries" would lose data. */
|
|
51
|
+
async function loadActive(filePath) {
|
|
52
|
+
let text;
|
|
53
|
+
try {
|
|
54
|
+
text = await node_fs.promises.readFile(filePath, "utf-8");
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (isNotFoundError(err)) return { entries: {} };
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
const parsed = JSON.parse(text);
|
|
60
|
+
if (typeof parsed !== "object" || parsed === null || !("entries" in parsed)) throw new Error(`notifier: malformed active.json at ${filePath}`);
|
|
61
|
+
const { entries } = parsed;
|
|
62
|
+
if (typeof entries !== "object" || entries === null || Array.isArray(entries)) throw new Error(`notifier: malformed active.json at ${filePath}`);
|
|
63
|
+
return parsed;
|
|
64
|
+
}
|
|
65
|
+
/** Write the active-entries file via the injected atomic writer so a
|
|
66
|
+
* half-written file is never visible to readers. The caller serialises
|
|
67
|
+
* writes (engine.ts queues mutations) — this function makes no
|
|
68
|
+
* concurrency guarantees of its own. */
|
|
69
|
+
async function saveActive(writeJson, filePath, state) {
|
|
70
|
+
await writeJson(filePath, state);
|
|
71
|
+
}
|
|
72
|
+
/** Read the history file. Empty array on first run. Same parse-error
|
|
73
|
+
* policy as `loadActive`. */
|
|
74
|
+
async function loadHistory(filePath) {
|
|
75
|
+
let text;
|
|
76
|
+
try {
|
|
77
|
+
text = await node_fs.promises.readFile(filePath, "utf-8");
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (isNotFoundError(err)) return { entries: [] };
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
const parsed = JSON.parse(text);
|
|
83
|
+
if (typeof parsed !== "object" || parsed === null || !("entries" in parsed) || !Array.isArray(parsed.entries)) throw new Error(`notifier: malformed history.json at ${filePath}`);
|
|
84
|
+
return parsed;
|
|
85
|
+
}
|
|
86
|
+
async function saveHistory(writeJson, filePath, state) {
|
|
87
|
+
await writeJson(filePath, state);
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/notifier/validate.ts
|
|
91
|
+
/** Hard caps on publish-input fields. The engine reads each entry on
|
|
92
|
+
* every list/get call (no in-memory cache), so unbounded fields hurt
|
|
93
|
+
* every reader. Caps chosen to be generous for legitimate UX copy
|
|
94
|
+
* while bounding active.json growth: a notification fundamentally is
|
|
95
|
+
* a short blurb, not a document. */
|
|
96
|
+
var NOTIFIER_LIMITS = {
|
|
97
|
+
titleMax: 200,
|
|
98
|
+
bodyMax: 4e3,
|
|
99
|
+
navigateTargetMax: 1e3,
|
|
100
|
+
pluginDataMaxBytes: 16 * 1024
|
|
101
|
+
};
|
|
102
|
+
function validateTitle(title) {
|
|
103
|
+
if (typeof title !== "string" || title.length === 0) return "title must be a non-empty string";
|
|
104
|
+
if (title.length > NOTIFIER_LIMITS.titleMax) return `title exceeds max length of ${NOTIFIER_LIMITS.titleMax} chars`;
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
function validateBody(body) {
|
|
108
|
+
if (body === void 0) return null;
|
|
109
|
+
if (body.length > NOTIFIER_LIMITS.bodyMax) return `body exceeds max length of ${NOTIFIER_LIMITS.bodyMax} chars`;
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
function validateNavigateTarget(target) {
|
|
113
|
+
if (target === void 0) return null;
|
|
114
|
+
if (target.length === 0) return "navigateTarget must be a non-empty relative path when set";
|
|
115
|
+
if (target.length > NOTIFIER_LIMITS.navigateTargetMax) return `navigateTarget exceeds max length of ${NOTIFIER_LIMITS.navigateTargetMax} chars`;
|
|
116
|
+
if (!target.startsWith("/") || target.startsWith("//")) return "navigateTarget must be a relative path beginning with a single '/' (no scheme, no '//')";
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function validatePluginData(pluginData) {
|
|
120
|
+
if (pluginData === void 0) return null;
|
|
121
|
+
let serialized;
|
|
122
|
+
try {
|
|
123
|
+
serialized = JSON.stringify(pluginData);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
return `pluginData is not JSON-serialisable: ${String(err)}`;
|
|
126
|
+
}
|
|
127
|
+
if (typeof serialized !== "string") return "pluginData is not JSON-serialisable";
|
|
128
|
+
if (serialized.length > NOTIFIER_LIMITS.pluginDataMaxBytes) return `pluginData JSON exceeds ${NOTIFIER_LIMITS.pluginDataMaxBytes} bytes`;
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
function validateActionCoherence(input) {
|
|
132
|
+
if (input.lifecycle !== "action") return null;
|
|
133
|
+
if (input.severity === "info") return "action lifecycle is incompatible with info severity (use fyi for low-priority pings)";
|
|
134
|
+
if (typeof input.navigateTarget !== "string" || input.navigateTarget.length === 0) return "action lifecycle requires a non-empty navigateTarget";
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
/** Validate a `PublishInput`. Returns `null` if OK, or a
|
|
138
|
+
* human-readable error string. Order matters — shape/size errors are
|
|
139
|
+
* reported before lifecycle/severity coherence errors so the message
|
|
140
|
+
* the caller sees points at the most fundamental problem first. */
|
|
141
|
+
function validatePublishInput(input) {
|
|
142
|
+
return validateTitle(input.title) ?? validateBody(input.body) ?? validateNavigateTarget(input.navigateTarget) ?? validatePluginData(input.pluginData) ?? validateActionCoherence(input);
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/notifier/engine.ts
|
|
146
|
+
var NOOP_LOG = {
|
|
147
|
+
warn: () => {},
|
|
148
|
+
error: () => {}
|
|
149
|
+
};
|
|
150
|
+
var config = null;
|
|
151
|
+
var activeFilePath = "";
|
|
152
|
+
var historyFilePath = "";
|
|
153
|
+
function logger() {
|
|
154
|
+
return config?.log ?? NOOP_LOG;
|
|
155
|
+
}
|
|
156
|
+
/** Wire the engine's I/O deps. Call once at startup, before the first
|
|
157
|
+
* mutation. Does NOT set file paths — those are set independently via
|
|
158
|
+
* `setNotifierFilePaths` so a host can bind production paths at module
|
|
159
|
+
* load and a test can override them without re-supplying the deps. */
|
|
160
|
+
function configureNotifier(injected) {
|
|
161
|
+
config = injected;
|
|
162
|
+
}
|
|
163
|
+
var listeners = [];
|
|
164
|
+
/** Register an in-process listener for engine events. Returns an
|
|
165
|
+
* unsubscribe function the caller can use during teardown. */
|
|
166
|
+
function onEvent(listener) {
|
|
167
|
+
listeners.push(listener);
|
|
168
|
+
return () => {
|
|
169
|
+
const idx = listeners.indexOf(listener);
|
|
170
|
+
if (idx >= 0) listeners.splice(idx, 1);
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function emit(event) {
|
|
174
|
+
for (const listener of listeners) try {
|
|
175
|
+
listener(event);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
logger().error("in-process listener failed", {
|
|
178
|
+
type: event.type,
|
|
179
|
+
error: String(err)
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (!config) {
|
|
183
|
+
logger().warn("emit before init", { type: event.type });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
config.publishEvent(event);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
logger().error("emit failed", {
|
|
190
|
+
type: event.type,
|
|
191
|
+
error: String(err)
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
var writing = false;
|
|
196
|
+
var waiters = [];
|
|
197
|
+
/** Point the engine at its active/history files. Resets the write
|
|
198
|
+
* queue, so callers must not have in-flight mutations. The host calls
|
|
199
|
+
* this once with the workspace paths; tests call it per-case with temp
|
|
200
|
+
* files. */
|
|
201
|
+
function setNotifierFilePaths(paths) {
|
|
202
|
+
activeFilePath = paths.active;
|
|
203
|
+
historyFilePath = paths.history;
|
|
204
|
+
writing = false;
|
|
205
|
+
waiters = [];
|
|
206
|
+
}
|
|
207
|
+
/** Test-only: clear config + queue so each suite starts clean. */
|
|
208
|
+
function resetNotifier() {
|
|
209
|
+
config = null;
|
|
210
|
+
activeFilePath = "";
|
|
211
|
+
historyFilePath = "";
|
|
212
|
+
writing = false;
|
|
213
|
+
waiters = [];
|
|
214
|
+
listeners.length = 0;
|
|
215
|
+
}
|
|
216
|
+
function requireWriteJson() {
|
|
217
|
+
if (!config) throw new Error("notifier: configureNotifier() not called");
|
|
218
|
+
return config.writeJson;
|
|
219
|
+
}
|
|
220
|
+
function applyBatchMutations(batch, state) {
|
|
221
|
+
return batch.map((waiter) => {
|
|
222
|
+
try {
|
|
223
|
+
return {
|
|
224
|
+
ok: true,
|
|
225
|
+
outcome: waiter.mutate(state)
|
|
226
|
+
};
|
|
227
|
+
} catch (err) {
|
|
228
|
+
return {
|
|
229
|
+
ok: false,
|
|
230
|
+
error: err
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
function collectEvents(results) {
|
|
236
|
+
const events = [];
|
|
237
|
+
for (const result of results) if (result.ok && result.outcome !== null) events.push(result.outcome.event);
|
|
238
|
+
return events;
|
|
239
|
+
}
|
|
240
|
+
function collectHistoryEntries(results) {
|
|
241
|
+
const entries = [];
|
|
242
|
+
for (const result of results) if (result.ok && result.outcome !== null && result.outcome.historyEntry) entries.push(result.outcome.historyEntry);
|
|
243
|
+
return entries;
|
|
244
|
+
}
|
|
245
|
+
function settleBatch(batch, results) {
|
|
246
|
+
for (let index = 0; index < batch.length; index += 1) {
|
|
247
|
+
const result = results[index];
|
|
248
|
+
if (result.ok) batch[index].resolve();
|
|
249
|
+
else batch[index].reject(result.error);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function rejectBatch(batch, err) {
|
|
253
|
+
for (const waiter of batch) waiter.reject(err);
|
|
254
|
+
}
|
|
255
|
+
async function persistHistory(newEntries) {
|
|
256
|
+
const existing = await loadHistory(historyFilePath);
|
|
257
|
+
const merged = [...newEntries.slice().reverse(), ...existing.entries].slice(0, 50);
|
|
258
|
+
await saveHistory(requireWriteJson(), historyFilePath, { entries: merged });
|
|
259
|
+
}
|
|
260
|
+
async function processBatch(batch) {
|
|
261
|
+
let state;
|
|
262
|
+
try {
|
|
263
|
+
state = await loadActive(activeFilePath);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
logger().error("load failed", { error: String(err) });
|
|
266
|
+
rejectBatch(batch, err);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const results = applyBatchMutations(batch, state);
|
|
270
|
+
const events = collectEvents(results);
|
|
271
|
+
const historyEntries = collectHistoryEntries(results);
|
|
272
|
+
if (events.length > 0) {
|
|
273
|
+
try {
|
|
274
|
+
await saveActive(requireWriteJson(), activeFilePath, state);
|
|
275
|
+
} catch (err) {
|
|
276
|
+
logger().error("active write failed", { error: String(err) });
|
|
277
|
+
rejectBatch(batch, err);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (historyEntries.length > 0) try {
|
|
281
|
+
await persistHistory(historyEntries);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
logger().error("history write failed", { error: String(err) });
|
|
284
|
+
}
|
|
285
|
+
for (const event of events) emit(event);
|
|
286
|
+
}
|
|
287
|
+
settleBatch(batch, results);
|
|
288
|
+
}
|
|
289
|
+
async function drain() {
|
|
290
|
+
writing = true;
|
|
291
|
+
try {
|
|
292
|
+
while (waiters.length > 0) {
|
|
293
|
+
const batch = waiters;
|
|
294
|
+
waiters = [];
|
|
295
|
+
await processBatch(batch);
|
|
296
|
+
}
|
|
297
|
+
} finally {
|
|
298
|
+
writing = false;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function enqueue(mutate) {
|
|
302
|
+
return new Promise((resolve, reject) => {
|
|
303
|
+
waiters.push({
|
|
304
|
+
mutate,
|
|
305
|
+
resolve,
|
|
306
|
+
reject
|
|
307
|
+
});
|
|
308
|
+
if (!writing) drain();
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
function removeEntry(state, entryId) {
|
|
312
|
+
const { [entryId]: __removed, ...remaining } = state.entries;
|
|
313
|
+
return remaining;
|
|
314
|
+
}
|
|
315
|
+
function buildHistoryEntry(entry, terminalType) {
|
|
316
|
+
return {
|
|
317
|
+
...entry,
|
|
318
|
+
terminalType,
|
|
319
|
+
terminalAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
async function publish(input) {
|
|
323
|
+
const validationError = validatePublishInput(input);
|
|
324
|
+
if (validationError) throw new Error(`notifier.publish: ${validationError}`);
|
|
325
|
+
const entryId = (0, node_crypto.randomUUID)();
|
|
326
|
+
const entry = {
|
|
327
|
+
id: entryId,
|
|
328
|
+
pluginPkg: input.pluginPkg,
|
|
329
|
+
severity: input.severity,
|
|
330
|
+
lifecycle: input.lifecycle,
|
|
331
|
+
title: input.title,
|
|
332
|
+
body: input.body,
|
|
333
|
+
navigateTarget: input.navigateTarget,
|
|
334
|
+
pluginData: input.pluginData,
|
|
335
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
336
|
+
};
|
|
337
|
+
await enqueue((state) => {
|
|
338
|
+
state.entries[entryId] = entry;
|
|
339
|
+
return { event: {
|
|
340
|
+
type: "published",
|
|
341
|
+
entry
|
|
342
|
+
} };
|
|
343
|
+
});
|
|
344
|
+
return { id: entryId };
|
|
345
|
+
}
|
|
346
|
+
async function clear(entryId) {
|
|
347
|
+
await enqueue((state) => {
|
|
348
|
+
const entry = state.entries[entryId];
|
|
349
|
+
if (!entry) return null;
|
|
350
|
+
state.entries = removeEntry(state, entryId);
|
|
351
|
+
return {
|
|
352
|
+
event: {
|
|
353
|
+
type: "cleared",
|
|
354
|
+
id: entryId
|
|
355
|
+
},
|
|
356
|
+
historyEntry: buildHistoryEntry(entry, "cleared")
|
|
357
|
+
};
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
async function cancel(entryId) {
|
|
361
|
+
await enqueue((state) => {
|
|
362
|
+
const entry = state.entries[entryId];
|
|
363
|
+
if (!entry) return null;
|
|
364
|
+
state.entries = removeEntry(state, entryId);
|
|
365
|
+
return {
|
|
366
|
+
event: {
|
|
367
|
+
type: "cancelled",
|
|
368
|
+
id: entryId
|
|
369
|
+
},
|
|
370
|
+
historyEntry: buildHistoryEntry(entry, "cancelled")
|
|
371
|
+
};
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
/** In-place update for an active entry. Only the fields present on
|
|
375
|
+
* `patch` are rewritten; `id`, `pluginPkg`, `lifecycle`, and
|
|
376
|
+
* `createdAt` stay fixed. Emits a single `"updated"` event with the
|
|
377
|
+
* post-mutation entry — no history record is written because the
|
|
378
|
+
* entry is still active, just with refreshed content.
|
|
379
|
+
*
|
|
380
|
+
* No-ops (no throw) when the id is unknown, the entry belongs to a
|
|
381
|
+
* different plugin, or the merged shape would violate
|
|
382
|
+
* `validatePublishInput`. The silent skip matches `clearForPlugin`'s
|
|
383
|
+
* isolation semantics; validation failures are logged for diagnosis. */
|
|
384
|
+
async function updateForPlugin(pluginPkg, entryId, patch) {
|
|
385
|
+
await enqueue((state) => {
|
|
386
|
+
const entry = state.entries[entryId];
|
|
387
|
+
if (!entry) return null;
|
|
388
|
+
if (entry.pluginPkg !== pluginPkg) return null;
|
|
389
|
+
const next = {
|
|
390
|
+
...entry,
|
|
391
|
+
...patch.severity !== void 0 ? { severity: patch.severity } : {},
|
|
392
|
+
...patch.title !== void 0 ? { title: patch.title } : {},
|
|
393
|
+
...patch.body !== void 0 ? { body: patch.body } : {},
|
|
394
|
+
...patch.navigateTarget !== void 0 ? { navigateTarget: patch.navigateTarget } : {},
|
|
395
|
+
...patch.pluginData !== void 0 ? { pluginData: patch.pluginData } : {}
|
|
396
|
+
};
|
|
397
|
+
const validationError = validatePublishInput({
|
|
398
|
+
pluginPkg: next.pluginPkg,
|
|
399
|
+
severity: next.severity,
|
|
400
|
+
title: next.title,
|
|
401
|
+
body: next.body,
|
|
402
|
+
lifecycle: next.lifecycle,
|
|
403
|
+
navigateTarget: next.navigateTarget,
|
|
404
|
+
pluginData: next.pluginData
|
|
405
|
+
});
|
|
406
|
+
if (validationError) {
|
|
407
|
+
logger().warn("update rejected by validation", {
|
|
408
|
+
entryId,
|
|
409
|
+
pluginPkg,
|
|
410
|
+
error: validationError
|
|
411
|
+
});
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
state.entries[entryId] = next;
|
|
415
|
+
return { event: {
|
|
416
|
+
type: "updated",
|
|
417
|
+
entry: next
|
|
418
|
+
} };
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
/** Plugin-scoped point lookup. Returns the entry by id, but only if it
|
|
422
|
+
* belongs to the caller's plugin; otherwise undefined. Cross-plugin
|
|
423
|
+
* reads return undefined for isolation — same property as
|
|
424
|
+
* `clearForPlugin` / `updateForPlugin`. */
|
|
425
|
+
async function getForPlugin(pluginPkg, entryId) {
|
|
426
|
+
const entry = (await loadActive(activeFilePath)).entries[entryId];
|
|
427
|
+
if (!entry) return void 0;
|
|
428
|
+
if (entry.pluginPkg !== pluginPkg) return void 0;
|
|
429
|
+
return entry;
|
|
430
|
+
}
|
|
431
|
+
/** Plugin-scoped clear. Same as `clear` but no-ops if the entry's
|
|
432
|
+
* `pluginPkg` doesn't match the caller's, so a plugin can't dismiss
|
|
433
|
+
* another plugin's notification by guessing or scraping its id. */
|
|
434
|
+
async function clearForPlugin(pluginPkg, entryId) {
|
|
435
|
+
await enqueue((state) => {
|
|
436
|
+
const entry = state.entries[entryId];
|
|
437
|
+
if (!entry) return null;
|
|
438
|
+
if (entry.pluginPkg !== pluginPkg) return null;
|
|
439
|
+
state.entries = removeEntry(state, entryId);
|
|
440
|
+
return {
|
|
441
|
+
event: {
|
|
442
|
+
type: "cleared",
|
|
443
|
+
id: entryId
|
|
444
|
+
},
|
|
445
|
+
historyEntry: buildHistoryEntry(entry, "cleared")
|
|
446
|
+
};
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
async function get(entryId) {
|
|
450
|
+
return (await loadActive(activeFilePath)).entries[entryId];
|
|
451
|
+
}
|
|
452
|
+
async function listFor(pluginPkg) {
|
|
453
|
+
const state = await loadActive(activeFilePath);
|
|
454
|
+
return Object.values(state.entries).filter((entry) => entry.pluginPkg === pluginPkg);
|
|
455
|
+
}
|
|
456
|
+
async function listAll() {
|
|
457
|
+
const state = await loadActive(activeFilePath);
|
|
458
|
+
return Object.values(state.entries);
|
|
459
|
+
}
|
|
460
|
+
async function listHistory() {
|
|
461
|
+
return (await loadHistory(historyFilePath)).entries;
|
|
462
|
+
}
|
|
463
|
+
//#endregion
|
|
464
|
+
exports.HISTORY_CAP = HISTORY_CAP;
|
|
465
|
+
exports.NOTIFIER_LIFECYCLES = NOTIFIER_LIFECYCLES;
|
|
466
|
+
exports.NOTIFIER_LIMITS = NOTIFIER_LIMITS;
|
|
467
|
+
exports.NOTIFIER_SEVERITIES = NOTIFIER_SEVERITIES;
|
|
468
|
+
exports.cancel = cancel;
|
|
469
|
+
exports.clear = clear;
|
|
470
|
+
exports.clearForPlugin = clearForPlugin;
|
|
471
|
+
exports.configureNotifier = configureNotifier;
|
|
472
|
+
exports.get = get;
|
|
473
|
+
exports.getForPlugin = getForPlugin;
|
|
474
|
+
exports.listAll = listAll;
|
|
475
|
+
exports.listFor = listFor;
|
|
476
|
+
exports.listHistory = listHistory;
|
|
477
|
+
exports.onEvent = onEvent;
|
|
478
|
+
exports.publish = publish;
|
|
479
|
+
exports.resetNotifier = resetNotifier;
|
|
480
|
+
exports.setNotifierFilePaths = setNotifierFilePaths;
|
|
481
|
+
exports.updateForPlugin = updateForPlugin;
|
|
482
|
+
exports.validatePublishInput = validatePublishInput;
|
|
483
|
+
|
|
484
|
+
//# sourceMappingURL=index.cjs.map
|