@nighthawk.hq/macro-sdk 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/build/index.js ADDED
@@ -0,0 +1,49 @@
1
+ import { build } from 'esbuild';
2
+ import { readFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const HERE = dirname(fileURLToPath(import.meta.url));
7
+ const MANIFEST = JSON.parse(readFileSync(join(HERE, '..', 'runtime-manifest.json'), 'utf8'));
8
+ const SPECIFIER = '@nighthawk.hq/macro-sdk';
9
+ const MAX_BYTES = 256 * 1024;
10
+
11
+ function externalSdkPlugin() {
12
+ const shim =
13
+ 'const s = globalThis.__nhSdk;\n' +
14
+ "if (!s) throw new Error('Nighthawk SDK runtime not present in this realm');\n" +
15
+ MANIFEST.map((n) => `export const ${n} = s.${n};`).join('\n') +
16
+ '\n';
17
+ return {
18
+ name: 'nighthawk-sdk-external',
19
+ setup(b) {
20
+ b.onResolve({ filter: /^@nighthawk\.hq\/macro-sdk$/ }, () => ({
21
+ path: SPECIFIER,
22
+ namespace: 'nh-sdk',
23
+ }));
24
+ b.onLoad({ filter: /.*/, namespace: 'nh-sdk' }, () => ({ contents: shim, loader: 'js' }));
25
+ },
26
+ };
27
+ }
28
+
29
+ export async function buildMacro({ id, entry, outDir }) {
30
+ const outfile = join(outDir ?? 'dist', `${id}.js`);
31
+ await build({
32
+ entryPoints: [entry],
33
+ bundle: true,
34
+ format: 'iife',
35
+ target: 'es2022',
36
+ platform: 'neutral',
37
+ outfile,
38
+ plugins: [externalSdkPlugin()],
39
+ logLevel: 'info',
40
+ });
41
+ const bytes = readFileSync(outfile).length;
42
+ if (bytes > MAX_BYTES) {
43
+ throw new Error(
44
+ `macro "${id}" bundle is ${bytes}B (> ${MAX_BYTES}B) — the SDK may not be externalized`
45
+ );
46
+ }
47
+ console.log(`[macro:${id}] built → ${outfile} (${bytes}B)`);
48
+ return { outfile, bytes };
49
+ }
@@ -0,0 +1,31 @@
1
+ import type { MacroBase } from './macro-base';
2
+ import type { Target } from './types';
3
+ export type ActionOptions = {
4
+ id: string;
5
+ bindable?: boolean;
6
+ label?: string;
7
+ suggestedKey?: string;
8
+ };
9
+ type MacroOpts = {
10
+ id: string;
11
+ schemaVersion?: number;
12
+ target?: Target;
13
+ tickTimeoutMs?: number;
14
+ };
15
+ type AnyCtor = abstract new (...args: never[]) => MacroBase;
16
+ export declare function Macro(opts: MacroOpts): <T extends AnyCtor>(value: T, context: ClassDecoratorContext) => T;
17
+ export declare function OnStart(): (_method: unknown, context: ClassMethodDecoratorContext) => void;
18
+ export declare function OnStop(): (_method: unknown, context: ClassMethodDecoratorContext) => void;
19
+ export declare function OnTick(hz: number): (_method: unknown, context: ClassMethodDecoratorContext) => void;
20
+ export declare function Action(action: string | ActionOptions): (_method: unknown, context: ClassMethodDecoratorContext) => void;
21
+ export declare function Panel(meta: {
22
+ id: string;
23
+ title: string;
24
+ }): (_method: unknown, context: ClassMethodDecoratorContext) => void;
25
+ /**
26
+ * Marks a method that returns the macro's in-game overlay HUD layout — a compact
27
+ * view drawn over the game, separate from the tabbed @Panel views. A macro may
28
+ * declare several @Overlay methods; their widgets are concatenated into one HUD.
29
+ */
30
+ export declare function Overlay(): (_method: unknown, context: ClassMethodDecoratorContext) => void;
31
+ export {};
@@ -0,0 +1,21 @@
1
+ export type Point = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+ export type Region = {
6
+ x: number;
7
+ y: number;
8
+ w: number;
9
+ h: number;
10
+ };
11
+ export type RegionRatio = {
12
+ x: number;
13
+ y: number;
14
+ w: number;
15
+ h: number;
16
+ };
17
+ export type ColorRGB = {
18
+ r: number;
19
+ g: number;
20
+ b: number;
21
+ };
package/lib/index.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ export { Macro, OnStart, OnStop, OnTick, Action, Panel, Overlay } from './decorators';
2
+ export type { ActionOptions } from './decorators';
3
+ export { MacroBase } from './macro-base';
4
+ export { robloxTarget, processTarget } from './targets';
5
+ export { region, ratio, point, color, number, string, boolean, select, pixelColor, findColor, defaultsOf, } from './settings';
6
+ export type { Setting, SettingMap, RegionSetting, RatioSetting, PointSetting, ColorSetting, NumberSetting, StringSetting, BooleanSetting, SelectSetting, PixelColorSetting, PixelColorValue, FindColorSetting, FindColorValue, } from './settings';
7
+ export { Group, Row, Stat, Chip, Divider, Button, Text, Meter, Image } from './widgets';
8
+ export type { Widget, PanelDeclaration, WidgetTone, WidgetSize, WidgetAlign, WidgetWeight, } from './panel-types';
9
+ export { Roblox, Instance } from './roblox/index';
10
+ export type { RobloxOffsets } from './roblox/offsets';
11
+ export type { UDim2, Vector3, TreeNode } from './roblox/instance';
12
+ export { offsetsFromDump } from './roblox/offsets-dump';
13
+ export type { RobloxOffsetDump } from './roblox/offsets-dump';
14
+ export { Address } from './process/address';
15
+ export { struct, f } from './process/struct';
16
+ export type { StructSpec } from './process/struct';
17
+ export type { ProcessAccess } from './process/process-access';
18
+ export type { Point, Region, RegionRatio, ColorRGB } from './geometry';
19
+ export type { ScriptCtx, Target, GameId, WindowTarget, ProcessTarget, PackageDeclaration, PixelReadout, MouseButton, } from './types';
@@ -0,0 +1,23 @@
1
+ import type { ProcessAccess } from './process/process-access';
2
+ import type { ScriptCtx } from './types';
3
+ declare const CTX: unique symbol;
4
+ export declare abstract class MacroBase<State extends Record<string, unknown> = Record<string, unknown>> {
5
+ /** Set by the generated declaration before each lifecycle/tick/action call. */
6
+ private [CTX];
7
+ /** Initial state, published to the UI when the macro starts. */
8
+ initialState?: State;
9
+ /**
10
+ * @internal Bind the live ctx; returns the previous binding so callers can
11
+ * restore it (LIFO) — an action interleaving a suspended async tick must not
12
+ * clobber the tick's ctx.
13
+ */
14
+ __bind(ctx: ScriptCtx | undefined): ScriptCtx | undefined;
15
+ protected get ctx(): ScriptCtx;
16
+ protected get process(): ProcessAccess | undefined;
17
+ protected get input(): ScriptCtx['input'];
18
+ protected get log(): ScriptCtx['log'];
19
+ protected get settings(): Record<string, unknown>;
20
+ protected get state(): State;
21
+ protected setState(partial: Partial<State>): void;
22
+ }
23
+ export {};
@@ -0,0 +1,61 @@
1
+ export type WidgetTone = 'default' | 'ok' | 'warn' | 'err' | 'info';
2
+ export type WidgetSize = 'sm' | 'md' | 'lg';
3
+ export type WidgetAlign = 'start' | 'center' | 'end';
4
+ export type WidgetWeight = 'normal' | 'medium' | 'bold';
5
+ export type Widget = {
6
+ kind: 'stat';
7
+ label: string;
8
+ bind: string;
9
+ tone?: WidgetTone;
10
+ size?: WidgetSize;
11
+ align?: WidgetAlign;
12
+ } | {
13
+ kind: 'meter';
14
+ label: string;
15
+ bind: string;
16
+ min: number;
17
+ max: number;
18
+ tone?: WidgetTone;
19
+ colorMode?: 'auto' | 'tone';
20
+ } | {
21
+ kind: 'chip';
22
+ label: string;
23
+ bind: string;
24
+ tone?: WidgetTone;
25
+ size?: WidgetSize;
26
+ } | {
27
+ kind: 'button';
28
+ label: string;
29
+ action: string;
30
+ payload?: unknown;
31
+ variant?: 'default' | 'outline' | 'destructive' | 'ghost';
32
+ size?: WidgetSize;
33
+ } | {
34
+ kind: 'divider';
35
+ } | {
36
+ kind: 'group';
37
+ title?: string;
38
+ children: Widget[];
39
+ } | {
40
+ kind: 'row';
41
+ children: Widget[];
42
+ align?: WidgetAlign;
43
+ gap?: 'tight' | 'normal' | 'wide';
44
+ } | {
45
+ kind: 'text';
46
+ value?: string;
47
+ bind?: string;
48
+ tone?: WidgetTone;
49
+ size?: WidgetSize;
50
+ align?: WidgetAlign;
51
+ weight?: WidgetWeight;
52
+ mono?: boolean;
53
+ } | {
54
+ kind: 'image';
55
+ bind: string;
56
+ };
57
+ export type PanelDeclaration = {
58
+ id: string;
59
+ title: string;
60
+ layout: Widget[];
61
+ };
@@ -0,0 +1,10 @@
1
+ export declare class Address {
2
+ readonly value: bigint;
3
+ constructor(value: bigint | number);
4
+ add(offset: number | bigint): Address;
5
+ sub(offset: number | bigint): Address;
6
+ equals(other: Address): boolean;
7
+ isNull(): boolean;
8
+ toBigInt(): bigint;
9
+ toString(): string;
10
+ }
@@ -0,0 +1,23 @@
1
+ import type { Address } from './address';
2
+ import type { StructSpec } from './struct';
3
+ export interface ProcessAccess {
4
+ attached(): boolean;
5
+ moduleBase(name?: string): Address | null;
6
+ readBytes(addr: Address, size: number): Buffer | null;
7
+ readU8(addr: Address): number | null;
8
+ readI8(addr: Address): number | null;
9
+ readU16(addr: Address): number | null;
10
+ readI16(addr: Address): number | null;
11
+ readU32(addr: Address): number | null;
12
+ readI32(addr: Address): number | null;
13
+ readU64(addr: Address): bigint | null;
14
+ readI64(addr: Address): bigint | null;
15
+ readF32(addr: Address): number | null;
16
+ readF64(addr: Address): number | null;
17
+ readBool(addr: Address): boolean | null;
18
+ readPointer(addr: Address): Address | null;
19
+ readStruct<T>(addr: Address, spec: StructSpec<T>): T | null;
20
+ readString(addr: Address, len: number, encoding?: BufferEncoding): string | null;
21
+ readCString(addr: Address, maxLen?: number, encoding?: BufferEncoding): string | null;
22
+ walk(base: Address, offsets: readonly number[]): Address | null;
23
+ }
@@ -0,0 +1,29 @@
1
+ export type StructSpec<T> = {
2
+ readonly size: number;
3
+ read(buf: Buffer, base?: number): T;
4
+ };
5
+ type FieldReader<T> = (buf: Buffer, off: number) => T;
6
+ type Field<T> = {
7
+ offset: number;
8
+ size: number;
9
+ read: FieldReader<T>;
10
+ };
11
+ export declare const f: {
12
+ u8: (offset: number) => Field<number>;
13
+ i8: (offset: number) => Field<number>;
14
+ u16: (offset: number) => Field<number>;
15
+ i16: (offset: number) => Field<number>;
16
+ u32: (offset: number) => Field<number>;
17
+ i32: (offset: number) => Field<number>;
18
+ u64: (offset: number) => Field<bigint>;
19
+ i64: (offset: number) => Field<bigint>;
20
+ f32: (offset: number) => Field<number>;
21
+ f64: (offset: number) => Field<number>;
22
+ bool: (offset: number) => Field<boolean>;
23
+ };
24
+ type Shape = Record<string, Field<unknown>>;
25
+ type Decoded<S extends Shape> = {
26
+ [K in keyof S]: S[K] extends Field<infer T> ? T : never;
27
+ };
28
+ export declare function struct<S extends Shape>(fields: S): StructSpec<Decoded<S>>;
29
+ export {};
@@ -0,0 +1,32 @@
1
+ import { Address } from '../process/address';
2
+ import type { ProcessAccess } from '../process/process-access';
3
+ import { Instance } from './instance';
4
+ import type { RobloxOffsets } from './offsets';
5
+ export declare class Roblox {
6
+ #private;
7
+ readonly process: ProcessAccess;
8
+ readonly offsets: RobloxOffsets;
9
+ private constructor();
10
+ static bind(process: ProcessAccess, offsets: RobloxOffsets): Roblox;
11
+ invalidateCache(): void;
12
+ /**
13
+ * Sanity-check the offsets against live memory: resolve the DataModel and
14
+ * confirm it plus the Workspace/Players services look right. Catches a stale
15
+ * offset table after a Roblox build bump, instead of silently reading garbage.
16
+ */
17
+ validate(): {
18
+ ok: boolean;
19
+ problems: string[];
20
+ };
21
+ dataModel(): Instance | null;
22
+ service(name: string): Instance | null;
23
+ workspace(): Instance | null;
24
+ players(): Instance | null;
25
+ localPlayer(): Instance | null;
26
+ playerGui(): Instance | null;
27
+ character(): Instance | null;
28
+ equippedTool(): Instance | null;
29
+ }
30
+ export { Instance, Address };
31
+ export type { RobloxOffsets };
32
+ export type { UDim2, Vector3, TreeNode } from './instance';
@@ -0,0 +1,64 @@
1
+ import { Address } from '../process/address';
2
+ import type { ProcessAccess } from '../process/process-access';
3
+ import type { RobloxOffsets } from './offsets';
4
+ /** A Roblox UDim2 axis pair: `scale` (0..1 fraction of parent) + `offset` (px). */
5
+ export type UDim2 = {
6
+ /** X scale, 0..1. */
7
+ sx: number;
8
+ /** X offset, px. */
9
+ ox: number;
10
+ /** Y scale, 0..1. */
11
+ sy: number;
12
+ /** Y offset, px. */
13
+ oy: number;
14
+ };
15
+ /** A 3D world position. */
16
+ export type Vector3 = {
17
+ x: number;
18
+ y: number;
19
+ z: number;
20
+ };
21
+ /** A serializable snapshot of an instance subtree (see Instance.snapshot). */
22
+ export type TreeNode = {
23
+ name: string;
24
+ className: string;
25
+ children: TreeNode[];
26
+ };
27
+ export declare class Instance {
28
+ #private;
29
+ readonly address: Address;
30
+ constructor(address: Address, proc: ProcessAccess, off: RobloxOffsets);
31
+ name(): string;
32
+ className(): string;
33
+ text(): string;
34
+ /** A *Value object's `.Value` string (StringValue, etc.) — read inline at +Value. */
35
+ value(): string;
36
+ readF32At(offset: number): number | null;
37
+ readF64At(offset: number): number | null;
38
+ readI32At(offset: number): number | null;
39
+ readU64At(offset: number): bigint | null;
40
+ readBoolAt(offset: number): boolean | null;
41
+ readPointerAt(offset: number): Address | null;
42
+ /** Read an inline Roblox std::string at this instance + offset. */
43
+ readStringAt(offset: number): string | null;
44
+ /** A BasePart's world position (BasePart → Primitive → Position). Null without spatial offsets. */
45
+ worldPosition(): Vector3 | null;
46
+ /** Structured snapshot of this subtree, `depth` levels of descendants deep. */
47
+ snapshot(depth?: number): TreeNode;
48
+ /** Pretty-printed `name : ClassName` tree, for exploring an unfamiliar game. */
49
+ tree(depth?: number): string;
50
+ parent(): Instance | null;
51
+ children(): Instance[];
52
+ findFirstChild(name: string): Instance | null;
53
+ findFirstChildOfClass(className: string): Instance | null;
54
+ /** Frame/TextLabel `.Visible`. Returns null if the read fails. */
55
+ visible(): boolean | null;
56
+ /** ScreenGui `.Enabled`. Returns null if the read fails. */
57
+ enabled(): boolean | null;
58
+ /** GuiObject `.Position` as a UDim2. Returns null if the read fails. */
59
+ position(): UDim2 | null;
60
+ /** GuiObject `.Size` as a UDim2. Returns null if the read fails. */
61
+ size(): UDim2 | null;
62
+ findFirstDescendant(name: string, maxDepth?: number): Instance | null;
63
+ walk(path: string): Instance | null;
64
+ }
@@ -0,0 +1,6 @@
1
+ import type { RobloxOffsets } from './offsets';
2
+ export type RobloxOffsetDump = {
3
+ 'Roblox Version'?: string;
4
+ Offsets: Record<string, Record<string, number> | undefined>;
5
+ };
6
+ export declare function offsetsFromDump(dump: RobloxOffsetDump): RobloxOffsets;
@@ -0,0 +1,20 @@
1
+ export type RobloxOffsets = {
2
+ FakeDataModelPointer: number;
3
+ FakeDataModelToDataModel: number;
4
+ Name: number;
5
+ Parent: number;
6
+ Children: number;
7
+ ClassDescriptor: number;
8
+ ClassDescriptorToClassName: number;
9
+ StringLength: number;
10
+ Value: number;
11
+ LocalPlayer: number;
12
+ TextLabelText: number;
13
+ FramePositionX: number;
14
+ FrameSizeX: number;
15
+ FrameVisible: number;
16
+ TextLabelVisible: number;
17
+ ScreenGuiEnabled: number;
18
+ Primitive?: number;
19
+ PrimitivePosition?: number;
20
+ };
@@ -0,0 +1,52 @@
1
+ import type { Point, Region, RegionRatio } from './geometry';
2
+ type Base<K extends string, T> = {
3
+ kind: K;
4
+ label: string;
5
+ description?: string;
6
+ default: T;
7
+ };
8
+ export type RegionSetting = Base<'region', Region>;
9
+ export type RatioSetting = Base<'ratio', RegionRatio>;
10
+ export type PointSetting = Base<'point', {
11
+ x: number;
12
+ y: number;
13
+ }>;
14
+ export type ColorSetting = Base<'color', string> & {
15
+ tolerance?: number;
16
+ };
17
+ export type NumberSetting = Base<'number', number> & {
18
+ min?: number;
19
+ max?: number;
20
+ step?: number;
21
+ };
22
+ export type StringSetting = Base<'string', string>;
23
+ export type BooleanSetting = Base<'boolean', boolean>;
24
+ export type SelectSetting<T extends string = string> = Base<'select', T> & {
25
+ options: readonly T[];
26
+ };
27
+ export type PixelColorValue = {
28
+ point: Point;
29
+ color: string;
30
+ tolerance?: number;
31
+ };
32
+ export type PixelColorSetting = Base<'pixelColor', PixelColorValue>;
33
+ export type FindColorValue = {
34
+ rect: Region;
35
+ color: string;
36
+ tolerance?: number;
37
+ };
38
+ export type FindColorSetting = Base<'findColor', FindColorValue>;
39
+ export type Setting = RegionSetting | RatioSetting | PointSetting | ColorSetting | NumberSetting | StringSetting | BooleanSetting | SelectSetting | PixelColorSetting | FindColorSetting;
40
+ export type SettingMap = Record<string, Setting>;
41
+ export declare const region: (s: Omit<RegionSetting, "kind">) => RegionSetting;
42
+ export declare const ratio: (s: Omit<RatioSetting, "kind">) => RatioSetting;
43
+ export declare const point: (s: Omit<PointSetting, "kind">) => PointSetting;
44
+ export declare const color: (s: Omit<ColorSetting, "kind">) => ColorSetting;
45
+ export declare const number: (s: Omit<NumberSetting, "kind">) => NumberSetting;
46
+ export declare const string: (s: Omit<StringSetting, "kind">) => StringSetting;
47
+ export declare const boolean: (s: Omit<BooleanSetting, "kind">) => BooleanSetting;
48
+ export declare const select: <T extends string>(s: Omit<SelectSetting<T>, "kind">) => SelectSetting<T>;
49
+ export declare const pixelColor: (s: Omit<PixelColorSetting, "kind">) => PixelColorSetting;
50
+ export declare const findColor: (s: Omit<FindColorSetting, "kind">) => FindColorSetting;
51
+ export declare function defaultsOf<M extends SettingMap>(map: M): Record<string, unknown>;
52
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { ProcessTarget } from './types';
2
+ export declare function processTarget(opts: {
3
+ processName: string;
4
+ moduleName?: string;
5
+ expectedVersion?: string;
6
+ }): ProcessTarget;
7
+ export declare function robloxTarget(opts?: {
8
+ expectedVersion?: string;
9
+ }): ProcessTarget;
package/lib/types.d.ts ADDED
@@ -0,0 +1,118 @@
1
+ import type { ColorRGB, Point, Region } from './geometry';
2
+ import type { PanelDeclaration, Widget } from './panel-types';
3
+ import type { ProcessAccess } from './process/process-access';
4
+ import type { RobloxOffsets } from './roblox/offsets';
5
+ import type { SettingMap } from './settings';
6
+ export type PixelReadout = ColorRGB & {
7
+ hex: string;
8
+ };
9
+ export type MouseButton = 'left' | 'right' | 'middle';
10
+ export type ScriptCtx = {
11
+ settings: Record<string, unknown>;
12
+ state: {
13
+ get(): Record<string, unknown>;
14
+ set(partial: Record<string, unknown>): void;
15
+ };
16
+ signal: AbortSignal;
17
+ sleep(ms: number): Promise<void>;
18
+ /** Wall-clock time in milliseconds (host-mediated; equivalent to `Date.now()`). */
19
+ now(): number;
20
+ screen: {
21
+ captureRegion(name: string): Promise<Buffer>;
22
+ pixelAt(name: string): Promise<PixelReadout>;
23
+ pixelAtPoint(point: Point): Promise<PixelReadout>;
24
+ pixelAtRatio(rx: number, ry: number): Promise<PixelReadout>;
25
+ pixelMatches(settingName: string): Promise<boolean>;
26
+ pixelMatches(name: string, hex: string, tolerance?: number): Promise<boolean>;
27
+ pixelMatchesAt(point: Point, hex: string, tolerance?: number): Promise<boolean>;
28
+ findColor(settingName: string): Promise<Array<{
29
+ x: number;
30
+ y: number;
31
+ distance: number;
32
+ }>>;
33
+ findColor(regionName: string, hex: string, opts?: {
34
+ tolerance?: number;
35
+ limit?: number;
36
+ step?: number;
37
+ }): Promise<Array<{
38
+ x: number;
39
+ y: number;
40
+ distance: number;
41
+ }>>;
42
+ };
43
+ input: {
44
+ click(name: string, button?: MouseButton): Promise<void>;
45
+ clickAt(point: Point, button?: MouseButton): Promise<void>;
46
+ clickRatio(rx: number, ry: number, button?: MouseButton): Promise<void>;
47
+ move(name: string): Promise<void>;
48
+ moveAt(point: Point): Promise<void>;
49
+ mouseDown(button: MouseButton): Promise<void>;
50
+ mouseUp(button: MouseButton): Promise<void>;
51
+ tapKey(key: string): Promise<void>;
52
+ holdKey(key: string, ms: number): Promise<void>;
53
+ releaseAll(): Promise<void>;
54
+ };
55
+ color: {
56
+ distance(a: string | ColorRGB, b: string | ColorRGB): number;
57
+ matches(a: string | ColorRGB, b: string | ColorRGB, tolerance?: number): boolean;
58
+ hexToRgb(hex: string): ColorRGB;
59
+ rgbToHex(rgb: ColorRGB): string;
60
+ };
61
+ log: {
62
+ trace(msg: string, data?: unknown): void;
63
+ debug(msg: string, data?: unknown): void;
64
+ info(msg: string, data?: unknown): void;
65
+ warn(msg: string, data?: unknown): void;
66
+ error(msg: string, data?: unknown): void;
67
+ success(msg: string, data?: unknown): void;
68
+ };
69
+ invoke(actionName: string, payload?: unknown): Promise<unknown>;
70
+ process?: ProcessAccess;
71
+ /** Game memory offsets, fetched per game build and injected by the runtime. */
72
+ offsets: RobloxOffsets;
73
+ };
74
+ /** Identifies which game an offset set belongs to, so the runtime knows which
75
+ * table to fetch and inject as `ctx.offsets`. Roblox is the only game today. */
76
+ export type GameId = 'roblox';
77
+ export type WindowTarget = {
78
+ kind: 'window';
79
+ titlePattern: RegExp;
80
+ referenceSize: {
81
+ w: number;
82
+ h: number;
83
+ };
84
+ };
85
+ export type ProcessTarget = {
86
+ kind: 'process';
87
+ processName?: string;
88
+ moduleName?: string;
89
+ expectedVersion?: string;
90
+ game?: GameId;
91
+ };
92
+ export type Target = WindowTarget | ProcessTarget;
93
+ export type PackageDeclaration = {
94
+ id: string;
95
+ schemaVersion: number;
96
+ target?: Target;
97
+ state?: Record<string, unknown>;
98
+ settings?: SettingMap;
99
+ macro?: {
100
+ onStart?: (ctx: ScriptCtx) => Promise<void> | void;
101
+ tick?: (ctx: ScriptCtx) => Promise<void> | void;
102
+ onStop?: (ctx: ScriptCtx) => Promise<void> | void;
103
+ pollMs?: number;
104
+ tickTimeoutMs?: number;
105
+ };
106
+ panels?: PanelDeclaration[];
107
+ /** Combined in-game overlay HUD layout (concatenation of all @Overlay methods). */
108
+ overlay?: Widget[];
109
+ actions?: Record<string, (ctx: ScriptCtx, payload?: unknown) => Promise<unknown> | unknown>;
110
+ actionMeta?: Record<string, {
111
+ label?: string;
112
+ bindable?: boolean;
113
+ suggestedKey?: string;
114
+ }>;
115
+ debug?: Record<string, (ctx: ScriptCtx) => unknown>;
116
+ migrations?: Record<number, (old: Record<string, unknown>) => Record<string, unknown>>;
117
+ };
118
+ export type { Region };
@@ -0,0 +1,41 @@
1
+ import type { Widget, WidgetAlign, WidgetSize, WidgetTone, WidgetWeight } from './panel-types';
2
+ type StatOpts = {
3
+ tone?: WidgetTone;
4
+ size?: WidgetSize;
5
+ align?: WidgetAlign;
6
+ };
7
+ export declare const Stat: (label: string, bind: string, opts?: StatOpts) => Widget;
8
+ type ChipOpts = {
9
+ tone?: WidgetTone;
10
+ size?: WidgetSize;
11
+ };
12
+ export declare const Chip: (label: string, bind: string, opts?: ChipOpts) => Widget;
13
+ type ButtonOpts = {
14
+ payload?: unknown;
15
+ variant?: 'default' | 'outline' | 'destructive' | 'ghost';
16
+ size?: WidgetSize;
17
+ };
18
+ export declare const Button: (label: string, action: string, opts?: ButtonOpts) => Widget;
19
+ export declare const Divider: () => Widget;
20
+ export declare const Group: (title: string, children: Widget[]) => Widget;
21
+ type RowOpts = {
22
+ align?: WidgetAlign;
23
+ gap?: 'tight' | 'normal' | 'wide';
24
+ };
25
+ export declare const Row: (children: Widget[], opts?: RowOpts) => Widget;
26
+ type TextOpts = {
27
+ bind?: string;
28
+ tone?: WidgetTone;
29
+ size?: WidgetSize;
30
+ align?: WidgetAlign;
31
+ weight?: WidgetWeight;
32
+ mono?: boolean;
33
+ };
34
+ export declare const Text: (value: string, opts?: TextOpts) => Widget;
35
+ type MeterOpts = {
36
+ tone?: WidgetTone;
37
+ colorMode?: 'auto' | 'tone';
38
+ };
39
+ export declare const Meter: (label: string, bind: string, min: number, max: number, opts?: MeterOpts) => Widget;
40
+ export declare const Image: (bind: string) => Widget;
41
+ export {};
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@nighthawk.hq/macro-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Nighthawk macro author SDK — types only. Runtime is provided by the host.",
5
+ "type": "module",
6
+ "types": "./lib/index.d.ts",
7
+ "exports": {
8
+ ".": { "types": "./lib/index.d.ts", "default": "./stub.js" },
9
+ "./build": "./build/index.js"
10
+ },
11
+ "files": ["lib/**/*.d.ts", "stub.js", "build/index.js", "runtime-manifest.json"],
12
+ "scripts": {
13
+ "test": "node --test test/shim.test.mjs"
14
+ },
15
+ "dependencies": {
16
+ "esbuild": "^0.24.0"
17
+ }
18
+ }
@@ -0,0 +1,38 @@
1
+ [
2
+ "Action",
3
+ "Address",
4
+ "Button",
5
+ "Chip",
6
+ "Divider",
7
+ "Group",
8
+ "Image",
9
+ "Instance",
10
+ "Macro",
11
+ "MacroBase",
12
+ "Meter",
13
+ "OnStart",
14
+ "OnStop",
15
+ "OnTick",
16
+ "Overlay",
17
+ "Panel",
18
+ "Roblox",
19
+ "Row",
20
+ "Stat",
21
+ "Text",
22
+ "boolean",
23
+ "color",
24
+ "defaultsOf",
25
+ "f",
26
+ "findColor",
27
+ "number",
28
+ "offsetsFromDump",
29
+ "pixelColor",
30
+ "point",
31
+ "processTarget",
32
+ "ratio",
33
+ "region",
34
+ "robloxTarget",
35
+ "select",
36
+ "string",
37
+ "struct"
38
+ ]
package/stub.js ADDED
@@ -0,0 +1,4 @@
1
+ throw new Error(
2
+ 'Nighthawk SDK runtime is provided by the host, not by this package. ' +
3
+ 'Macros must be built with @nighthawk.hq/macro-sdk/build, which externalizes this import.'
4
+ );