@ham2k/extension-sdk 0.12.15 → 1.12.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/README.md CHANGED
@@ -53,6 +53,13 @@ offline and describe the version installed here rather than whatever the
53
53
  repository says today. If an agent is writing your extension, point it at
54
54
  `node_modules/@ham2k/extension-sdk/AGENTS.md` — `h2kext-init` does that for you.
55
55
 
56
+ ## Versions
57
+
58
+ The minor version is the extension API: SDK 1.12 describes API 12, and
59
+ exports it as `API_VERSION`. `@ham2k/extension-tools` follows the same rule,
60
+ so a `^1.12.0` range says the extension needs API 12 and admits every later
61
+ one. A major version is a change that breaks an extension already written.
62
+
56
63
  ## Export types and settings
57
64
 
58
65
  Export hooks can register shared types with `getExportTypes()`. Use
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- // @ham2k/extension-sdk 0.12.15
1
+ // @ham2k/extension-sdk 1.12.0
2
2
  /** Experimental v1 panel scene contract. All coordinates are in the scene's viewBox. */
3
3
  export interface PanelScene {
4
4
  version: 1;
@@ -1360,6 +1360,9 @@ export interface CommandAction {
1360
1360
  findInLog?: {
1361
1361
  query?: string;
1362
1362
  };
1363
+ navigate?: {
1364
+ to: string;
1365
+ };
1363
1366
  setCallField?: {
1364
1367
  text: string;
1365
1368
  };
@@ -1456,6 +1459,8 @@ export interface CommandCatalogEntry {
1456
1459
  params?: string;
1457
1460
  category?: string;
1458
1461
  needsOperation?: boolean;
1462
+ needsTestData?: boolean;
1463
+ keywords?: string[];
1459
1464
  expectsParams?: boolean;
1460
1465
  suggest?: boolean;
1461
1466
  }
@@ -2614,6 +2619,14 @@ export interface TemplateContextArgs {
2614
2619
  }
2615
2620
  export declare function templateContext(args: TemplateContextArgs): Record<string, unknown>;
2616
2621
  export declare function isTestOperation(stationCall: JSONValue | undefined): boolean;
2622
+ /**
2623
+ * The extension API this SDK describes: the highest `api` a manifest written
2624
+ * against it can declare, and the one its types and host calls are for. The
2625
+ * SDK's and `@ham2k/extension-tools`' minor version is this number, so a
2626
+ * `^1.<api>.0` range names the oldest API an extension needs and admits every
2627
+ * later one.
2628
+ */
2629
+ export declare const API_VERSION = 12;
2617
2630
  export declare function defineExtension(def: ExtensionDefinition): void;
2618
2631
  export declare const hooks: {
2619
2632
  invokeAll(category: HookCategoryName, method: string, args: unknown, online?: boolean): Promise<{
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { base64ToBytes, bytesToBase64 } from "./base64.js";
2
2
  import { adoptExtensionTimers, clearInterval, clearTimeout, setInterval, setTimeout } from "./timers.js";
3
+ const API_VERSION = 12;
3
4
  export * from "./types.js";
4
5
  export * from "./hookCategories.js";
5
6
  export * from "./panelScene.js";
@@ -426,6 +427,7 @@ const host = {
426
427
  }
427
428
  };
428
429
  export {
430
+ API_VERSION,
429
431
  defineExtension,
430
432
  hooks,
431
433
  host
package/docs/hooks.md CHANGED
@@ -1170,10 +1170,10 @@ interface CommandHook {
1170
1170
  catalog?(ctx): Promise<CommandCatalogEntry[]>
1171
1171
  }
1172
1172
  // CommandInterpretation: {expectsParams?, mixedCase?, error?, describe?, confirm?, commands?: CommandAction[]}
1173
- // CommandCatalogEntry: {command, describe, params?, category?, needsOperation?, expectsParams?, suggest?}
1173
+ // CommandCatalogEntry: {command, describe, params?, category?, needsOperation?, needsTestData?, keywords?, expectsParams?, suggest?}
1174
1174
  // CommandAction (closed set): {setVfo?, setPower?, updateQso?, addQso?,
1175
1175
  // setKeyerSpeed?, showKeyer?, setEnterSends?, sendSpots?, autoRespot?, syncAll?, updateSettings?, updateSetting?, openSettings?,
1176
- // updateOperation?, markSegment?, findInLog?, setCallField?, setTime?, reloadDataFiles?, devNotice?, devStatus?,
1176
+ // updateOperation?, markSegment?, findInLog?, navigate?, setCallField?, setTime?, reloadDataFiles?, devNotice?, devStatus?,
1177
1177
  // devProgress?, devHammer?, devSplash?, devOnboard?, devTester?, devPopup?,
1178
1178
  // devTimeTravel?, toggleExperiment?}
1179
1179
  ```
@@ -1366,9 +1366,12 @@ Three things differ from `interpret`, all deliberate:
1366
1366
  - **Called once and cached**, not per keystroke — dropped when the runtime is
1367
1367
  replaced (an extension enabled/disabled) or the hook context is
1368
1368
  invalidated (locale change, since `describe` is localized by the hook).
1369
- - **Matching is host-side**, plain case-insensitive substring against
1370
- `command` and `describe`. Same rule `interpret`'s own matching follows: the
1371
- host never compiles extension-supplied patterns.
1369
+ - **Matching is host-side**, case-insensitive, at the start of a word in
1370
+ `command` or in `keywords` — never mid-word, and `describe` is never
1371
+ searched. Every word that should find a command belongs in its
1372
+ `keywords`: the aliases it accepts when typed, and plain words for what it
1373
+ does. Same rule `interpret`'s own matching follows: the host never
1374
+ compiles extension-supplied patterns.
1372
1375
  - **List commands, not variants.** `radio-commands` advertises one BAND
1373
1376
  entry, not fifteen; `call-notes` advertises `//`, not every key the user
1374
1377
  has defined.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ham2k/extension-sdk",
3
- "version": "0.12.15",
3
+ "version": "1.12.0",
4
4
  "description": "Write extensions for the Ham2K Logger: typed hook contracts and the host API",
5
5
  "keywords": [
6
6
  "ham2k",
@@ -4,9 +4,17 @@ The radio's panadapter and waterfall in a scene panel, with its span, reference
4
4
  level and sweep speed, a local contrast control, and click-to-tune. The host
5
5
  draws the sweeps in the `spectrum` control; they never reach the extension.
6
6
 
7
- Needs a radio whose CAT protocol carries its scope: an Icom IC-705/7300/7610 (at
8
- 115200 bps on a serial line), or a FlexRadio, which is mirrored through a DAX IQ
9
- channel.
7
+ Needs a supported radio:
8
+
9
+ - Icom IC-705/7300/7610, at 115200 bps when using a serial line.
10
+ - FlexRadio, mirrored through a DAX IQ channel.
11
+ - Elecraft K4 over Ethernet, with a separate local TCP spectrum connection on
12
+ port 9201 alongside CAT. It displays the main panadapter with local zoom,
13
+ reference and speed controls. Levels are displayed as received, without an
14
+ RF calibration claim.
15
+
16
+ The panel shows only the controls each radio supports; contrast, averaging,
17
+ frame rate and waterfall rows remain local.
10
18
 
11
19
  In this repository:
12
20
 
@@ -14,3 +22,8 @@ In this repository:
14
22
  node build.mjs
15
23
  node ../../tools/h2kext-pack.mjs build -o /tmp/k2hrc-panafall.h2kext
16
24
  ```
25
+
26
+ Samples are not included in the app's regular extension build. Install the
27
+ resulting `.h2kext` through **Settings → Extensions & Features → Install from
28
+ file…**, then add its **Panadapter** panel to an operation. The host must have
29
+ the matching radio support.
@@ -42,15 +42,25 @@ const spanLabel = (hz: number) => `±${hz >= 1000 ? `${hz / 1000} kHz` : `${hz}
42
42
  export function scene(radio: PanelRadioState | null, display: Display, step = "100"): PanelScene {
43
43
  const spectrum: PanelRadioSpectrum | null | undefined = radio?.spectrum
44
44
  const knobs = spectrum && !spectrum.problem
45
+ const hasSpan = knobs && spectrum.spansHz.length > 0
46
+ const hasSpeed = knobs && spectrum.speeds.length > 0
47
+ const hasRef =
48
+ knobs &&
49
+ Number.isFinite(spectrum.refMinDb) &&
50
+ Number.isFinite(spectrum.refMaxDb) &&
51
+ Number.isFinite(spectrum.refStepDb) &&
52
+ spectrum.refMinDb < spectrum.refMaxDb &&
53
+ spectrum.refStepDb > 0
54
+ const ref = hasRef ? Math.max(spectrum.refMinDb, Math.min(spectrum.refMaxDb, spectrum.refLevelDb ?? spectrum.refMinDb)) : 0
45
55
  const band = passband(radio?.mode)
46
56
  return {
47
57
  version: 1,
48
58
  width: 600,
49
59
  height: 24,
50
- values: { floor: 0, average: 40, fps: 15, rows: 10, hoverHz: 0, hoverDb: 0, ref: spectrum?.refLevelDb ?? 0 },
60
+ values: { floor: 0, average: 40, fps: 15, rows: 10, hoverHz: 0, hoverDb: 0, ref },
51
61
  strings: {
52
- span: spectrum?.spanHz != null ? String(spectrum.spanHz) : "",
53
- speed: spectrum?.speed ?? "",
62
+ span: hasSpan && spectrum.spanHz != null && spectrum.spansHz.includes(spectrum.spanHz) ? String(spectrum.spanHz) : "",
63
+ speed: hasSpeed && spectrum.speed != null && spectrum.speeds.includes(spectrum.speed) ? spectrum.speed : "",
54
64
  display,
55
65
  step,
56
66
  },
@@ -65,7 +75,7 @@ export function scene(radio: PanelRadioState | null, display: Display, step = "1
65
75
  },
66
76
  ],
67
77
  controls: [
68
- ...(knobs
78
+ ...(hasSpan
69
79
  ? [
70
80
  {
71
81
  id: "span",
@@ -75,6 +85,10 @@ export function scene(radio: PanelRadioState | null, display: Display, step = "1
75
85
  event: "span",
76
86
  options: spectrum.spansHz.map((hz) => ({ label: spanLabel(hz), value: String(hz) })),
77
87
  },
88
+ ]
89
+ : []),
90
+ ...(hasRef
91
+ ? [
78
92
  {
79
93
  id: "ref",
80
94
  label: "Reference level",
@@ -85,6 +99,10 @@ export function scene(radio: PanelRadioState | null, display: Display, step = "1
85
99
  max: spectrum.refMaxDb,
86
100
  step: spectrum.refStepDb,
87
101
  },
102
+ ]
103
+ : []),
104
+ ...(hasSpeed
105
+ ? [
88
106
  {
89
107
  id: "speed",
90
108
  label: "Speed",
@@ -100,7 +118,7 @@ export function scene(radio: PanelRadioState | null, display: Display, step = "1
100
118
  { id: "fps", label: "Panadapter FPS", kind: "nativeSlider" as const, value: "fps", min: 1, max: 30, step: 1 },
101
119
  { id: "rows", label: "Waterfall rows per second", kind: "nativeSlider" as const, value: "rows", min: 1, max: 30, step: 1 },
102
120
  // A control the layout does not place fails the scene: Ref's label goes with its slider.
103
- ...[...(knobs ? ["Ref"] : []), "Contrast", "Avg", "FPS", "Rows"].map((text) => ({
121
+ ...[...(hasRef ? ["Ref"] : []), "Contrast", "Avg", "FPS", "Rows"].map((text) => ({
104
122
  id: `label-${text}`,
105
123
  label: text,
106
124
  kind: "nativeText" as const,
@@ -179,7 +197,9 @@ export function scene(radio: PanelRadioState | null, display: Display, step = "1
179
197
  column: [
180
198
  {
181
199
  wrap: [
182
- ...(knobs ? [{ control: "span" }, labeled("Ref", "ref"), { control: "speed" }] : []),
200
+ ...(hasSpan ? [{ control: "span" }] : []),
201
+ ...(hasRef ? [labeled("Ref", "ref")] : []),
202
+ ...(hasSpeed ? [{ control: "speed" }] : []),
183
203
  labeled("Contrast", "floor"),
184
204
  labeled("Avg", "average"),
185
205
  labeled("FPS", "fps"),