@blastlauncher/runtime 1.0.5 → 3.0.0-alpha.1

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.
Files changed (110) hide show
  1. package/README.md +4 -10
  2. package/dist/api/action-panel.d.ts +134 -0
  3. package/dist/api/action-panel.js +36 -0
  4. package/dist/api/action.d.ts +386 -0
  5. package/dist/api/action.js +260 -0
  6. package/dist/api/ai-browser.d.ts +423 -0
  7. package/dist/api/ai-browser.js +113 -0
  8. package/dist/api/ai-decide.d.ts +41 -0
  9. package/dist/api/ai-decide.js +62 -0
  10. package/dist/api/applications.d.ts +13 -0
  11. package/dist/api/applications.js +48 -0
  12. package/dist/api/cache.d.ts +27 -0
  13. package/dist/api/cache.js +126 -0
  14. package/dist/api/capability.d.ts +14 -0
  15. package/dist/api/capability.js +30 -0
  16. package/dist/api/clipboard.d.ts +57 -0
  17. package/dist/api/clipboard.js +83 -0
  18. package/dist/api/detail.d.ts +33 -0
  19. package/dist/api/detail.js +11 -0
  20. package/dist/api/diagnostics.d.ts +16 -0
  21. package/dist/api/diagnostics.js +26 -0
  22. package/dist/api/dropdown.d.ts +17 -0
  23. package/dist/api/dropdown.js +48 -0
  24. package/dist/api/enums.generated.d.ts +750 -0
  25. package/dist/api/enums.generated.js +719 -0
  26. package/dist/api/environment.d.ts +44 -0
  27. package/dist/api/environment.js +61 -0
  28. package/dist/api/feedback.d.ts +94 -0
  29. package/dist/api/feedback.js +228 -0
  30. package/dist/api/form.d.ts +162 -0
  31. package/dist/api/form.js +345 -0
  32. package/dist/api/grid.d.ts +293 -0
  33. package/dist/api/grid.js +36 -0
  34. package/dist/api/implemented.d.ts +53 -0
  35. package/dist/api/implemented.js +43 -0
  36. package/dist/api/index.d.ts +612 -0
  37. package/dist/api/index.js +82 -0
  38. package/dist/api/keyboard.d.ts +182 -0
  39. package/dist/api/keyboard.js +107 -0
  40. package/dist/api/launch.d.ts +40 -0
  41. package/dist/api/launch.js +27 -0
  42. package/dist/api/legacy.d.ts +9 -0
  43. package/dist/api/legacy.js +25 -0
  44. package/dist/api/list.d.ts +291 -0
  45. package/dist/api/list.js +28 -0
  46. package/dist/api/menu-bar.d.ts +54 -0
  47. package/dist/api/menu-bar.js +30 -0
  48. package/dist/api/metadata.d.ts +62 -0
  49. package/dist/api/metadata.js +33 -0
  50. package/dist/api/navigation.d.ts +36 -0
  51. package/dist/api/navigation.js +99 -0
  52. package/dist/api/oauth.d.ts +84 -0
  53. package/dist/api/oauth.js +125 -0
  54. package/dist/api/open.d.ts +18 -0
  55. package/dist/api/open.js +36 -0
  56. package/dist/api/preferences.d.ts +43 -0
  57. package/dist/api/preferences.js +41 -0
  58. package/dist/api/selection.d.ts +10 -0
  59. package/dist/api/selection.js +31 -0
  60. package/dist/api/slot.d.ts +7 -0
  61. package/dist/api/slot.js +11 -0
  62. package/dist/api/storage.d.ts +24 -0
  63. package/dist/api/storage.js +45 -0
  64. package/dist/api/types.d.ts +46 -0
  65. package/dist/api/types.js +5 -0
  66. package/dist/api/unsupported.d.ts +18 -0
  67. package/dist/api/unsupported.generated.d.ts +1255 -0
  68. package/dist/api/unsupported.generated.js +56 -0
  69. package/dist/api/unsupported.js +65 -0
  70. package/dist/api/window-management.d.ts +97 -0
  71. package/dist/api/window-management.js +164 -0
  72. package/dist/api/window.d.ts +16 -0
  73. package/dist/api/window.js +32 -0
  74. package/dist/main.d.ts +1 -0
  75. package/dist/main.js +339 -0
  76. package/dist/provider.d.ts +8 -0
  77. package/dist/provider.js +107 -0
  78. package/dist/reconciler/index.d.ts +9 -0
  79. package/dist/reconciler/index.js +186 -0
  80. package/dist/reconciler/serialize.d.ts +3 -0
  81. package/dist/reconciler/serialize.js +53 -0
  82. package/dist/reconciler/types.d.ts +8 -0
  83. package/dist/reconciler/types.js +1 -0
  84. package/package.json +38 -23
  85. package/.eslintrc.js +0 -6
  86. package/CHANGELOG.md +0 -19
  87. package/dist/App.d.ts +0 -5
  88. package/dist/App.js +0 -10
  89. package/dist/components/CommandList/index.d.ts +0 -1
  90. package/dist/components/CommandList/index.js +0 -20
  91. package/dist/components/CommandList/utils.d.ts +0 -1
  92. package/dist/components/CommandList/utils.js +0 -24
  93. package/dist/index.d.ts +0 -1
  94. package/dist/index.js +0 -11
  95. package/dist/utils/connectDevtools.d.ts +0 -1
  96. package/dist/utils/connectDevtools.js +0 -11
  97. package/dist/utils/run.d.ts +0 -4
  98. package/dist/utils/run.js +0 -12
  99. package/dist/utils/window.d.ts +0 -0
  100. package/dist/utils/window.js +0 -2
  101. package/src/@types/raycast.d.ts +0 -4
  102. package/src/@types/react-devtools-core.d.ts +0 -19
  103. package/src/App.tsx +0 -18
  104. package/src/components/CommandList/index.tsx +0 -27
  105. package/src/components/CommandList/utils.ts +0 -26
  106. package/src/index.ts +0 -13
  107. package/src/utils/connectDevtools.ts +0 -8
  108. package/src/utils/run.ts +0 -7
  109. package/src/utils/window.js +0 -1
  110. package/tsconfig.json +0 -10
