Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 6 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"test:fast": "vitest run --project unit"
},
"dependencies": {
"@elgato/streamdeck": "2.1.2"
"@elgato/streamdeck": "3.0.0"
},
"devDependencies": {
"@elgato/cli": "1.10.0",
Expand Down
3 changes: 2 additions & 1 deletion src/actions/agent-navigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { openNewChat, openThread } from "../lib/automation.js";
import { codexStore } from "../lib/codex-store.js";
import { statusIndicator } from "../lib/visuals.js";
import { renderFeedback } from "../lib/render-cache.js";
import { showActionAlert } from "../lib/streamdeck-alert.js";

type NavigatorSettings = {
index?: number;
Expand Down Expand Up @@ -70,7 +71,7 @@ export class AgentNavigatorAction extends SingletonAction<NavigatorSettings> {
}
if (actionInstance.isDial()) await this.draw(actionInstance);
} catch {
await actionInstance.showAlert();
await showActionAlert(actionInstance);
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/actions/agent-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ export class AgentStatusAction extends SingletonAction<AgentSettings> {
receivedSettings?: AgentSettings,
): Promise<void> {
if (!actionInstance.isKey()) return;
const settings =
receivedSettings ?? (await actionInstance.getSettings<AgentSettings>());
const settings = receivedSettings ?? (await actionInstance.getSettings());
const slot = this.slotFrom(
settings,
(actionInstance.coordinates?.column ?? 0) + 1,
Expand Down
3 changes: 2 additions & 1 deletion src/actions/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
import { codexStore } from "../lib/codex-store.js";
import { pickerFailureLabel } from "../lib/codex-ui-control.js";
import { renderFeedback, renderKey } from "../lib/render-cache.js";
import { showActionAlert } from "../lib/streamdeck-alert.js";

type CommandSettings = {
commandId?: string;
Expand Down Expand Up @@ -179,7 +180,7 @@ export class CommandAction extends SingletonAction<CommandSettings> {
title: pickerFailureLabel(error),
});
}
await actionInstance.showAlert();
await showActionAlert(actionInstance);
}
}

Expand Down
10 changes: 6 additions & 4 deletions src/actions/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@ import { toggleContextView, type ContextView } from "../lib/context.js";
import { contextKeySvg, svgDataUrl } from "../lib/visuals.js";
import { renderKey } from "../lib/render-cache.js";

type ContextSettings = Record<string, never>;

@action({ UUID: "com.todd.streamdeckcodex.context" })
export class ContextAction extends SingletonAction {
export class ContextAction extends SingletonAction<ContextSettings> {
readonly #mode = new Map<string, ContextView>();

async onWillAppear(event: WillAppearEvent): Promise<void> {
async onWillAppear(event: WillAppearEvent<ContextSettings>): Promise<void> {
if (!event.action.isKey()) return;
this.#mode.set(event.action.id, "remaining");
await this.draw(event.action);
}

async onKeyDown(event: KeyDownEvent): Promise<void> {
async onKeyDown(event: KeyDownEvent<ContextSettings>): Promise<void> {
const current = this.#mode.get(event.action.id) ?? "remaining";
this.#mode.set(event.action.id, toggleContextView(current));
await this.draw(event.action);
Expand All @@ -34,7 +36,7 @@ export class ContextAction extends SingletonAction {
);
}

private async draw(actionInstance: Action): Promise<void> {
private async draw(actionInstance: Action<ContextSettings>): Promise<void> {
if (!actionInstance.isKey()) return;
await renderKey(
actionInstance,
Expand Down
10 changes: 6 additions & 4 deletions src/actions/health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@ import {
import { renderKey } from "../lib/render-cache.js";
import { healthKeySvg, svgDataUrl } from "../lib/visuals.js";

type HealthSettings = Record<string, never>;

@action({ UUID: "com.todd.streamdeckcodex.health" })
export class HealthAction extends SingletonAction {
export class HealthAction extends SingletonAction<HealthSettings> {
readonly #selection = new Map<string, number>();

async onWillAppear(event: WillAppearEvent): Promise<void> {
async onWillAppear(event: WillAppearEvent<HealthSettings>): Promise<void> {
if (!event.action.isKey()) return;
this.#selection.set(event.action.id, 0);
await this.draw(event.action);
}

async onKeyDown(event: KeyDownEvent): Promise<void> {
async onKeyDown(event: KeyDownEvent<HealthSettings>): Promise<void> {
const next =
((this.#selection.get(event.action.id) ?? 0) + 1) %
HEALTH_COMPONENTS.length;
Expand All @@ -41,7 +43,7 @@ export class HealthAction extends SingletonAction {
);
}

private async draw(actionInstance: Action): Promise<void> {
private async draw(actionInstance: Action<HealthSettings>): Promise<void> {
if (!actionInstance.isKey()) return;
const snapshot = collectHealth(codexStore);
const component = HEALTH_COMPONENTS[
Expand Down
4 changes: 2 additions & 2 deletions src/actions/keycap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class KeycapAction extends SingletonAction<KeycapSettings> {
}

async onKeyDown(event: KeyDownEvent<KeycapSettings>): Promise<void> {
const settings = await event.action.getSettings<KeycapSettings>();
const settings = await event.action.getSettings();
const actionKind = settings.action ?? "info";
try {
if (actionKind === "new-chat") await openNewChat();
Expand Down Expand Up @@ -83,7 +83,7 @@ export class KeycapAction extends SingletonAction<KeycapSettings> {

private async draw(actionInstance: Action<KeycapSettings>): Promise<void> {
if (!actionInstance.isKey()) return;
const settings = await actionInstance.getSettings<KeycapSettings>();
const settings = await actionInstance.getSettings();
await renderKey(
actionInstance,
svgDataUrl(
Expand Down
5 changes: 3 additions & 2 deletions src/actions/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
type ModelDialState,
} from "../lib/model.js";
import { renderFeedback } from "../lib/render-cache.js";
import { showActionAlert } from "../lib/streamdeck-alert.js";
import { dialFailureFeedback } from "../lib/visuals.js";

type ModelSettings = {
Expand Down Expand Up @@ -115,7 +116,7 @@ export class ModelAction extends SingletonAction<ModelSettings> {
streamDeck.logger.warn(
`Ignored unavailable model ${current.selected || "(none)"}`,
);
await actionInstance.showAlert();
await showActionAlert(actionInstance);
return;
}

Expand Down Expand Up @@ -158,7 +159,7 @@ export class ModelAction extends SingletonAction<ModelSettings> {
dialFailureFeedback(pickerFailureLabel(error)),
);
}
await actionInstance.showAlert();
await showActionAlert(actionInstance);
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/actions/reasoning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
type ReasoningDialState,
} from "../lib/reasoning.js";
import { renderFeedback } from "../lib/render-cache.js";
import { showActionAlert } from "../lib/streamdeck-alert.js";
import { dialFailureFeedback } from "../lib/visuals.js";

type ReasoningSettings = {
Expand Down Expand Up @@ -121,7 +122,7 @@ export class ReasoningAction extends SingletonAction<ReasoningSettings> {
streamDeck.logger.warn(
`Ignored unsupported reasoning level ${current.selected} for ${snapshot.model ?? "current model"}`,
);
await actionInstance.showAlert();
await showActionAlert(actionInstance);
return;
}
await this.apply(context, actionInstance, current.selected);
Expand Down Expand Up @@ -176,7 +177,7 @@ export class ReasoningAction extends SingletonAction<ReasoningSettings> {
dialFailureFeedback(pickerFailureLabel(error)),
);
}
await actionInstance.showAlert();
await showActionAlert(actionInstance);
}
}

Expand Down
10 changes: 6 additions & 4 deletions src/actions/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@ import { toggleUsageView, type UsageView } from "../lib/usage.js";
import { svgDataUrl, usageKeySvg } from "../lib/visuals.js";
import { renderKey } from "../lib/render-cache.js";

type UsageSettings = Record<string, never>;

@action({ UUID: "com.todd.streamdeckcodex.usage" })
export class UsageAction extends SingletonAction {
export class UsageAction extends SingletonAction<UsageSettings> {
readonly #mode = new Map<string, UsageView>();

async onWillAppear(event: WillAppearEvent): Promise<void> {
async onWillAppear(event: WillAppearEvent<UsageSettings>): Promise<void> {
if (!event.action.isKey()) return;
this.#mode.set(event.action.id, "weekly");
await this.draw(event.action);
}

async onKeyDown(event: KeyDownEvent): Promise<void> {
async onKeyDown(event: KeyDownEvent<UsageSettings>): Promise<void> {
const current = this.#mode.get(event.action.id) ?? "weekly";
this.#mode.set(event.action.id, toggleUsageView(current));
await this.draw(event.action);
Expand All @@ -34,7 +36,7 @@ export class UsageAction extends SingletonAction {
);
}

private async draw(actionInstance: Action): Promise<void> {
private async draw(actionInstance: Action<UsageSettings>): Promise<void> {
if (!actionInstance.isKey()) return;
const snapshot = codexStore.usageSnapshotCached();
await renderKey(
Expand Down
3 changes: 2 additions & 1 deletion src/actions/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { launchWorkflow } from "../lib/automation.js";
import { codexStore } from "../lib/codex-store.js";
import { commandKeySvg, svgDataUrl } from "../lib/visuals.js";
import { renderFeedback, renderKey } from "../lib/render-cache.js";
import { showActionAlert } from "../lib/streamdeck-alert.js";
import {
workflowAt,
WORKFLOWS,
Expand Down Expand Up @@ -106,7 +107,7 @@ export class WorkflowAction extends SingletonAction<WorkflowSettings> {
);
if (actionInstance.isKey()) await actionInstance.showOk();
} catch {
await actionInstance.showAlert();
await showActionAlert(actionInstance);
}
}

Expand Down
16 changes: 16 additions & 0 deletions src/lib/streamdeck-alert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Action } from "@elgato/streamdeck";

type JsonObject = {
[key: string]: JsonValue;
};
type JsonValue =
boolean | number | string | null | undefined | JsonObject | JsonValue[];

/**
* SDK v3 keeps `showAlert` on keys and dials only; Neo Infobar actions omit it.
*/
export async function showActionAlert<T extends JsonObject>(
action: Action<T>,
): Promise<void> {
if (action.isKey() || action.isDial()) await action.showAlert();
}
7 changes: 5 additions & 2 deletions test/helpers/fake-streamdeck-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ export class FakeStreamDeckAction<
isDial(): boolean {
return this.kind === "dial";
}
async getSettings<U = T>(): Promise<U> {
isNeoInfobar(): boolean {
return false;
}
async getSettings(): Promise<T> {
this.calls.push({ method: "getSettings" });
return this.settings as unknown as U;
return this.settings;
}
async setSettings(value: T): Promise<void> {
this.settings = value;
Expand Down