@blastlauncher/cli 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.
@@ -0,0 +1,26 @@
1
+ import { type BrowserTab } from "./browser.js";
2
+ import type { CapabilityHandler } from "@blastlauncher/engine";
3
+ /** `Application` from the `@raycast/api` declarations, as it crosses the wire. */
4
+ export type Application = {
5
+ name: string;
6
+ path: string;
7
+ localizedName?: string;
8
+ bundleId?: string;
9
+ windowsAppId?: string;
10
+ };
11
+ export type HeadlessCapabilities = {
12
+ aiAnswer?: string | undefined;
13
+ browserTabs?: BrowserTab[] | undefined;
14
+ oauthCode?: string | undefined;
15
+ /** Answers `applications.list`; an empty list when omitted. */
16
+ applications?: Application[] | undefined;
17
+ selectedText?: string | undefined;
18
+ selectedFiles?: string[] | undefined;
19
+ /** Bundle identifier; resolves against applications or a synthetic application. */
20
+ frontmost?: string | undefined;
21
+ /** Answers `clipboard.read` at offset 0; an empty text when omitted. */
22
+ clipboard?: string | undefined;
23
+ };
24
+ /** Parse an `--applications` file: a JSON array of `{ name, path, … }`. */
25
+ export declare function parseApplications(text: string): Application[];
26
+ export declare function createHeadlessHandlers(capabilities?: HeadlessCapabilities): Record<string, CapabilityHandler>;
@@ -0,0 +1,162 @@
1
+ // The capabilities a headless Blast run answers without a desktop client. They
2
+ // read host state or record UI requests: `getApplications` reports the applications the caller supplied
3
+ // (an empty list otherwise), `Clipboard.read` reports the text the caller
4
+ // supplied (empty otherwise, with no history behind it), and opening a
5
+ // preference window or updating command metadata is only recorded. Selection
6
+ // comes from the supplied text and paths; the frontmost app is supplied or neutral.
7
+ // Window management reads are neutral too: one desktop, no windows, and no
8
+ // default application for a path. Everything that would change the machine
9
+ // (`open.*`, `clipboard.paste`, `clipboard.clear`, `window.setBounds`,
10
+ // `mcp.install`) stays denied unless granted.
11
+ import { createBrowserHandlers } from "./browser.js";
12
+ /** Fields other than the required `name` and `path`. */
13
+ const OPTIONAL_FIELDS = ["localizedName", "bundleId", "windowsAppId"];
14
+ /** The offsets the `Clipboard.read` declarations allow into clipboard history. */
15
+ const MAX_OFFSET = 5;
16
+ function readParams(params, api) {
17
+ if (typeof params !== "object" || params === null || Array.isArray(params)) {
18
+ throw new Error(`${api}: params must be an object`);
19
+ }
20
+ return params;
21
+ }
22
+ function toApplication(value, at) {
23
+ if (typeof value !== "object" ||
24
+ value === null ||
25
+ Array.isArray(value) ||
26
+ typeof value.name !== "string" ||
27
+ typeof value.path !== "string") {
28
+ throw new Error(`${at} must be an object with string "name" and "path"`);
29
+ }
30
+ const application = { name: value.name, path: value.path };
31
+ for (const field of OPTIONAL_FIELDS) {
32
+ const fieldValue = value[field];
33
+ if (typeof fieldValue === "string")
34
+ application[field] = fieldValue;
35
+ }
36
+ return application;
37
+ }
38
+ /** The wire form of one application: absent fields stay absent, never undefined. */
39
+ function applicationWire(application) {
40
+ const wire = { name: application.name, path: application.path };
41
+ for (const field of OPTIONAL_FIELDS) {
42
+ const value = application[field];
43
+ if (typeof value === "string")
44
+ wire[field] = value;
45
+ }
46
+ return wire;
47
+ }
48
+ /** Parse an `--applications` file: a JSON array of `{ name, path, … }`. */
49
+ export function parseApplications(text) {
50
+ let parsed;
51
+ try {
52
+ parsed = JSON.parse(text);
53
+ }
54
+ catch (error) {
55
+ throw new Error(`not JSON (${error instanceof Error ? error.message : String(error)})`, {
56
+ cause: error,
57
+ });
58
+ }
59
+ if (!Array.isArray(parsed))
60
+ throw new Error("must be a JSON array of applications");
61
+ return parsed.map((value, index) => toApplication(value, `application ${index + 1}`));
62
+ }
63
+ export function createHeadlessHandlers(capabilities = {}) {
64
+ const applications = capabilities.applications ?? [];
65
+ const clipboard = capabilities.clipboard ?? "";
66
+ const frontmost = capabilities.frontmost;
67
+ return {
68
+ ...(capabilities.aiAnswer === undefined ? {} : { "ai.ask": () => capabilities.aiAnswer }),
69
+ ...(capabilities.browserTabs === undefined
70
+ ? {}
71
+ : createBrowserHandlers(capabilities.browserTabs)),
72
+ ...(capabilities.oauthCode === undefined
73
+ ? {}
74
+ : { "oauth.authorize": () => ({ authorizationCode: capabilities.oauthCode }) }),
75
+ "selection.text": (params) => {
76
+ readParams(params, "selection.text");
77
+ if (!capabilities.selectedText)
78
+ throw new Error("No text selected");
79
+ return capabilities.selectedText;
80
+ },
81
+ "selection.files": (params) => {
82
+ readParams(params, "selection.files");
83
+ return (capabilities.selectedFiles ?? []).map((path) => ({ path }));
84
+ },
85
+ "applications.frontmost": (params) => {
86
+ readParams(params, "applications.frontmost");
87
+ const application = applications.find((app) => frontmost !== undefined && app.bundleId === frontmost);
88
+ return applicationWire(application ?? {
89
+ name: frontmost ?? "Headless",
90
+ path: "",
91
+ ...(frontmost === undefined ? {} : { bundleId: frontmost }),
92
+ });
93
+ },
94
+ "applications.list": (params) => {
95
+ // The supplied list is the whole answer: a headless run cannot narrow it
96
+ // to the applications that can open one path.
97
+ const { path } = readParams(params, "applications.list");
98
+ if (path !== undefined && typeof path !== "string") {
99
+ throw new Error('applications.list: "path" must be a string');
100
+ }
101
+ return applications.map(applicationWire);
102
+ },
103
+ "applications.default": (params) => {
104
+ const { path } = readParams(params, "applications.default");
105
+ if (typeof path !== "string" || path === "") {
106
+ throw new Error('applications.default: "path" must be a non-empty string');
107
+ }
108
+ // No application database exists, so no path has a default application;
109
+ // Raycast rejects in exactly that case.
110
+ throw new Error(`No default application for ${path}`);
111
+ },
112
+ // Window management is client state. Without a client there is one empty
113
+ // desktop, no windows and no active window, so the read-only calls answer
114
+ // neutrally and `window.setBounds` stays denied.
115
+ "window.desktops": (params) => {
116
+ readParams(params, "window.desktops");
117
+ return [
118
+ {
119
+ id: "headless-desktop",
120
+ screenId: "headless-screen",
121
+ active: true,
122
+ type: "User",
123
+ size: { width: 0, height: 0 },
124
+ },
125
+ ];
126
+ },
127
+ "window.list": (params) => {
128
+ readParams(params, "window.list");
129
+ return [];
130
+ },
131
+ "window.active": (params) => {
132
+ readParams(params, "window.active");
133
+ throw new Error("No active window");
134
+ },
135
+ "clipboard.read": (params) => {
136
+ const { offset } = readParams(params, "clipboard.read");
137
+ if (offset !== undefined) {
138
+ if (typeof offset !== "number" ||
139
+ !Number.isInteger(offset) ||
140
+ offset < 0 ||
141
+ offset > MAX_OFFSET) {
142
+ throw new Error(`clipboard.read: "offset" must be an integer between 0 and ${MAX_OFFSET}`);
143
+ }
144
+ // There is no history to read from, so only the current entry has content.
145
+ return { text: offset === 0 ? clipboard : "" };
146
+ }
147
+ return { text: clipboard };
148
+ },
149
+ "command.metadata": (params) => {
150
+ const { subtitle } = readParams(params, "command.metadata");
151
+ if (subtitle !== undefined && subtitle !== null && typeof subtitle !== "string") {
152
+ throw new Error('command.metadata: "subtitle" must be a string or null');
153
+ }
154
+ },
155
+ "preferences.open": (params) => {
156
+ const { scope } = readParams(params, "preferences.open");
157
+ if (scope !== "extension" && scope !== "command") {
158
+ throw new Error('preferences.open: "scope" must be "extension" or "command"');
159
+ }
160
+ },
161
+ };
162
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./headless.js";
2
+ export * from "./browser.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./headless.js";
2
+ export * from "./browser.js";
package/package.json CHANGED
@@ -1,31 +1,36 @@
1
1
  {
2
2
  "name": "@blastlauncher/cli",
3
- "version": "1.0.5",
4
- "description": "Blast CLI Tools",
5
- "main": "dist/index.cjs",
6
- "bin": {
7
- "blast": "dist/index.cjs"
3
+ "type": "module",
4
+ "exports": {
5
+ ".": {
6
+ "types": "./dist/index.d.ts",
7
+ "import": "./dist/index.js",
8
+ "default": "./dist/index.js"
9
+ }
8
10
  },
9
11
  "dependencies": {
10
- "commander": "^10.0.0",
11
- "esbuild": "^0.17.5",
12
- "esbuild-node-externals": "^1.6.0",
13
- "fast-glob": "^3.2.12",
14
- "fs-extra": "^11.1.0",
15
- "semver": "^7.3.8",
16
- "tempy": "^3.0.0"
12
+ "@blastlauncher/engine": "3.0.0-alpha.1",
13
+ "@blastlauncher/protocol": "3.0.0-alpha.1",
14
+ "@blastlauncher/shared-deps": "3.0.0-alpha.1"
17
15
  },
18
- "repository": "https://github.com/BlastLauncher/blast.git",
19
- "devDependencies": {
20
- "@types/fs-extra": "^9.0.13",
21
- "pkg": "^5.8.0"
16
+ "version": "3.0.0-alpha.1",
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/BlastLauncher/blast.git",
21
+ "directory": "packages/cli"
22
22
  },
23
- "pkg": {
24
- "outputPath": "dist",
25
- "outputName": "blast"
23
+ "homepage": "https://github.com/BlastLauncher/blast#readme",
24
+ "engines": {
25
+ "node": ">=24.20.0"
26
26
  },
27
- "scripts": {
28
- "build": "node build.js",
29
- "package": "pkg ."
27
+ "files": [
28
+ "dist",
29
+ "schema",
30
+ "LICENSE",
31
+ "README.md"
32
+ ],
33
+ "bin": {
34
+ "blast": "./dist/cli.js"
30
35
  }
31
- }
36
+ }
package/CHANGELOG.md DELETED
@@ -1,31 +0,0 @@
1
- # @blastlauncher/cli
2
-
3
- ## 1.0.5
4
-
5
- ### Patch Changes
6
-
7
- - Update README and fixing `prepublishOnly` script
8
-
9
- ## 1.0.4
10
-
11
- ### Patch Changes
12
-
13
- - Replace raycast extension publish command
14
-
15
- ## 1.0.3
16
-
17
- ### Patch Changes
18
-
19
- - Fix package should run build before publish
20
-
21
- ## 1.0.2
22
-
23
- ### Patch Changes
24
-
25
- - Run build in publish command
26
-
27
- ## 1.0.1
28
-
29
- ### Patch Changes
30
-
31
- - Initial npm publish
package/build.js DELETED
@@ -1,15 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-var-requires */
2
- const esbuild = require("esbuild");
3
- const { nodeExternalsPlugin } = require("esbuild-node-externals");
4
-
5
- esbuild.build({
6
- entryPoints: ["index.js"],
7
- bundle: true,
8
- platform: "node",
9
- outfile: "dist/index.cjs",
10
- plugins: [
11
- nodeExternalsPlugin({
12
- allowList: ["tempy"],
13
- }),
14
- ],
15
- });
package/dist/cli-linux DELETED
@@ -1,4 +0,0 @@
1
- [diffend] Oversized file quarantined before diffing.
2
- name: package/dist/cli-linux
3
- size: 46896639 bytes
4
- sha256: 5e0e21e6d8327f98b9c332526c169d484e216edeeb2072daf5cd427173379214
package/dist/cli-macos DELETED
@@ -1,4 +0,0 @@
1
- [diffend] Oversized file quarantined before diffing.
2
- name: package/dist/cli-macos
3
- size: 48710384 bytes
4
- sha256: 8fdf72b70b24171dbcd165450112e59836cc8022f5152cc530b3207e96fb0423
package/dist/cli-win.exe DELETED
Binary file