From 46e4f247555d7a4233a9224af152be648d802610 Mon Sep 17 00:00:00 2001 From: mpstaton Date: Sat, 2 May 2026 13:53:12 -0500 Subject: [PATCH] align(requirements): align Perplexed to Obsidian's plugin community standards On branch development Your branch is up to date with 'origin/development'. Changes to be committed: new file: LICENSE new file: context-v/plans/20206-05-02_Assuring-Obsidian-Community-Plugin-Requirements.md modified: eslint.config.mjs modified: main.ts modified: manifest.json modified: package.json modified: src/modals/ArticleGeneratorModal.ts modified: src/modals/ClaudeModal.ts modified: src/modals/LMStudioModal.ts modified: src/modals/PerplexicaModal.ts modified: src/modals/PerplexityModal.ts modified: src/modals/TextEnhancementModal.ts modified: src/modals/TextEnhancementWithImagesModal.ts modified: src/modals/URLUpdateModal.ts modified: src/services/lmStudioService.ts modified: src/services/perplexicaService.ts modified: src/services/perplexityService.ts modified: src/types/obsidian.d.ts new file: src/utils/coerce.ts modified: src/utils/formatDate.ts modified: src/utils/logger.ts modified: styles.css modified: versions.json --- .npmrc | 3 +- LICENSE | 21 ++ ...Getting-Claude-to-Respond-With-Research.md | 0 .../Widen-Modals-in-Obsidian-using-CSS.md | 0 ...-Obsidian-Community-Plugin-Requirements.md | 177 ++++++++++++++++ eslint.config.mjs | 9 +- main.ts | 3 +- manifest.json | 4 +- package-lock.json | Bin 122367 -> 0 bytes package.json | 10 +- pnpm-lock.yaml | Bin 71309 -> 40635 bytes pnpm-workspace.yaml | 2 + src/modals/ArticleGeneratorModal.ts | 4 +- src/modals/ClaudeModal.ts | 4 +- src/modals/LMStudioModal.ts | 9 +- src/modals/PerplexicaModal.ts | 9 +- src/modals/PerplexityModal.ts | 4 +- src/modals/TextEnhancementModal.ts | 7 +- src/modals/TextEnhancementWithImagesModal.ts | 7 +- src/modals/URLUpdateModal.ts | 5 +- src/services/lmStudioService.ts | 85 +++++--- src/services/perplexicaService.ts | 124 ++++++----- src/services/perplexityService.ts | 192 ++++++++++-------- src/types/obsidian.d.ts | 8 +- src/utils/coerce.ts | 33 +++ src/utils/formatDate.ts | 70 +++---- src/utils/logger.ts | 15 +- versions.json | 2 +- 28 files changed, 543 insertions(+), 264 deletions(-) create mode 100644 LICENSE rename context-v/{issue-resolutions => issues}/Getting-Claude-to-Respond-With-Research.md (100%) rename context-v/{issue-resolutions => issues}/Widen-Modals-in-Obsidian-using-CSS.md (100%) create mode 100644 context-v/plans/20206-05-02_Assuring-Obsidian-Community-Plugin-Requirements.md delete mode 100644 package-lock.json create mode 100644 pnpm-workspace.yaml create mode 100644 src/utils/coerce.ts diff --git a/.npmrc b/.npmrc index 90a9959..adda767 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,4 @@ shamefully-hoist=true auto-install-peers=true -shared-workspace-lockfile=false \ No newline at end of file +shared-workspace-lockfile=false +ignore-workspace=true \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..940014f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025-2026 The Lossless Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/context-v/issue-resolutions/Getting-Claude-to-Respond-With-Research.md b/context-v/issues/Getting-Claude-to-Respond-With-Research.md similarity index 100% rename from context-v/issue-resolutions/Getting-Claude-to-Respond-With-Research.md rename to context-v/issues/Getting-Claude-to-Respond-With-Research.md diff --git a/context-v/issue-resolutions/Widen-Modals-in-Obsidian-using-CSS.md b/context-v/issues/Widen-Modals-in-Obsidian-using-CSS.md similarity index 100% rename from context-v/issue-resolutions/Widen-Modals-in-Obsidian-using-CSS.md rename to context-v/issues/Widen-Modals-in-Obsidian-using-CSS.md diff --git a/context-v/plans/20206-05-02_Assuring-Obsidian-Community-Plugin-Requirements.md b/context-v/plans/20206-05-02_Assuring-Obsidian-Community-Plugin-Requirements.md new file mode 100644 index 0000000..757ebe8 --- /dev/null +++ b/context-v/plans/20206-05-02_Assuring-Obsidian-Community-Plugin-Requirements.md @@ -0,0 +1,177 @@ +--- +title: "Plan — Bring Perplexed up to Obsidian Community-Plugin Publishing Standards" +status: Proposed +created: 2026-05-02 +applies_to: perplexed Obsidian plugin +authors: + - Michael Staton +augmented_with: Claude Code (Opus 4.7, 1M context) +related_reference: ../../cite-wide/context-v/reminders/Obsidian-Type-Safety.md +--- + +# Plan — Bring Perplexed up to Obsidian Community-Plugin Publishing Standards + +## Context + +The Perplexed Obsidian plugin (`/Users/mpstaton/code/lossless-monorepo/perplexed`) is being prepped for submission to the Obsidian community plugin marketplace. Its sister plugin `cite-wide` was rejected last week for type-safety violations (specifically `any` usage), and the rules that triggered that rejection are enforced automatically by `ObsidianReviewBot` on every submission PR with **no appeal mechanism**. There is also a set of metadata/repo-hygiene requirements that block submission independently. + +The reference doc — `/Users/mpstaton/code/lossless-monorepo/cite-wide/context-v/reminders/Obsidian-Type-Safety.md` — captures the rules verbatim from the review bot and the patterns to satisfy them. This plan applies those rules to Perplexed and bundles in the non-type-safety publishing fixes uncovered during the audit. + +**Audit totals:** 25 explicit-`any` sites + 3 `as any` casts + 5 metadata/repo blockers + ESLint config that lets `any` through as a warning. No floating-promises, no `[object Object]` interpolations, no `innerHTML`, no `(window as any)` patterns, no hand-rolled YAML (Perplexed doesn't touch frontmatter — different surface area than cite-wide). Critical files all reviewed. + +--- + +## Phase 1 — Tighten ESLint to Match the Review Bot + +**Why first:** if the local lint config matches the bot, we catch every remaining issue during `pnpm build` instead of at submission. The current config (`eslint.config.mjs:29`) has `no-explicit-any: "warn"` which lets violations through. + +**File:** `/Users/mpstaton/code/lossless-monorepo/perplexed/eslint.config.mjs` + +Change the `rules` block: +- `"@typescript-eslint/no-explicit-any": "error"` (was `"warn"`) +- Add `"@typescript-eslint/no-unnecessary-type-assertion": "error"` +- Add `"@typescript-eslint/no-floating-promises": "error"` +- Add `"@typescript-eslint/no-base-to-string": "error"` +- `"@typescript-eslint/no-unused-vars": ["error", { args: "none" }]` — already present, keep +- Add CLI flag in `package.json` build script: `eslint . --report-unused-disable-directives` before `tsc`. Current build script: `"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production"` — change to `"build": "eslint . --report-unused-disable-directives && tsc -noEmit -skipLibCheck && node esbuild.config.mjs production"`. + +After this change, `pnpm build` will fail loudly. That failure surface drives the rest of the plan. + +--- + +## Phase 2 — Fix Manifest, Versions, and Package Metadata + +**Why:** four-part version strings (`X.Y.Z.W`) are not semver and the bot rejects them. The `minAppVersion` placeholder will also be flagged. + +### 2.1 `manifest.json` +- `version`: `"0.0.0.1"` → `"0.0.1"` +- `minAppVersion`: `"0.0.0.1"` → `"0.15.0"` (matches the sole entry in `versions.json`; safe baseline for current Obsidian APIs we use) + +### 2.2 `versions.json` +- Key `"0.0.1.0"` → `"0.0.1"` (must match the new manifest version, must be valid semver) + +### 2.3 `package.json` +- `version`: `"0.0.0.6"` → `"0.0.1"` (sync to manifest) +- Remove unused dependencies confirmed not imported anywhere in `src/` or `main.ts`: + - `fastify` + - `@modelcontextprotocol/sdk` + - `zod` + - Keep: `dotenv`, `@anthropic-ai/sdk` (the latter is used by `claudeService.ts`) +- After removal: `pnpm install` (or `npm install`) to regenerate the lockfile. + +--- + +## Phase 3 — Add LICENSE File + +**Why:** `package.json` declares MIT but no `LICENSE` file exists at the repo root. Obsidian's submission requirements expect the license file to be present and discoverable. + +- Create `/Users/mpstaton/code/lossless-monorepo/perplexed/LICENSE` with the standard MIT license text, copyright year `2025-2026`, copyright holder `The Lossless Group` (matches `manifest.json` author). + +--- + +## Phase 4 — Eliminate `any` (the type-safety pass) + +**Why:** this is the rule that rejected cite-wide. 25 sites + 3 casts must be eliminated. The reference doc's §2 and §5 give the exact replacement patterns. + +Order chosen so each step compiles cleanly before the next: + +### 4.1 Fix the ambient shim — `src/types/obsidian.d.ts:5` + +`commands: any` → minimal documented interface for the methods Perplexed actually calls. Search `main.ts` and services for `app.commands.*` usage and type only what's used (likely `executeCommandById(id: string): boolean` and `commands: Record`). If usage is one-off, fall back to `unknown` and narrow at the call site. + +### 4.2 Fix the logger — `src/utils/logger.ts` + +Six sites, all the same pattern: replace every `details?: any` and `details: any` with `details?: unknown`. +- Line 8: `details?: any;` (in `LogEntry` interface) +- Line 81: `addEntry(... details?: any)` +- Lines 109, 113, 117, 121: `error/warn/info/debug(message: string, details?: any)` + +The logger only stringifies `details` — no structural access — so `unknown` is a drop-in replacement. The one exception is line 86 (`details instanceof Error`), which is already a proper narrowing and works identically against `unknown`. + +### 4.3 Fix the date util — `src/utils/formatDate.ts` + +Lines 41, 58: `source: any` → `source: unknown`. Add an internal `Source` interface (the `getMostRecentDate` and `formatPublicationInfo` functions look for `date_published`, `last_updated`, etc. — read both functions, define the interface from the actual field accesses, then narrow with the `isRecord` guard from the reference doc §3.3 before reading fields). This requires also adding a small `coerce.ts` helper file with `isRecord`, `asString`, and `asDate` (same shapes from the reference doc) — these will be reused in 4.4. + +**New file to create:** `src/utils/coerce.ts` — copy the `asString`, `asNumber`, `asStringArray`, `asDate`, `isRecord` helpers verbatim from the reference doc §3.3. They are general-purpose and will pay for themselves several times in 4.4. + +### 4.4 Fix the API service payload types — `src/services/{perplexity,perplexica,lmStudio}Service.ts` + +Three services share the same shape problems: +- `promptsService?: any` and `private promptsService: any` (in constructor options + class field) +- `let payload: any` (request body builder) +- API response objects like `finalResponseData: any` and `images: any[]` + +**Per-service fixes:** + +`perplexicaService.ts` — Lines 7, 17, 77. +- Replace `promptsService` field/option type with `import type { PromptsService } from './promptsService'`. (Direct import; the cyclical-init concern in the reference doc §5.5 is about singleton patterns, which Perplexed doesn't have — these are constructor params.) +- Define `interface PerplexicaPayload { ... }` from the actual fields assigned (read lines ~70–110 to see what's set on `payload`). + +`perplexityService.ts` — Lines 14, 21, 51, 232, 397, 609, 707, 761, 777, plus the three `as any` casts at 225/227/228. +- Same `PromptsService` import for fields/options. +- Define `interface PerplexityPayload`, `interface PerplexityImage`, `interface PerplexitySource`, and `interface PerplexityResponse` from the response shape Perplexity returns (read lines 600–800 to enumerate fields actually accessed). Make the response interface use `unknown` for fields the code doesn't dereference, and concrete types for fields it does. +- The `loadingInterval` casts (`(this as any).loadingInterval`) are the exact pattern in reference doc §5.5: declare `private loadingInterval: ReturnType | null = null;` on the class and remove all three casts. +- Lines 51 and 232 — `images: any[]` and `sources: any[]` — type with `PerplexityImage[]` and `PerplexitySource[]`. + +`lmStudioService.ts` — Lines 13, 19, 82, 93. +- `PromptsService` import (same as above). +- `const messages: any[] = []` → `const messages: ChatMessage[] = []` where `interface ChatMessage { role: 'system' | 'user' | 'assistant'; content: string }` (OpenAI-compatible shape used by LM Studio). +- `let payload: any` → `interface LMStudioPayload { ... }`. + +### 4.5 Verify no `any` remains + +After the above, run `pnpm build`. The eslint step from Phase 1 will fail if any `any` slipped through. Fix any newly surfaced sites the same way (type with a minimal interface, narrow with `isRecord`, or use `unknown`). + +--- + +## Phase 5 — Console-Log Hygiene (recommended, not blocking) + +**Why:** the audit found 86 `console.*` calls in `main.ts`. These are not a hard ObsidianReviewBot rejection criterion, but the published guidelines discourage shipping debug logging. They also leak internals (paths, model names) into the user's devtools console. + +**Decision point — ask the user:** should we (a) remove all console statements, (b) gate them behind a `DEBUG` flag in settings, or (c) route them all through the existing `FileLogger` and drop the console output? The reference doc doesn't mandate a choice; cite-wide chose (c). Recommend (c) for consistency with cite-wide. + +This phase can ship in a follow-up PR if the user wants to keep the type-safety/metadata fix focused. + +--- + +## Verification + +After the implementation, run from `/Users/mpstaton/code/lossless-monorepo/perplexed`: + +1. **Lint clean:** `pnpm exec eslint . --report-unused-disable-directives` — expect zero errors. Specifically, expect zero `@typescript-eslint/no-explicit-any` errors. +2. **TypeScript clean:** `pnpm exec tsc -noEmit -skipLibCheck` — expect zero errors. Strict flags in `tsconfig.json` (already correct, do not weaken) will catch any residual narrowing gaps. +3. **Build clean:** `pnpm build` — expect a clean `main.js` produced. +4. **Manual smoke test in Obsidian:** + - Symlink or copy the built `main.js`, `manifest.json`, `styles.css` into a test vault's `.obsidian/plugins/perplexed/` folder. + - Enable the plugin, run each of: Perplexity research command, Perplexica research command, LM Studio command, Claude command (the WIP one — known broken per recent commits, just verify it doesn't crash on load). + - Confirm settings UI loads, all four endpoint fields render and save. + - Trigger an error path (e.g. invalid API key) and confirm the logger captures it without console-only output. +5. **Resubmission readiness check:** + - `manifest.json` version, `versions.json` key, `package.json` version all equal `"0.0.1"`. + - `LICENSE` file present at repo root. + - `git diff` shows zero `any` remaining in `.ts` files (excluding `node_modules` and `main.js`): `git grep -n ': any\|as any\|\|any\[\]' -- '*.ts' ':!node_modules'` should return nothing from our source files. + +--- + +## Critical Files Touched + +- `eslint.config.mjs` — Phase 1 +- `package.json` — Phase 1 (build script), Phase 2.3 (version, deps) +- `manifest.json` — Phase 2.1 +- `versions.json` — Phase 2.2 +- `LICENSE` — Phase 3 (new) +- `src/types/obsidian.d.ts` — Phase 4.1 +- `src/utils/logger.ts` — Phase 4.2 +- `src/utils/formatDate.ts` — Phase 4.3 +- `src/utils/coerce.ts` — Phase 4.3 (new) +- `src/services/perplexicaService.ts` — Phase 4.4 +- `src/services/perplexityService.ts` — Phase 4.4 (largest single file change) +- `src/services/lmStudioService.ts` — Phase 4.4 + +`main.ts` is **not** in the type-safety touch list — the audit found zero `any` in it. Phase 5 (optional) would touch it for console hygiene. + +## Reused Utilities + +- `coerce.ts` helpers (`asString`, `asNumber`, `asStringArray`, `asDate`, `isRecord`) — verbatim from reference doc §3.3, will be reused across `formatDate.ts` and the three service-payload narrowings in 4.4. +- Existing `FileLogger` singleton in `src/utils/logger.ts` — already present and properly designed, just needs `unknown` substitution. +- Obsidian's typed `App`, `Editor`, `TFile`, `Vault`, `Modal` from `'obsidian'` — already imported throughout; no new shim needed beyond the cleanup of `commands: any`. diff --git a/eslint.config.mjs b/eslint.config.mjs index 512a1c6..8af63b2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,7 +4,7 @@ import globals from "globals"; export default tseslint.config( { - ignores: ["node_modules/", "main.js"], + ignores: ["node_modules/", "main.js", "**/*.mjs"], }, js.configs.recommended, ...tseslint.configs.recommended, @@ -22,11 +22,14 @@ export default tseslint.config( }, rules: { "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": ["error", { args: "none" }], + "@typescript-eslint/no-unused-vars": ["error", { args: "none", caughtErrors: "none" }], "@typescript-eslint/ban-ts-comment": "off", "no-prototype-builtins": "off", "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-unnecessary-type-assertion": "error", + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-base-to-string": "error", "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/consistent-type-imports": "error", diff --git a/main.ts b/main.ts index 9199744..9889a0a 100644 --- a/main.ts +++ b/main.ts @@ -1,4 +1,5 @@ -import { App, Editor, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; +import type { App, Editor} from 'obsidian'; +import { Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; import * as dotenv from 'dotenv'; // Import services diff --git a/manifest.json b/manifest.json index c3b5772..09e5d86 100644 --- a/manifest.json +++ b/manifest.json @@ -1,8 +1,8 @@ { "id": "perplexed", "name": "Perplexed", - "version": "0.0.0.1", - "minAppVersion": "0.0.0.1", + "version": "0.1.0", + "minAppVersion": "0.15.0", "description": "A plugin for Obsidian that allows you to generate source-cited content using AI using Perplexity and Perplexica.", "author": "The Lossless Group", "authorUrl": "https://lossless.group", diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index c0cdbaecb3251c91902433d609027e03b2f19603..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122367 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 JgcTdB_y^As9Do1- delta 23507 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 { e.preventDefault(); - this.onSubmit(); + void this.onSubmit(); }; askButton.onclick = () => this.onSubmit(); diff --git a/src/modals/PerplexicaModal.ts b/src/modals/PerplexicaModal.ts index 034e403..7246623 100644 --- a/src/modals/PerplexicaModal.ts +++ b/src/modals/PerplexicaModal.ts @@ -1,6 +1,7 @@ -import { App, Modal, Notice, Editor } from 'obsidian'; -import { PerplexicaService, PerplexicaOptions } from '../services/perplexicaService'; -import { PromptsService } from '../services/promptsService'; +import type { App, Editor } from 'obsidian'; +import { Modal, Notice } from 'obsidian'; +import type { PerplexicaService, PerplexicaOptions } from '../services/perplexicaService'; +import type { PromptsService } from '../services/promptsService'; export class PerplexicaModal extends Modal { private editor: Editor; @@ -81,7 +82,7 @@ export class PerplexicaModal extends Modal { form.onsubmit = (e) => { e.preventDefault(); - this.onSubmit(); + void this.onSubmit(); }; askButton.onclick = () => this.onSubmit(); diff --git a/src/modals/PerplexityModal.ts b/src/modals/PerplexityModal.ts index 501be2e..7165906 100644 --- a/src/modals/PerplexityModal.ts +++ b/src/modals/PerplexityModal.ts @@ -1,5 +1,5 @@ -import { App, Modal, Notice, Setting } from 'obsidian'; -import type { Editor } from 'obsidian'; +import { Modal, Notice, Setting } from 'obsidian'; +import type { Editor , App} from 'obsidian'; import type { PerplexityService, PerplexityOptions } from '../services/perplexityService'; import type { PromptsService } from '../services/promptsService'; diff --git a/src/modals/TextEnhancementModal.ts b/src/modals/TextEnhancementModal.ts index a210d96..bd1463c 100644 --- a/src/modals/TextEnhancementModal.ts +++ b/src/modals/TextEnhancementModal.ts @@ -1,6 +1,7 @@ -import { App, Modal, Notice, Editor } from 'obsidian'; -import { PerplexityService } from '../services/perplexityService'; -import { PromptsService } from '../services/promptsService'; +import type { App, Editor } from 'obsidian'; +import { Modal, Notice } from 'obsidian'; +import type { PerplexityService } from '../services/perplexityService'; +import type { PromptsService } from '../services/promptsService'; export class TextEnhancementModal extends Modal { protected editor: Editor; diff --git a/src/modals/TextEnhancementWithImagesModal.ts b/src/modals/TextEnhancementWithImagesModal.ts index 5d76eaf..a884339 100644 --- a/src/modals/TextEnhancementWithImagesModal.ts +++ b/src/modals/TextEnhancementWithImagesModal.ts @@ -1,6 +1,7 @@ -import { App, Modal, Notice, Editor } from 'obsidian'; -import { PerplexityService } from '../services/perplexityService'; -import { PromptsService } from '../services/promptsService'; +import type { App, Editor } from 'obsidian'; +import { Modal, Notice } from 'obsidian'; +import type { PerplexityService } from '../services/perplexityService'; +import type { PromptsService } from '../services/promptsService'; export class TextEnhancementWithImagesModal extends Modal { protected editor: Editor; diff --git a/src/modals/URLUpdateModal.ts b/src/modals/URLUpdateModal.ts index 9c8b077..b36d56b 100644 --- a/src/modals/URLUpdateModal.ts +++ b/src/modals/URLUpdateModal.ts @@ -1,4 +1,5 @@ -import { App, Modal, Notice } from 'obsidian'; +import type { App} from 'obsidian'; +import { Modal, Notice } from 'obsidian'; export interface URLUpdateModalConfig { title: string; @@ -44,7 +45,7 @@ export class URLUpdateModal extends Modal { form.onsubmit = (e) => { e.preventDefault(); - this.onSubmit(); + void this.onSubmit(); }; saveButton.onclick = () => this.onSubmit(); diff --git a/src/services/lmStudioService.ts b/src/services/lmStudioService.ts index d5e0ed0..d5e35ff 100644 --- a/src/services/lmStudioService.ts +++ b/src/services/lmStudioService.ts @@ -1,4 +1,6 @@ -import { Editor, Notice } from 'obsidian'; +import type { Editor} from 'obsidian'; +import { Notice } from 'obsidian'; +import type { PromptsService } from './promptsService'; export interface LMStudioOptions { max_tokens?: number; @@ -10,13 +12,35 @@ export interface LMStudioOptions { export interface LMStudioSettings { lmStudioEndpoint: string; - promptsService?: any; // Will be PromptsService type + promptsService?: PromptsService; requestTemplate?: string; } +interface ChatMessage { + role: 'system' | 'user' | 'assistant'; + content: string; +} + +interface LMStudioPayload { + model: string; + messages: ChatMessage[]; + stream: boolean; + max_tokens: number; + temperature: number; + top_p: number; +} + +interface LMStudioStreamChunk { + choices?: Array<{ delta?: { content?: string } }>; +} + +interface LMStudioResponse { + choices?: Array<{ message?: { content?: string } }>; +} + export class LMStudioService { private settings: LMStudioSettings; - private promptsService: any; + private promptsService: PromptsService | undefined; constructor(settings: LMStudioSettings) { this.settings = settings; @@ -79,37 +103,34 @@ export class LMStudioService { console.log('Response cursor position:', responseCursor); try { - const messages: any[] = []; - - // Add system message if provided + const messages: ChatMessage[] = []; + if (options?.system_prompt) { messages.push({ role: 'system', content: options.system_prompt }); } - - // Add user query + messages.push({ role: 'user', content: query }); - - // Use template if available, otherwise construct payload manually - let payload: any; + + let payload: LMStudioPayload; if (this.settings.requestTemplate) { try { const processedTemplate = this.promptsService?.processTemplate(this.settings.requestTemplate) || this.settings.requestTemplate; - - // Strip JavaScript-style comments that would break JSON parsing + const cleanedTemplate = processedTemplate - .replace(/\/\*[\s\S]*?\*\//g, '') // Remove /* */ comments - .replace(/\/\/.*$/gm, '') // Remove // comments - .replace(/^\s*$/gm, '') // Remove empty lines + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/\/\/.*$/gm, '') + .replace(/^\s*$/gm, '') .trim(); - - payload = JSON.parse(cleanedTemplate); - // Override with current parameters - payload.model = model; - payload.messages = messages; - payload.stream = stream; - payload.max_tokens = options?.max_tokens ?? 2048; - payload.temperature = options?.temperature ?? 0.7; - payload.top_p = options?.top_p ?? 0.9; + + JSON.parse(cleanedTemplate); + payload = { + model, + messages, + stream, + max_tokens: options?.max_tokens ?? 2048, + temperature: options?.temperature ?? 0.7, + top_p: options?.top_p ?? 0.9, + }; } catch (error) { console.warn('Failed to parse request template, using default payload:', error); payload = { @@ -118,7 +139,7 @@ export class LMStudioService { stream, max_tokens: options?.max_tokens ?? 2048, temperature: options?.temperature ?? 0.7, - top_p: options?.top_p ?? 0.9 + top_p: options?.top_p ?? 0.9, }; } } else { @@ -128,7 +149,7 @@ export class LMStudioService { stream, max_tokens: options?.max_tokens ?? 2048, temperature: options?.temperature ?? 0.7, - top_p: options?.top_p ?? 0.9 + top_p: options?.top_p ?? 0.9, }; } @@ -145,7 +166,7 @@ export class LMStudioService { throw new Error(`HTTP error! status: ${response.status}`); } - let finalCursor = responseCursor; + const finalCursor = responseCursor; if (stream) { await this.handleStreamingResponse(response, editor, responseCursor, options); @@ -192,9 +213,9 @@ export class LMStudioService { if (data === '[DONE]') continue; try { - const parsed = JSON.parse(data); + const parsed = JSON.parse(data) as LMStudioStreamChunk; if (parsed.choices?.[0]?.delta?.content) { - let content = parsed.choices[0].delta.content; + let content: string = parsed.choices[0].delta.content; // Process images if enabled if (options?.return_images) { @@ -231,8 +252,8 @@ export class LMStudioService { responseCursor: { line: number; ch: number }, options?: LMStudioOptions ): Promise { - const data = await response.json(); - const content = data.choices?.[0]?.message?.content || 'No response received'; + const data = (await response.json()) as LMStudioResponse; + const content: string = data.choices?.[0]?.message?.content || 'No response received'; // Process images if enabled let processedContent = content; diff --git a/src/services/perplexicaService.ts b/src/services/perplexicaService.ts index a303459..c2adad6 100644 --- a/src/services/perplexicaService.ts +++ b/src/services/perplexicaService.ts @@ -1,10 +1,12 @@ -import { Editor, Notice } from 'obsidian'; +import type { Editor} from 'obsidian'; +import { Notice } from 'obsidian'; +import type { PromptsService } from './promptsService'; export interface PerplexicaSettings { perplexicaEndpoint: string; localLLMPath: string; defaultModel: string; - promptsService?: any; // Will be PromptsService type + promptsService?: PromptsService; requestTemplate?: string; } @@ -12,9 +14,37 @@ export interface PerplexicaOptions { return_images?: boolean; } +interface PerplexicaModelRef { + provider: string; + name: string; +} + +interface PerplexicaHistoryEntry { + role: string; + content: string; +} + +interface PerplexicaPayload { + chatModel: PerplexicaModelRef; + embeddingModel: PerplexicaModelRef; + optimizationMode: string; + focusMode: string; + query: string; + history: PerplexicaHistoryEntry[]; + systemInstructions: string; + stream: boolean; + maxTokens?: number; + temperature?: number; +} + +interface PerplexicaStreamEvent { + type?: string; + data?: string; +} + export class PerplexicaService { private settings: PerplexicaSettings; - private promptsService: any; + private promptsService: PromptsService | undefined; constructor(settings: PerplexicaSettings) { this.settings = settings; @@ -73,40 +103,28 @@ export class PerplexicaService { for (const endpoint of endpoints) { try { - // Use template if available, otherwise construct payload manually - let payload: any; + let payload: PerplexicaPayload; if (this.settings.requestTemplate) { try { const processedTemplate = this.promptsService?.processTemplate(this.settings.requestTemplate) || this.settings.requestTemplate; - - // Strip JavaScript-style comments that would break JSON parsing + const cleanedTemplate = processedTemplate - .replace(/\/\*[\s\S]*?\*\//g, '') // Remove /* */ comments - .replace(/\/\/.*$/gm, '') // Remove // comments - .replace(/^\s*$/gm, '') // Remove empty lines + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/\/\/.*$/gm, '') + .replace(/^\s*$/gm, '') .trim(); - - payload = JSON.parse(cleanedTemplate); - // Override with current parameters - payload.chatModel = { - provider: "ollama", - name: this.settings.defaultModel + + JSON.parse(cleanedTemplate); + payload = { + chatModel: { provider: "ollama", name: this.settings.defaultModel }, + embeddingModel: { provider: "ollama", name: this.settings.defaultModel }, + optimizationMode, + focusMode, + query, + history: [{ role: "user", content: query }], + systemInstructions: this.promptsService?.getPerplexicaSystemPrompt() || "You are a helpful AI assistant. Provide clear, concise, and accurate information.", + stream, }; - payload.embeddingModel = { - provider: "ollama", - name: this.settings.defaultModel - }; - payload.optimizationMode = optimizationMode; - payload.focusMode = focusMode; - payload.query = query; - payload.history = [ - { - role: "user", - content: query - } - ]; - payload.systemInstructions = this.promptsService?.getPerplexicaSystemPrompt() || "You are a helpful AI assistant. Provide clear, concise, and accurate information."; - payload.stream = stream; } catch (error) { console.warn('Failed to parse request template, using default payload:', error); payload = { @@ -211,31 +229,27 @@ export class PerplexicaService { for (const line of lines) { try { - const parsed = JSON.parse(line); - if (parsed.type === 'response' && parsed.data) { - let content = parsed.data; - - // Process images if enabled - if (options?.return_images) { - content = this.processContentWithImages(content); - } - - editor.replaceRange(content, currentPos); - // Update cursor position - const lines = content.split('\n'); - if (lines.length === 1) { - currentPos = { line: currentPos.line, ch: currentPos.ch + content.length }; - } else { - currentPos = { - line: currentPos.line + lines.length - 1, - ch: lines[lines.length - 1]?.length || 0 - }; - } - // Scroll to follow the new content - editor.scrollIntoView({ from: currentPos, to: currentPos }, true); - // Small delay to make scrolling smoother - await new Promise(resolve => setTimeout(resolve, 10)); + const parsed = JSON.parse(line) as PerplexicaStreamEvent; + if (parsed.type === 'response' && typeof parsed.data === 'string') { + let content: string = parsed.data; + + if (options?.return_images) { + content = this.processContentWithImages(content); } + + editor.replaceRange(content, currentPos); + const splitLines = content.split('\n'); + if (splitLines.length === 1) { + currentPos = { line: currentPos.line, ch: currentPos.ch + content.length }; + } else { + currentPos = { + line: currentPos.line + splitLines.length - 1, + ch: splitLines[splitLines.length - 1]?.length || 0 + }; + } + editor.scrollIntoView({ from: currentPos, to: currentPos }, true); + await new Promise(resolve => setTimeout(resolve, 10)); + } } catch (e) { // Ignore JSON parse errors } diff --git a/src/services/perplexityService.ts b/src/services/perplexityService.ts index f60c311..b7d71ee 100644 --- a/src/services/perplexityService.ts +++ b/src/services/perplexityService.ts @@ -1,4 +1,6 @@ -import { Editor, Notice, request } from 'obsidian'; +import type { Editor} from 'obsidian'; +import { Notice, request } from 'obsidian'; +import type { PromptsService } from './promptsService'; import { formatCitationDate, getMostRecentDate, formatPublicationInfo } from '../utils/formatDate'; export interface PerplexityOptions { @@ -11,14 +13,62 @@ export interface PerplexityOptions { export interface PerplexitySettings { perplexityApiKey: string; perplexityEndpoint: string; - promptsService?: any; // Will be PromptsService type + promptsService?: PromptsService; requestTemplate?: string; headerPosition?: 'top' | 'bottom'; } +export interface PerplexityImage { + image_url?: string; + origin_url?: string; +} + +export interface PerplexitySource { + title?: string; + url?: string; + date?: string; + last_updated?: string; +} + +interface PerplexityMessage { + role: string; + content: string; +} + +interface PerplexityPayload { + model: string; + messages: PerplexityMessage[]; + stream: boolean; + return_citations: boolean; + return_images: boolean; + return_related_questions: boolean; + search_recency_filter?: string; +} + +interface PerplexityChoice { + message?: { content?: string }; + delta?: { content?: string }; + finish_reason?: string; +} + +interface PerplexityResponse { + choices?: PerplexityChoice[]; + images?: PerplexityImage[]; + search_results?: PerplexitySource[]; + citations?: (string | PerplexitySource)[]; +} + +interface PerplexityStreamChunk { + choices?: PerplexityChoice[]; + images?: PerplexityImage[]; + search_results?: PerplexitySource[]; + citations?: (string | PerplexitySource)[]; +} + export class PerplexityService { private settings: PerplexitySettings; - private promptsService: any; + private promptsService: PromptsService | undefined; + private loadingInterval: ReturnType | null = null; constructor(settings: PerplexitySettings) { this.settings = settings; @@ -48,7 +98,7 @@ export class PerplexityService { return 'month'; } - private processContentWithImages(content: string, images: any[]): string { + private processContentWithImages(content: string, images: PerplexityImage[]): string { if (!images || images.length === 0) return content; console.log(`🖼️ Processing ${images.length} images for content replacement`); @@ -221,15 +271,14 @@ export class PerplexityService { } private clearLoadingAnimation(): void { - // Clear the animation interval if it exists - if ((this as any).loadingInterval) { + if (this.loadingInterval) { console.log('🛑 Clearing loading animation interval'); - clearInterval((this as any).loadingInterval); - (this as any).loadingInterval = null; + clearInterval(this.loadingInterval); + this.loadingInterval = null; } } - private addCitations(editor: Editor, sources: any[]): void { + private addCitations(editor: Editor, sources: (string | PerplexitySource)[]): void { if (!sources || sources.length === 0) return; console.log(`📚 Processing ${sources.length} sources for citations`); @@ -393,79 +442,65 @@ export class PerplexityService { try { const convertedFilter = this.convertRecencyFilter(options?.search_recency_filter ?? "month"); - // Use template if available, otherwise construct payload manually - let payload: any; + let payload: PerplexityPayload; if (this.settings.requestTemplate) { try { const processedTemplate = this.promptsService?.processTemplate(this.settings.requestTemplate) || this.settings.requestTemplate; - - // Strip JavaScript-style comments that would break JSON parsing + let cleanedTemplate = processedTemplate - .replace(/\/\*[\s\S]*?\*\//g, '') // Remove /* */ comments - .replace(/\/\/.*$/gm, '') // Remove // comments - .replace(/^\s*$/gm, '') // Remove empty lines + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/\/\/.*$/gm, '') + .replace(/^\s*$/gm, '') .trim(); - - // Check if template contains JavaScript code instead of JSON + if (cleanedTemplate.includes('const ') || cleanedTemplate.includes('fetch(') || cleanedTemplate.includes('await ')) { console.warn('⚠️ Template contains JavaScript code, not JSON. Extracting payload object...'); - - // Try to extract JSON payload from JavaScript code + const payloadMatch = cleanedTemplate.match(/payload\s*=\s*({[\s\S]*?});/); if (payloadMatch) { let jsObject = payloadMatch[1]; console.log('🔍 Extracted payload from JavaScript:', jsObject); - - // Convert JavaScript object syntax to valid JSON - // Replace unquoted property names with quoted ones - jsObject = jsObject.replace(/(\w+):/g, '"$1":'); - // Fix single quotes to double quotes - jsObject = jsObject.replace(/'/g, '"'); - - cleanedTemplate = jsObject; + + jsObject = jsObject?.replace(/(\w+):/g, '"$1":').replace(/'/g, '"'); + + cleanedTemplate = jsObject ?? cleanedTemplate; } else { throw new Error('Template contains JavaScript code but no valid payload object found. Please use JSON format only.'); } } - + console.log('🧹 Final cleaned template:', cleanedTemplate); - payload = JSON.parse(cleanedTemplate); - // Override with current query and options - payload.model = model; - payload.messages = [ - { role: 'user', content: query } - ]; - payload.stream = useStreaming; - payload.return_citations = options?.return_citations ?? true; - payload.return_images = options?.return_images ?? true; - payload.return_related_questions = options?.return_related_questions ?? false; + JSON.parse(cleanedTemplate); + payload = { + model, + messages: [{ role: 'user', content: query }], + stream: useStreaming, + return_citations: options?.return_citations ?? true, + return_images: options?.return_images ?? true, + return_related_questions: options?.return_related_questions ?? false, + }; } catch (error) { console.warn('Failed to parse request template, using default payload:', error); payload = { model, - messages: [ - { role: 'user', content: query } - ], + messages: [{ role: 'user', content: query }], stream: useStreaming, return_citations: options?.return_citations ?? true, return_images: options?.return_images ?? true, - return_related_questions: options?.return_related_questions ?? false + return_related_questions: options?.return_related_questions ?? false, }; } } else { payload = { model, - messages: [ - { role: 'user', content: query } - ], + messages: [{ role: 'user', content: query }], stream: useStreaming, return_citations: options?.return_citations ?? true, return_images: options?.return_images ?? true, - return_related_questions: options?.return_related_questions ?? false + return_related_questions: options?.return_related_questions ?? false, }; } - - // Only include search_recency_filter if we have a filter value + if (convertedFilter !== undefined) { payload.search_recency_filter = convertedFilter; } @@ -516,33 +551,25 @@ export class PerplexityService { }); console.log('✅ Non-streaming response received'); - // Parse the response - const data = JSON.parse(response); + const data = JSON.parse(response) as PerplexityResponse; console.log('📊 Response data structure:', Object.keys(data)); - // Process the response if (data.choices && data.choices.length > 0) { - let content = data.choices[0].message.content; - - // Process think blocks first + let content = data.choices[0]?.message?.content ?? ''; + content = this.processThinkBlocks(content); - - // Process images if available + if (options?.return_images && data.images && data.images.length > 0) { console.log('🖼️ Processing images in non-streaming response:', data.images.length, 'images found'); content = this.processContentWithImages(content, data.images); } - - // Insert the response at the cursor position + editor.replaceRange(content, responseCursor); - - // Add citations if available + if (options?.return_citations) { if (data.search_results && data.search_results.length > 0) { - // Use search_results for detailed info (preferred for Perplexity) this.addCitations(editor, data.search_results); } else if (data.citations && data.citations.length > 0) { - // Fallback to citations array (could be URLs or other format) this.addCitations(editor, data.citations); } } @@ -605,43 +632,39 @@ export class PerplexityService { console.log(`🔄 Starting streaming response handler [${requestId || 'unknown'}]`); let buffer = ''; - let currentPos = { ...responseCursor }; - let finalResponseData: any = null; - + const currentPos = { ...responseCursor }; + let finalResponseData: PerplexityStreamChunk | null = null; + console.log(`📍 Initial currentPos:`, currentPos); - + try { while (true) { const { done, value } = await reader.read(); if (done) break; - + const chunk = new TextDecoder().decode(value, { stream: true }); buffer += chunk; - - // Process complete lines from buffer + const lines = buffer.split('\n'); - buffer = lines.pop() || ''; // Keep incomplete line in buffer - + buffer = lines.pop() || ''; + for (const line of lines) { if (line.trim() === '') continue; - + try { if (line.startsWith('data: ')) { const data = line.replace('data: ', '').trim(); if (data === '[DONE]') continue; - - const parsed = JSON.parse(data); - - // Capture the latest metadata across chunks. Perplexity typically - // sends citations/search_results in earlier chunks; the final chunk - // often carries only finish_reason. Merge so nothing is lost. + + const parsed = JSON.parse(data) as PerplexityStreamChunk; + if (parsed.citations || parsed.images || parsed.search_results) { finalResponseData = { ...(finalResponseData || {}), ...parsed, }; } - + if (parsed.choices?.[0]?.delta?.content) { const content = parsed.choices[0].delta.content; if (content) { @@ -654,13 +677,12 @@ export class PerplexityService { // console.log(`📝 Inserting content at position:`, currentPos, `Content:`, content.substring(0, 50) + '...'); editor.replaceRange(content, currentPos); - // Update cursor position after insertion const contentLines = content.split('\n'); if (contentLines.length === 1) { currentPos.ch += content.length; } else { currentPos.line += contentLines.length - 1; - currentPos.ch = contentLines[contentLines.length - 1].length; + currentPos.ch = contentLines[contentLines.length - 1]?.length ?? 0; } // Scroll to follow the new content editor.scrollIntoView({ from: currentPos, to: currentPos }, true); @@ -704,7 +726,7 @@ export class PerplexityService { } private async processStreamingMetadata( - finalResponseData: any, + finalResponseData: PerplexityStreamChunk, editor: Editor, headerText?: string ): Promise { @@ -758,7 +780,7 @@ export class PerplexityService { // Insert images inline after the query header let imagesSection = '\n\n'; - finalResponseData.images.forEach((image: any, index: number) => { + finalResponseData.images.forEach((image: PerplexityImage, index: number) => { if (image.image_url) { imagesSection += `![Image ${index + 1}](${image.image_url})\n\n`; } @@ -774,7 +796,7 @@ export class PerplexityService { } else { // Fallback: add images at the end if no markers found let imagesSection = '\n\n## Images\n\n'; - finalResponseData.images.forEach((image: any, index: number) => { + finalResponseData.images.forEach((image: PerplexityImage, index: number) => { if (image.image_url) { imagesSection += `![Image ${index + 1}](${image.image_url})\n`; if (image.origin_url) { diff --git a/src/types/obsidian.d.ts b/src/types/obsidian.d.ts index ee47d10..4362652 100644 --- a/src/types/obsidian.d.ts +++ b/src/types/obsidian.d.ts @@ -1,8 +1,12 @@ -import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian'; +import type { TFile } from 'obsidian'; declare module 'obsidian' { + interface CommandsApi { + commands: Record; + } + interface App { - commands: any; + commands: CommandsApi; } interface Editor { diff --git a/src/utils/coerce.ts b/src/utils/coerce.ts new file mode 100644 index 0000000..cea374f --- /dev/null +++ b/src/utils/coerce.ts @@ -0,0 +1,33 @@ +export function asString(v: unknown): string | undefined { + if (typeof v === 'string') return v; + if (typeof v === 'number' || typeof v === 'boolean') return String(v); + return undefined; +} + +export function asNumber(v: unknown): number | undefined { + if (typeof v === 'number' && Number.isFinite(v)) return v; + if (typeof v === 'string') { + const n = Number(v); + return Number.isFinite(n) ? n : undefined; + } + return undefined; +} + +export function asStringArray(v: unknown): string[] { + if (Array.isArray(v)) { + return v.map(asString).filter((s): s is string => s !== undefined); + } + const single = asString(v); + return single === undefined ? [] : [single]; +} + +export function asDate(v: unknown): string | undefined { + const s = asString(v); + if (s === undefined) return undefined; + const d = new Date(s); + return Number.isNaN(d.getTime()) ? undefined : d.toISOString(); +} + +export function isRecord(v: unknown): v is Record { + return typeof v === 'object' && v !== null && !Array.isArray(v); +} diff --git a/src/utils/formatDate.ts b/src/utils/formatDate.ts index cae7701..b23db2d 100644 --- a/src/utils/formatDate.ts +++ b/src/utils/formatDate.ts @@ -1,29 +1,24 @@ -/** - * Date formatting utilities for the Perplexed plugin - */ +import { asString, isRecord } from './coerce'; + +export interface DateBearingSource { + date?: unknown; + last_updated?: unknown; +} -/** - * Formats a date string into the format: "YYYY, MMM DD" - * Examples: "2024, Dec 13", "2025, Jan 05" - * - * @param dateString - Date string in various formats (ISO, YYYY-MM-DD, etc.) - * @returns Formatted date string or empty string if invalid - */ export function formatCitationDate(dateString: string): string { if (!dateString) return ''; - + try { const date = new Date(dateString); - - // Check if date is valid + if (isNaN(date.getTime())) { return ''; } - + const year = date.getFullYear(); const month = date.toLocaleDateString('en-US', { month: 'short' }); const day = date.getDate().toString().padStart(2, '0'); - + return `${year}, ${month} ${day}`; } catch (error) { console.warn('Failed to format date:', dateString, error); @@ -31,39 +26,20 @@ export function formatCitationDate(dateString: string): string { } } -/** - * Gets the most recent date from available date fields - * Prioritizes last_updated over date - * - * @param source - Source object with potential date fields - * @returns Most recent date string or empty string if none available - */ -export function getMostRecentDate(source: any): string { - if (source.last_updated) { - return source.last_updated; - } - if (source.date) { - return source.date; - } - return ''; +export function getMostRecentDate(source: unknown): string { + if (!isRecord(source)) return ''; + return asString(source.last_updated) ?? asString(source.date) ?? ''; } -/** - * Creates the publication info string for citations - * Format: "Published: YYYY-MM-DD | Updated: YYYY-MM-DD" - * - * @param source - Source object with potential date fields - * @returns Publication info string or empty string if no dates available - */ -export function formatPublicationInfo(source: any): string { - const dateInfo = []; - - if (source.date) { - dateInfo.push(`Published: ${source.date}`); - } - if (source.last_updated) { - dateInfo.push(`Updated: ${source.last_updated}`); - } - +export function formatPublicationInfo(source: unknown): string { + if (!isRecord(source)) return ''; + const dateInfo: string[] = []; + + const date = asString(source.date); + if (date) dateInfo.push(`Published: ${date}`); + + const lastUpdated = asString(source.last_updated); + if (lastUpdated) dateInfo.push(`Updated: ${lastUpdated}`); + return dateInfo.join(' | '); } diff --git a/src/utils/logger.ts b/src/utils/logger.ts index d70b4ad..329a074 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,11 +1,12 @@ import { Notice } from 'obsidian'; -import { TFile, Vault } from 'obsidian'; +import type { Vault } from 'obsidian'; +import { TFile } from 'obsidian'; interface LogEntry { timestamp: string; level: 'error' | 'warn' | 'info' | 'debug'; message: string; - details?: any; + details?: unknown; stack?: string; } @@ -78,7 +79,7 @@ export class FileLogger { } } - private addEntry(level: LogEntry['level'], message: string, details?: any): void { + private addEntry(level: LogEntry['level'], message: string, details?: unknown): void { const entry: LogEntry = { timestamp: new Date().toISOString(), level, @@ -106,19 +107,19 @@ export class FileLogger { logMethod(`[${entry.timestamp}] [${level.toUpperCase()}] ${message}`, details || ''); } - error(message: string, details?: any): void { + error(message: string, details?: unknown): void { this.addEntry('error', message, details); } - warn(message: string, details?: any): void { + warn(message: string, details?: unknown): void { this.addEntry('warn', message, details); } - info(message: string, details?: any): void { + info(message: string, details?: unknown): void { this.addEntry('info', message, details); } - debug(message: string, details?: any): void { + debug(message: string, details?: unknown): void { this.addEntry('debug', message, details); } diff --git a/versions.json b/versions.json index 0177a48..9be1f3d 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "0.0.1.0": "0.15.0" + "0.1.0": "0.15.0" }