@filamind-app/core 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -3,6 +3,11 @@
3
3
  The shared foundation of the FilaMind suite — **framework-agnostic TypeScript** consumed by FilaMind 3d
4
4
  (web), FilaMind screen (touch), and FilaMind flow. Phase 0.
5
5
 
6
+ [![CI](https://github.com/filamind-app/filamind-core/actions/workflows/ci.yml/badge.svg)](https://github.com/filamind-app/filamind-core/actions/workflows/ci.yml)
7
+ [![npm](https://img.shields.io/npm/v/@filamind-app/core?color=111111&label=npm)](https://www.npmjs.com/package/@filamind-app/core)
8
+ [![License: GPLv3](https://img.shields.io/badge/License-GPLv3-111111.svg)](LICENSE)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-111111?logo=typescript&logoColor=white)](https://www.typescriptlang.org)
10
+
6
11
  ## What's here
7
12
  | Module | Purpose |
8
13
  | --- | --- |
@@ -11,6 +16,7 @@ The shared foundation of the FilaMind suite — **framework-agnostic TypeScript*
11
16
  | `state/printer.ts` | `PrinterState` — merge-patch from `notify_status_update`, **1 s coalescing + `motion_report` fast-path** |
12
17
  | `moonraker/connector.ts` | the backend-agnostic `Connector` seam (Moonraker first; remote/others later) |
13
18
  | `moonraker/client.ts` | `MoonrakerClient` — reconnecting JSON-RPC WS (**injectable socket**, backoff **+ jitter + max-attempts**), id-correlated, notify fan-out, re-subscribe on reconnect, **REST file channel** (`upload`/`download`) |
19
+ | `moonraker/discovery.ts` | **zero-config endpoint discovery** — `resolveMoonrakerUrl()` races candidate endpoints (same-origin proxy / direct `:7125` / localhost) → first to open wins; runtime `override` short-circuits. Socket-injectable |
14
20
  | `moonraker/subscriptions.ts` | the **versioned** canonical subscription contract — `NARROW_STATUS` / `FULL_CONTROL` tiers + `tier()` |
15
21
  | `printer/klippy.ts` | the Klippy lifecycle (`ready`/`startup`/`shutdown`/`error`/`disconnected`) — tracked apart from the socket so a FIRMWARE_RESTART re-seeds instead of showing stale-as-live |
16
22
  | `printer/prompt-parser.ts` | parses Klipper's `// action:prompt_*` protocol into structured modal dialogs (`PromptParser`) |
@@ -20,7 +26,8 @@ The shared foundation of the FilaMind suite — **framework-agnostic TypeScript*
20
26
  | `backup/restore-points.ts` | reversible, retention-bounded **`RestorePoints`** (back-up-first substrate for config/flash/migrate) |
21
27
  | `observability/logger.ts` | pluggable ring-buffer `Logger` (the diagnostics-bundle source; replaces silent `catch {}`) |
22
28
  | `registry/widget-registry.ts` | the cross-surface widget/plugin registry + `aggregateSubscriptions` |
23
- | `theme/tokens.ts` | the exclusive Pharaonic themes (Tutankhamun · Horus · Anubis) as `--fm-*` design tokens |
29
+ | `remote/commands.ts` · `remote/command-sender.ts` | the cross-surface command bus — **UI-only** commands (navigate / message / locate) over a Moonraker **agent** connection. `CommandSender` identifies as an agent, re-identifies on reconnect, is single-flight, and sanitizes control/bidi text; it is never a mutation path (those still go through `WriteArbiter`) |
30
+ | `theme/tokens.ts` | the theme palettes as `--fm-*` design tokens — 3 signature Pharaonic themes (Tutankhamun · Horus · Anubis) + neutral **light** / **dark** |
24
31
  | `i18n/locale-meta.ts` | the 19 shipped locales `{ code, name, rtl, dir }` + RTL list + CLDR `pluralCategory` (`Intl.PluralRules`) |
25
32
  | `i18n/translator.ts` | framework-agnostic `translate` / `Translator` + the backend message-code contract (`resolveMessage`) |
26
33
  | `i18n/locales/*.json` | drop-in catalogs (en + ar shipped as proof, incl. the full Arabic plural set) |
package/dist/index.d.ts CHANGED
@@ -143,6 +143,37 @@ declare class MoonrakerClient implements Connector {
143
143
  private onMessage;
144
144
  }
145
145
 
146
+ /** A minimal view of `location` — the DOM Location satisfies it; tests pass a plain object. */
147
+ interface LocationLike {
148
+ protocol?: string;
149
+ hostname?: string;
150
+ }
151
+ interface DiscoveryOptions {
152
+ /** Explicit runtime override (e.g. from settings). If set, it wins immediately — no probing. */
153
+ override?: string;
154
+ /** Candidate ws(s):// URLs to race. If omitted, they are derived from `location`. */
155
+ candidates?: string[];
156
+ /** Overall budget before discovery gives up (ms). Default 3000. */
157
+ timeoutMs?: number;
158
+ /** Injectable socket factory (tests / non-DOM). Default: `new WebSocket`. */
159
+ wsFactory?: (url: string) => WebSocketLike;
160
+ /** Source for deriving candidates. Default: `globalThis.location`. */
161
+ location?: LocationLike;
162
+ }
163
+ /**
164
+ * Ordered, de-duplicated candidate endpoints:
165
+ * 1. same-origin `/websocket` (the suite deploy reverse-proxies Moonraker here),
166
+ * 2. the page host on Moonraker's default `:7125` (direct, no proxy),
167
+ * 3. `localhost:7125` (on-printer / Tauri webview, whose origin is `tauri.localhost`).
168
+ */
169
+ declare function deriveCandidates(location?: LocationLike): string[];
170
+ /**
171
+ * Resolve a reachable Moonraker WebSocket URL. `override` wins immediately; otherwise the
172
+ * candidates are raced and the first socket to open wins (the rest are closed). Rejects if
173
+ * none open within `timeoutMs`.
174
+ */
175
+ declare function resolveMoonrakerUrl(opts?: DiscoveryOptions): Promise<string>;
176
+
146
177
  declare const SUBSCRIPTION_CONTRACT_VERSION = 1;
147
178
  /** Status-only tier (farm overview / ambient screen): the minimum to show state + progress. */
148
179
  declare const NARROW_STATUS: SubscriptionMap;
@@ -361,7 +392,7 @@ interface ThemeTokens {
361
392
  warning: string;
362
393
  danger: string;
363
394
  }
364
- type ThemeName = 'tutankhamun' | 'horus' | 'anubis';
395
+ type ThemeName = 'tutankhamun' | 'horus' | 'anubis' | 'light' | 'dark';
365
396
  declare const themes: Record<ThemeName, ThemeTokens>;
366
397
  declare const DEFAULT_THEME: ThemeName;
367
398
  /** `{ "--fm-bg": "#0E0F12", ... }` for a theme. */
@@ -512,4 +543,4 @@ declare function applySettings(s: UserSettings, el?: {
512
543
  dir: 'ltr' | 'rtl';
513
544
  };
514
545
 
515
- export { type AgentAllow, type AgentEvent, type BackendMessage, type Catalog, CommandSender, type CommandSenderOptions, type ConnectionState, type Connector, type ConnectorCallbacks, DEFAULT_LOCALE, DEFAULT_SETTINGS, DEFAULT_THEME, FILAMIND_COMMAND_EVENT, FULL_CONTROL, FilaMindSession, type IdentifyInfo, type KlippyState, LOCALES, type Listener, type LocaleMeta, type LogEntry, type LogLevel, Logger, MoonrakerClient, type MoonrakerClientOptions, NARROW_STATUS, NULL_LOGGER, Observable, type Params, type PluralCategory, type PrinterObjects, PrinterState, type PromptButton, type PromptDialog, type PromptEvent, PromptParser, REMOTE_MESSAGE_LEVELS, REMOTE_VIEWS, RTL_LOCALES, type RemoteCommand, type RemoteMessageLevel, type RemoteView, type RestorePoint, RestorePoints, type RestoreStore, SETTINGS_VERSION, SUBSCRIPTION_CONTRACT_VERSION, type SessionOptions, type SettingsPersistence, SettingsStore, type Source, type Stamped, type SubscriptionMap, type SubscriptionTier, type SurfaceTarget, type ThemeName, type ThemeTokens, Translator, UNKNOWN, type UserSettings, type WebSocketLike, type WidgetDefinition, WriteArbiter, type WriteGuard, WriteRefused, _resetRegistry, aggregateSubscriptions, applySettings, applyTheme, deepMerge, deriveKlippyState, deriveMachineId, fnv1a, freshness, getWidget, getWidgets, handleAgentCommand, isKlippyLive, isRtl, isStale, localStoragePersistence, localeMeta, memoryPersistence, memoryRestoreStore, mergeSubscriptions, migrate, moonrakerDbPersistence, parseAgentEvent, parseCommand, pluralCategory, registerWidget, resolveMessage, roamSettings, stamp, themeToCssVars, themes, tier, translate };
546
+ export { type AgentAllow, type AgentEvent, type BackendMessage, type Catalog, CommandSender, type CommandSenderOptions, type ConnectionState, type Connector, type ConnectorCallbacks, DEFAULT_LOCALE, DEFAULT_SETTINGS, DEFAULT_THEME, type DiscoveryOptions, FILAMIND_COMMAND_EVENT, FULL_CONTROL, FilaMindSession, type IdentifyInfo, type KlippyState, LOCALES, type Listener, type LocaleMeta, type LocationLike, type LogEntry, type LogLevel, Logger, MoonrakerClient, type MoonrakerClientOptions, NARROW_STATUS, NULL_LOGGER, Observable, type Params, type PluralCategory, type PrinterObjects, PrinterState, type PromptButton, type PromptDialog, type PromptEvent, PromptParser, REMOTE_MESSAGE_LEVELS, REMOTE_VIEWS, RTL_LOCALES, type RemoteCommand, type RemoteMessageLevel, type RemoteView, type RestorePoint, RestorePoints, type RestoreStore, SETTINGS_VERSION, SUBSCRIPTION_CONTRACT_VERSION, type SessionOptions, type SettingsPersistence, SettingsStore, type Source, type Stamped, type SubscriptionMap, type SubscriptionTier, type SurfaceTarget, type ThemeName, type ThemeTokens, Translator, UNKNOWN, type UserSettings, type WebSocketLike, type WidgetDefinition, WriteArbiter, type WriteGuard, WriteRefused, _resetRegistry, aggregateSubscriptions, applySettings, applyTheme, deepMerge, deriveCandidates, deriveKlippyState, deriveMachineId, fnv1a, freshness, getWidget, getWidgets, handleAgentCommand, isKlippyLive, isRtl, isStale, localStoragePersistence, localeMeta, memoryPersistence, memoryRestoreStore, mergeSubscriptions, migrate, moonrakerDbPersistence, parseAgentEvent, parseCommand, pluralCategory, registerWidget, resolveMessage, resolveMoonrakerUrl, roamSettings, stamp, themeToCssVars, themes, tier, translate };
package/dist/index.js CHANGED
@@ -270,6 +270,85 @@ function xhrUpload(url, form, onProgress) {
270
270
  });
271
271
  }
272
272
 
273
+ // src/moonraker/discovery.ts
274
+ var defaultWsFactory2 = (url) => new WebSocket(url);
275
+ function deriveCandidates(location) {
276
+ const loc = location ?? globalThis.location ?? {};
277
+ const wsProto = loc.protocol === "https:" ? "wss" : "ws";
278
+ const host = loc.hostname && loc.hostname.length > 0 ? loc.hostname : "localhost";
279
+ return [
280
+ .../* @__PURE__ */ new Set([
281
+ `${wsProto}://${host}/websocket`,
282
+ `${wsProto}://${host}:7125/websocket`,
283
+ `ws://localhost:7125/websocket`
284
+ ])
285
+ ];
286
+ }
287
+ function resolveMoonrakerUrl(opts = {}) {
288
+ const override = opts.override?.trim();
289
+ if (override) return Promise.resolve(override);
290
+ const candidates = opts.candidates ?? deriveCandidates(opts.location);
291
+ if (candidates.length === 0) {
292
+ return Promise.reject(new Error("moonraker discovery: no candidate endpoints"));
293
+ }
294
+ const makeWs = opts.wsFactory ?? defaultWsFactory2;
295
+ const timeoutMs = opts.timeoutMs ?? 3e3;
296
+ return new Promise((resolve, reject) => {
297
+ let settled = false;
298
+ let remaining = candidates.length;
299
+ const sockets = [];
300
+ const timer = setTimeout(finishFail, timeoutMs);
301
+ function cleanup() {
302
+ clearTimeout(timer);
303
+ for (const s of sockets) {
304
+ s.onopen = null;
305
+ s.onerror = null;
306
+ s.onclose = null;
307
+ try {
308
+ s.close();
309
+ } catch {
310
+ }
311
+ }
312
+ }
313
+ function finishOk(url) {
314
+ if (settled) return;
315
+ settled = true;
316
+ cleanup();
317
+ resolve(url);
318
+ }
319
+ function finishFail() {
320
+ if (settled) return;
321
+ settled = true;
322
+ cleanup();
323
+ reject(new Error(`moonraker discovery: no endpoint responded (${candidates.join(", ")})`));
324
+ }
325
+ function lose() {
326
+ remaining -= 1;
327
+ if (remaining <= 0) finishFail();
328
+ }
329
+ for (const url of candidates) {
330
+ let s;
331
+ try {
332
+ s = makeWs(url);
333
+ } catch {
334
+ lose();
335
+ continue;
336
+ }
337
+ sockets.push(s);
338
+ let done = false;
339
+ const fail = () => {
340
+ if (!done) {
341
+ done = true;
342
+ lose();
343
+ }
344
+ };
345
+ s.onopen = () => finishOk(url);
346
+ s.onerror = fail;
347
+ s.onclose = fail;
348
+ }
349
+ });
350
+ }
351
+
273
352
  // src/moonraker/subscriptions.ts
274
353
  var SUBSCRIPTION_CONTRACT_VERSION = 1;
275
354
  var NARROW_STATUS = {
@@ -759,6 +838,38 @@ var themes = {
759
838
  success: "#6E7C3A",
760
839
  warning: "#D98E2B",
761
840
  danger: "#8C1F1A"
841
+ },
842
+ // neutral light — a conventional bright UI for users who prefer it
843
+ light: {
844
+ bg: "#F7F8FA",
845
+ surface: "#FFFFFF",
846
+ surface2: "#EEF1F5",
847
+ border: "#D6DBE2",
848
+ text: "#1A1D23",
849
+ textMuted: "#5B6573",
850
+ primary: "#2563EB",
851
+ primaryContrast: "#FFFFFF",
852
+ secondary: "#64748B",
853
+ accent: "#0D9488",
854
+ success: "#15803D",
855
+ warning: "#B45309",
856
+ danger: "#DC2626"
857
+ },
858
+ // neutral dark — a conventional dark UI, distinct from the warm Pharaonic palettes
859
+ dark: {
860
+ bg: "#0F1115",
861
+ surface: "#171A21",
862
+ surface2: "#1F232C",
863
+ border: "#2C313C",
864
+ text: "#E6E8EC",
865
+ textMuted: "#9AA2AE",
866
+ primary: "#4F8EF7",
867
+ primaryContrast: "#0B0D11",
868
+ secondary: "#6B7280",
869
+ accent: "#14B8A6",
870
+ success: "#22A06B",
871
+ warning: "#E0A92E",
872
+ danger: "#E5484D"
762
873
  }
763
874
  };
764
875
  var DEFAULT_THEME = "tutankhamun";
@@ -1140,6 +1251,7 @@ export {
1140
1251
  applySettings,
1141
1252
  applyTheme,
1142
1253
  deepMerge,
1254
+ deriveCandidates,
1143
1255
  deriveKlippyState,
1144
1256
  deriveMachineId,
1145
1257
  fnv1a,
@@ -1162,6 +1274,7 @@ export {
1162
1274
  pluralCategory,
1163
1275
  registerWidget,
1164
1276
  resolveMessage,
1277
+ resolveMoonrakerUrl,
1165
1278
  roamSettings,
1166
1279
  stamp,
1167
1280
  themeToCssVars,
package/package.json CHANGED
@@ -1,21 +1,33 @@
1
1
  {
2
2
  "name": "@filamind-app/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Shared foundation for the FilaMind suite — Moonraker client, reactive printer state, provenance-stamped values, design tokens, and the widget/plugin registry. Framework-agnostic TypeScript.",
5
5
  "license": "GPL-3.0-or-later",
6
- "repository": { "type": "git", "url": "git+https://github.com/filamind-app/filamind-core.git" },
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/filamind-app/filamind-core.git"
9
+ },
7
10
  "homepage": "https://github.com/filamind-app/filamind-core#readme",
8
- "bugs": { "url": "https://github.com/filamind-app/filamind-core/issues" },
11
+ "bugs": {
12
+ "url": "https://github.com/filamind-app/filamind-core/issues"
13
+ },
9
14
  "type": "module",
10
15
  "main": "./dist/index.js",
11
16
  "module": "./dist/index.js",
12
17
  "types": "./dist/index.d.ts",
13
18
  "exports": {
14
- ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" }
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
15
23
  },
16
- "files": ["dist"],
24
+ "files": [
25
+ "dist"
26
+ ],
17
27
  "sideEffects": false,
18
- "publishConfig": { "access": "public" },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
19
31
  "scripts": {
20
32
  "build": "tsup src/index.ts --format esm --dts --clean",
21
33
  "dev": "tsup src/index.ts --format esm --dts --watch",