@@ -0,0 +1,48 @@
1
+ import { requestCapability } from "./capability.js";
2
+ /** Fields other than the required `name` and `path`. */
3
+ const OPTIONAL_FIELDS = ["localizedName", "bundleId", "windowsAppId"];
4
+ export function pathParam(path, api) {
5
+ if (typeof path !== "string" && !(path instanceof URL) && !Buffer.isBuffer(path)) {
6
+ throw new TypeError(`${api}: path must be a string, Buffer or URL`);
7
+ }
8
+ const value = String(path);
9
+ if (value === "")
10
+ throw new TypeError(`${api}: path must not be empty`);
11
+ return value;
12
+ }
13
+ // The host is another process: rebuild each application from the declared
14
+ // fields rather than trusting whatever shape arrived on the wire.
15
+ export function toApplication(value, api = "getApplications") {
16
+ if (typeof value !== "object" ||
17
+ value === null ||
18
+ Array.isArray(value) ||
19
+ typeof value.name !== "string" ||
20
+ typeof value.path !== "string") {
21
+ throw new TypeError(`${api}: the host returned an invalid Application`);
22
+ }
23
+ const application = { name: value.name, path: value.path };
24
+ for (const field of OPTIONAL_FIELDS) {
25
+ const fieldValue = value[field];
26
+ if (typeof fieldValue === "string")
27
+ application[field] = fieldValue;
28
+ }
29
+ return application;
30
+ }
31
+ export async function getApplications(path) {
32
+ const params = path === undefined ? {} : { path: pathParam(path, "getApplications") };
33
+ const value = await requestCapability("applications.list", params);
34
+ if (!Array.isArray(value)) {
35
+ throw new TypeError("getApplications: the host returned a non-array");
36
+ }
37
+ return value.map((item) => toApplication(item));
38
+ }
39
+ /**
40
+ * `getDefaultApplication(path)`: the application the machine would open the
41
+ * path with. Rejects when there is none, exactly as the declarations promise
42
+ * and as a headless host without an application database always does.
43
+ */
44
+ export async function getDefaultApplication(path) {
45
+ return toApplication(await requestCapability("applications.default", {
46
+ path: pathParam(path, "getDefaultApplication"),
47
+ }), "getDefaultApplication");
48
+ }
@@ -0,0 +1,27 @@
1
+ export type CacheOptions = {
2
+ namespace?: string;
3
+ directory?: string;
4
+ capacity?: number;
5
+ };
6
+ export type CacheSubscriber = (key: string | undefined, data: string | undefined) => void;
7
+ export type CacheSubscription = () => void;
8
+ export declare class Cache {
9
+ static get STORAGE_DIRECTORY_NAME(): string;
10
+ static get DEFAULT_CAPACITY(): number;
11
+ private directory;
12
+ private capacity;
13
+ constructor(options?: CacheOptions);
14
+ get storageDirectory(): string;
15
+ private read;
16
+ private write;
17
+ private notify;
18
+ get(key: string): string | undefined;
19
+ has(key: string): boolean;
20
+ get isEmpty(): boolean;
21
+ set(key: string, data: string): void;
22
+ remove(key: string): boolean;
23
+ clear(options?: {
24
+ notifySubscribers: boolean;
25
+ }): void;
26
+ subscribe: (subscriber: CacheSubscriber) => CacheSubscription;
27
+ }
@@ -0,0 +1,126 @@
1
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { environment } from "./environment.js";
4
+ const subscribers = new Map();
5
+ let writes = 0;
6
+ // Namespaces are single segments even when supplied by an extension.
7
+ function segment(value) {
8
+ return encodeURIComponent(value).replaceAll(".", "%2E");
9
+ }
10
+ export class Cache {
11
+ static get STORAGE_DIRECTORY_NAME() {
12
+ return "cache";
13
+ }
14
+ static get DEFAULT_CAPACITY() {
15
+ // Cache.Options.capacity in the public declarations specifies 10 MB.
16
+ return 10 * 1024 * 1024;
17
+ }
18
+ directory;
19
+ capacity;
20
+ constructor(options = {}) {
21
+ const parent = options.directory ?? environment.supportPath;
22
+ if (!parent)
23
+ throw new Error("Cache requires an assigned support directory");
24
+ this.capacity = options.capacity ?? Cache.DEFAULT_CAPACITY;
25
+ if (!Number.isFinite(this.capacity) || this.capacity < 0)
26
+ throw new Error("Cache capacity must be a finite non-negative number");
27
+ this.directory = resolve(parent, Cache.STORAGE_DIRECTORY_NAME);
28
+ if (options.namespace)
29
+ this.directory = join(this.directory, segment(options.namespace));
30
+ mkdirSync(this.directory, { recursive: true });
31
+ }
32
+ get storageDirectory() {
33
+ return this.directory;
34
+ }
35
+ read() {
36
+ let text;
37
+ try {
38
+ text = readFileSync(join(this.directory, "entries.json"), "utf8");
39
+ }
40
+ catch (error) {
41
+ if (error.code === "ENOENT")
42
+ return new Map();
43
+ throw error;
44
+ }
45
+ const entries = JSON.parse(text);
46
+ if (!Array.isArray(entries) ||
47
+ !entries.every((entry) => Array.isArray(entry) &&
48
+ entry.length === 2 &&
49
+ entry.every((value) => typeof value === "string")))
50
+ throw new Error("Invalid Cache entries");
51
+ return new Map(entries);
52
+ }
53
+ write(entries) {
54
+ const file = join(this.directory, "entries.json");
55
+ const temporary = `${file}.${process.pid}-${++writes}.tmp`;
56
+ try {
57
+ writeFileSync(temporary, JSON.stringify([...entries]), "utf8");
58
+ renameSync(temporary, file);
59
+ }
60
+ finally {
61
+ rmSync(temporary, { force: true });
62
+ }
63
+ }
64
+ notify(key, data) {
65
+ // Callbacks may subscribe or unsubscribe while notifications are delivered.
66
+ const snapshot = [...(subscribers.get(this.directory) ?? [])];
67
+ for (const subscriber of snapshot)
68
+ subscriber(key, data);
69
+ }
70
+ get(key) {
71
+ const entries = this.read();
72
+ const data = entries.get(key);
73
+ if (data !== undefined) {
74
+ entries.delete(key);
75
+ entries.set(key, data);
76
+ this.write(entries);
77
+ }
78
+ return data;
79
+ }
80
+ has(key) {
81
+ return this.read().has(key);
82
+ }
83
+ get isEmpty() {
84
+ return this.read().size === 0;
85
+ }
86
+ set(key, data) {
87
+ if (typeof key !== "string" || typeof data !== "string")
88
+ throw new Error("Cache keys and data must be strings");
89
+ const entries = this.read();
90
+ entries.delete(key);
91
+ entries.set(key, data);
92
+ let bytes = [...entries.values()].reduce((sum, value) => sum + Buffer.byteLength(value), 0);
93
+ for (const [oldKey, value] of entries) {
94
+ if (bytes <= this.capacity)
95
+ break;
96
+ entries.delete(oldKey);
97
+ bytes -= Buffer.byteLength(value);
98
+ }
99
+ this.write(entries);
100
+ this.notify(key, data);
101
+ }
102
+ remove(key) {
103
+ const entries = this.read();
104
+ const removed = entries.delete(key);
105
+ if (removed)
106
+ this.write(entries);
107
+ this.notify(key);
108
+ return removed;
109
+ }
110
+ clear(options) {
111
+ this.write(new Map());
112
+ if (options?.notifySubscribers !== false)
113
+ this.notify();
114
+ }
115
+ subscribe = (subscriber) => {
116
+ let listeners = subscribers.get(this.directory);
117
+ if (!listeners)
118
+ subscribers.set(this.directory, (listeners = new Set()));
119
+ listeners.add(subscriber);
120
+ return () => {
121
+ listeners.delete(subscriber);
122
+ if (!listeners.size)
123
+ subscribers.delete(this.directory);
124
+ };
125
+ };
126
+ }
@@ -0,0 +1,14 @@
1
+ import type { JsonValue } from "../reconciler/index.js";
2
+ /**
3
+ * Bridge used by privileged APIs to ask the host for a capability, e.g.
4
+ * `("clipboard.copy", { text })`. Installed by the runtime entry, never by
5
+ * extension code.
6
+ */
7
+ export type CapabilityOptions = {
8
+ signal?: AbortSignal;
9
+ onProgress?: (value: JsonValue) => void;
10
+ };
11
+ export type CapabilityRequester = (name: string, params: Record<string, JsonValue>, options?: CapabilityOptions) => Promise<JsonValue>;
12
+ /** Installs (or, with `undefined`, removes) the host bridge for privileged APIs. */
13
+ export declare function setCapabilityRequester(next: CapabilityRequester | undefined): void;
14
+ export declare function requestCapability(name: string, params: Record<string, JsonValue>, options?: CapabilityOptions): Promise<JsonValue>;
@@ -0,0 +1,30 @@
1
+ let requester;
2
+ /** Installs (or, with `undefined`, removes) the host bridge for privileged APIs. */
3
+ export function setCapabilityRequester(next) {
4
+ requester = next;
5
+ }
6
+ export function requestCapability(name, params, options) {
7
+ if (options?.signal?.aborted)
8
+ return Promise.reject(new DOMException("The operation was aborted", "AbortError"));
9
+ if (!requester) {
10
+ return Promise.reject(new Error(`Cannot request capability '${name}': no capability requester is installed. ` +
11
+ "Call setCapabilityRequester() before using privileged APIs."));
12
+ }
13
+ const signal = options?.signal;
14
+ if (!signal)
15
+ return requester(name, params, options);
16
+ const dispatch = requester;
17
+ return new Promise((resolve, reject) => {
18
+ const abort = () => reject(new DOMException("The operation was aborted", "AbortError"));
19
+ signal.addEventListener("abort", abort, { once: true });
20
+ try {
21
+ void dispatch(name, params, options)
22
+ .then(resolve, reject)
23
+ .finally(() => signal.removeEventListener("abort", abort));
24
+ }
25
+ catch (error) {
26
+ signal.removeEventListener("abort", abort);
27
+ reject(error);
28
+ }
29
+ });
30
+ }
@@ -0,0 +1,57 @@
1
+ import type { JsonValue } from "../reconciler/index.js";
2
+ import { type ApplicationPath } from "./applications.js";
3
+ /** `Clipboard.Content`: the text, file and HTML forms the declarations define. */
4
+ export type ClipboardContent = {
5
+ text: string;
6
+ } | {
7
+ file: ApplicationPath;
8
+ } | {
9
+ html: string;
10
+ text?: string;
11
+ };
12
+ /** `Clipboard.CopyOptions`. */
13
+ export interface CopyOptions {
14
+ /** @deprecated Use `concealed` instead. */
15
+ transient?: boolean;
16
+ /** Indicates whether the content should be treated as confidential. */
17
+ concealed?: boolean;
18
+ }
19
+ /** `Clipboard.PasteOptions`. */
20
+ export interface PasteOptions {
21
+ /** Whether the host should close its main window after pasting. */
22
+ closeMainWindow?: boolean;
23
+ }
24
+ /** Options shared by `Clipboard.read` and `Clipboard.readText`. */
25
+ export interface ReadOptions {
26
+ /** Reaches back through the clipboard history; 0 is the current content. */
27
+ offset?: number;
28
+ }
29
+ /** `Clipboard.ReadContent`. */
30
+ export interface ReadContent {
31
+ /** The text representation of the content. */
32
+ text: string;
33
+ /** The file representation of the content. */
34
+ file?: string;
35
+ /** The HTML representation of the content. */
36
+ html?: string;
37
+ }
38
+ /**
39
+ * `Clipboard.Content` as capability params: exactly one representation per
40
+ * request, so a client can tell a file copy from an HTML copy. `concealed` is
41
+ * a host-side concern (history elision) and travels alongside, never inside.
42
+ */
43
+ export declare function contentParams(content: string | number | ClipboardContent, api: string): Record<string, JsonValue>;
44
+ export declare function copy(content: string | number | ClipboardContent, options?: CopyOptions): Promise<void>;
45
+ export declare function clear(): Promise<void>;
46
+ export declare function paste(content: string | number | ClipboardContent, options?: PasteOptions): Promise<void>;
47
+ /** Reads the clipboard at `offset`; the host answers with whatever it has. */
48
+ export declare function read(options?: ReadOptions): Promise<ReadContent>;
49
+ export declare function readText(options?: ReadOptions): Promise<string | undefined>;
50
+ /** Members in the order the declarations list them. */
51
+ export declare const Clipboard: {
52
+ copy: typeof copy;
53
+ clear: typeof clear;
54
+ paste: typeof paste;
55
+ read: typeof read;
56
+ readText: typeof readText;
57
+ };
@@ -0,0 +1,83 @@
1
+ import { pathParam } from "./applications.js";
2
+ import { requestCapability } from "./capability.js";
3
+ /** The offsets the declarations allow into the clipboard history. */
4
+ const MAX_OFFSET = 5;
5
+ /**
6
+ * `Clipboard.Content` as capability params: exactly one representation per
7
+ * request, so a client can tell a file copy from an HTML copy. `concealed` is
8
+ * a host-side concern (history elision) and travels alongside, never inside.
9
+ */
10
+ export function contentParams(content, api) {
11
+ if (typeof content === "string")
12
+ return { text: content };
13
+ if (typeof content === "number")
14
+ return { text: String(content) };
15
+ if (typeof content === "object" && content !== null && !Array.isArray(content)) {
16
+ if ("file" in content &&
17
+ (typeof content.file === "string" ||
18
+ Buffer.isBuffer(content.file) ||
19
+ content.file instanceof URL))
20
+ return { file: pathParam(content.file, api) };
21
+ if ("html" in content && typeof content.html === "string") {
22
+ if (content.text === undefined)
23
+ return { html: content.html };
24
+ if (typeof content.text === "string")
25
+ return { html: content.html, text: content.text };
26
+ }
27
+ else if ("text" in content && typeof content.text === "string")
28
+ return { text: content.text };
29
+ }
30
+ throw new TypeError(`${api}: unsupported content in Blast`);
31
+ }
32
+ export async function copy(content, options) {
33
+ const params = contentParams(content, "Clipboard.copy");
34
+ // Only a set flag crosses the wire: absent stays absent, like every prop.
35
+ if (options?.concealed !== undefined)
36
+ params["concealed"] = options.concealed;
37
+ // `transient` is the deprecated name of `concealed`; hosts that still know the
38
+ // old flag receive it as authored rather than silently dropped.
39
+ if (options?.transient !== undefined)
40
+ params["transient"] = options.transient;
41
+ await requestCapability("clipboard.copy", params);
42
+ }
43
+ export async function clear() {
44
+ await requestCapability("clipboard.clear", {});
45
+ }
46
+ export async function paste(content, options) {
47
+ const params = contentParams(content, "Clipboard.paste");
48
+ // Raycast closes its main window by default; preserve an explicit false and
49
+ // make the default visible to hosts so every embedder can apply the policy.
50
+ params.closeMainWindow = options?.closeMainWindow ?? true;
51
+ await requestCapability("clipboard.paste", params);
52
+ }
53
+ function readParams(options, api) {
54
+ const offset = options?.offset;
55
+ if (offset === undefined)
56
+ return {};
57
+ if (!Number.isInteger(offset) || offset < 0 || offset > MAX_OFFSET) {
58
+ throw new TypeError(`${api}: offset must be an integer between 0 and ${MAX_OFFSET}`);
59
+ }
60
+ return { offset };
61
+ }
62
+ /** Reads the clipboard at `offset`; the host answers with whatever it has. */
63
+ export async function read(options) {
64
+ const value = await requestCapability("clipboard.read", readParams(options, "Clipboard.read"));
65
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
66
+ throw new TypeError("Clipboard.read: the host returned a non-object clipboard content");
67
+ }
68
+ // The declarations make `text` required, so a host without text answers empty.
69
+ const content = { text: typeof value.text === "string" ? value.text : "" };
70
+ if (typeof value.file === "string")
71
+ content.file = value.file;
72
+ if (typeof value.html === "string")
73
+ content.html = value.html;
74
+ return content;
75
+ }
76
+ export async function readText(options) {
77
+ const { text } = await read(options);
78
+ // The wire cannot tell "no text" apart from an empty string; Raycast reports
79
+ // the former as undefined, so an empty text maps to that.
80
+ return text === "" ? undefined : text;
81
+ }
82
+ /** Members in the order the declarations list them. */
83
+ export const Clipboard = { copy, clear, paste, read, readText };
@@ -0,0 +1,33 @@
1
+ import { type ReactNode } from "react";
2
+ export interface DetailProps {
3
+ markdown?: string | null;
4
+ /** A `Detail.Metadata`; rendered as a `Slot` child named `metadata`. */
5
+ metadata?: ReactNode;
6
+ /** An ActionPanel; rendered as a `Slot` child named `actions`. */
7
+ actions?: ReactNode;
8
+ navigationTitle?: string;
9
+ isLoading?: boolean;
10
+ }
11
+ export interface ListItemDetailProps {
12
+ /** Indicates whether a loading bar should be shown above the detail. */
13
+ isLoading?: boolean;
14
+ /** The CommonMark string rendered when the parent item is selected. */
15
+ markdown?: string | null;
16
+ /** A `List.Item.Detail.Metadata`; rendered as a `Slot` child named `metadata`. */
17
+ metadata?: ReactNode;
18
+ }
19
+ export declare const Detail: ((props: DetailProps) => import("react").ReactElement<{
20
+ markdown?: string | null;
21
+ navigationTitle?: string;
22
+ isLoading?: boolean;
23
+ }, string | import("react").JSXElementConstructor<any>>) & {
24
+ Metadata: import("./metadata.js").MetadataComponents;
25
+ };
26
+ export declare const ListItemDetail: ((props: ListItemDetailProps) => import("react").ReactElement<{
27
+ /** Indicates whether a loading bar should be shown above the detail. */
28
+ isLoading?: boolean;
29
+ /** The CommonMark string rendered when the parent item is selected. */
30
+ markdown?: string | null;
31
+ }, string | import("react").JSXElementConstructor<any>>) & {
32
+ Metadata: import("./metadata.js").MetadataComponents;
33
+ };
@@ -0,0 +1,11 @@
1
+ import { createElement } from "react";
2
+ import { createMetadata } from "./metadata.js";
3
+ import { slot } from "./slot.js";
4
+ export const Detail = Object.assign(function Detail(props) {
5
+ const { actions, metadata, ...rest } = props;
6
+ return createElement("Detail", rest, slot("actions", actions), slot("metadata", metadata));
7
+ }, { Metadata: createMetadata("Detail") });
8
+ export const ListItemDetail = Object.assign(function ListItemDetail(props) {
9
+ const { metadata, ...rest } = props;
10
+ return createElement("List.Item.Detail", rest, slot("metadata", metadata));
11
+ }, { Metadata: createMetadata("List.Item.Detail") });
@@ -0,0 +1,16 @@
1
+ /** The log levels the protocol's `log` message carries. */
2
+ export type RuntimeLogLevel = "debug" | "info" | "warn" | "error";
3
+ export type RuntimeLogSink = (level: RuntimeLogLevel, message: string) => void;
4
+ /** Installs (or, with `undefined`, removes) the runtime log sink. */
5
+ export declare function setRuntimeLogSink(next: RuntimeLogSink | undefined): void;
6
+ /**
7
+ * Reports an exception. Without a sink (a bare module import in a test) this is
8
+ * a no-op: reporting must never be the reason a command fails.
9
+ */
10
+ export declare function captureException(exception: unknown): void;
11
+ /**
12
+ * Records heap statistics with a label. Raycast only records when memory
13
+ * reporting is enabled, so this stays at `debug` level: a client decides what
14
+ * to keep, and nothing is returned to the extension.
15
+ */
16
+ export declare function captureMemorySnapshot(label: string): void;
@@ -0,0 +1,26 @@
1
+ // `captureException` and `captureMemorySnapshot` from the declarations. Raycast
2
+ // reports both to the Developer Hub; Blast has no network and writes to the
3
+ // runtime's log channel instead, which the host already collects per session.
4
+ // The sink is installed by the runtime entry, never by extension code.
5
+ import { format } from "node:util";
6
+ let sink;
7
+ /** Installs (or, with `undefined`, removes) the runtime log sink. */
8
+ export function setRuntimeLogSink(next) {
9
+ sink = next;
10
+ }
11
+ /**
12
+ * Reports an exception. Without a sink (a bare module import in a test) this is
13
+ * a no-op: reporting must never be the reason a command fails.
14
+ */
15
+ export function captureException(exception) {
16
+ sink?.("error", `captureException: ${format(exception)}`);
17
+ }
18
+ /**
19
+ * Records heap statistics with a label. Raycast only records when memory
20
+ * reporting is enabled, so this stays at `debug` level: a client decides what
21
+ * to keep, and nothing is returned to the extension.
22
+ */
23
+ export function captureMemorySnapshot(label) {
24
+ const { heapUsed, heapTotal } = process.memoryUsage();
25
+ sink?.("debug", `captureMemorySnapshot: ${label} heapUsed=${heapUsed} heapTotal=${heapTotal}`);
26
+ }
@@ -0,0 +1,17 @@
1
+ import type { ListDropdownProps } from "./list.js";
2
+ export declare function useDropdown(type: string, props: ListDropdownProps): import("react").ReactElement<{
3
+ children?: import("react").ReactNode;
4
+ id?: string;
5
+ tooltip: string;
6
+ placeholder?: string;
7
+ storeValue?: boolean;
8
+ defaultValue?: string;
9
+ filtering?: boolean | {
10
+ keepSectionOrder: boolean;
11
+ };
12
+ isLoading?: boolean;
13
+ throttle?: boolean;
14
+ onSearchTextChange?: (text: string) => void;
15
+ value: string | undefined;
16
+ onChange: ((value: string) => void | undefined) | undefined;
17
+ }, string | import("react").JSXElementConstructor<any>>;
@@ -0,0 +1,48 @@
1
+ import { Children, createElement, isValidElement, useEffect, useRef, useState } from "react";
2
+ import { requestCapability } from "./capability.js";
3
+ import { environment } from "./environment.js";
4
+ // Search/filtering/throttle are client presentation requests, like List/Grid's.
5
+ export function useDropdown(type, props) {
6
+ const [selection, setSelection] = useState(props.defaultValue);
7
+ const edited = useRef(false);
8
+ const callback = useRef(props.onChange);
9
+ callback.current = props.onChange;
10
+ const key = `blast:dropdown:${JSON.stringify([environment.commandName, type, props.id ?? "default"])}`;
11
+ useEffect(() => {
12
+ if (!props.storeValue || props.value !== undefined)
13
+ return;
14
+ let mounted = true;
15
+ void requestCapability("storage.get", { scope: "blast:internal", key })
16
+ .then((value) => {
17
+ if (!mounted || edited.current || typeof value !== "string")
18
+ return;
19
+ const contains = (children) => Children.toArray(children).some((child) => {
20
+ if (!isValidElement(child))
21
+ return false;
22
+ return child.props.value === value || contains(child.props.children);
23
+ });
24
+ if (contains(props.children)) {
25
+ setSelection(value);
26
+ callback.current?.(value);
27
+ }
28
+ })
29
+ .catch((error) => console.error("Dropdown restore failed", error));
30
+ return () => {
31
+ mounted = false;
32
+ };
33
+ }, [key, props.storeValue, props.value]);
34
+ const onChange = (value) => {
35
+ edited.current = true;
36
+ if (props.value === undefined)
37
+ setSelection(value);
38
+ if (props.storeValue) {
39
+ void requestCapability("storage.set", { scope: "blast:internal", key, value }).catch((error) => console.error("Dropdown save failed", error));
40
+ }
41
+ return props.onChange?.(value);
42
+ };
43
+ return createElement(type, {
44
+ ...props,
45
+ value: props.value ?? selection,
46
+ onChange: props.onChange || props.value === undefined || props.storeValue ? onChange : undefined,
47
+ });
48
+ }