blinkai 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/package.json +23 -0
- package/src/api.js +122 -0
- package/src/config.js +43 -0
- package/src/index.js +304 -0
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "blinkai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "BlinkAI command line client",
|
|
6
|
+
"bin": {
|
|
7
|
+
"blinkai": "src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"start": "node ./src/index.js",
|
|
14
|
+
"pack:check": "npm pack --dry-run",
|
|
15
|
+
"link:global": "npm link"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=20.0.0"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
function trimSlash(value) {
|
|
2
|
+
return String(value || "").replace(/\/$/, "");
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export class BlinkApiClient {
|
|
6
|
+
constructor({ apiBaseUrl, token = "" }) {
|
|
7
|
+
this.apiBaseUrl = trimSlash(apiBaseUrl);
|
|
8
|
+
this.token = token;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
setToken(token) {
|
|
12
|
+
this.token = token;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async request(path, init = {}) {
|
|
16
|
+
const headers = new Headers(init.headers || {});
|
|
17
|
+
if (this.token) {
|
|
18
|
+
headers.set("Authorization", `Bearer ${this.token}`);
|
|
19
|
+
}
|
|
20
|
+
if (init.body !== undefined && !headers.has("Content-Type")) {
|
|
21
|
+
headers.set("Content-Type", "application/json");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const response = await fetch(`${this.apiBaseUrl}${path}`, {
|
|
25
|
+
...init,
|
|
26
|
+
headers,
|
|
27
|
+
body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
|
|
28
|
+
});
|
|
29
|
+
const raw = await response.text();
|
|
30
|
+
let payload = null;
|
|
31
|
+
try {
|
|
32
|
+
payload = raw ? JSON.parse(raw) : null;
|
|
33
|
+
} catch {
|
|
34
|
+
payload = raw;
|
|
35
|
+
}
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const detail = payload && typeof payload === "object"
|
|
38
|
+
? payload.detail || payload.error?.message || payload.error
|
|
39
|
+
: payload;
|
|
40
|
+
throw new Error(String(detail || response.statusText || "Request failed"));
|
|
41
|
+
}
|
|
42
|
+
return payload;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
login(email, password) {
|
|
46
|
+
return this.request("/auth/login", {
|
|
47
|
+
method: "POST",
|
|
48
|
+
body: { email, password },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
register(email, username, password) {
|
|
53
|
+
return this.request("/auth/register", {
|
|
54
|
+
method: "POST",
|
|
55
|
+
body: { email, username, password },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getSession() {
|
|
60
|
+
return this.request("/cli/session");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
connectProvider(payload) {
|
|
64
|
+
return this.request("/cli/provider/connect", {
|
|
65
|
+
method: "POST",
|
|
66
|
+
body: payload,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setAgentName(name) {
|
|
71
|
+
return this.request("/cli/agent/name", {
|
|
72
|
+
method: "POST",
|
|
73
|
+
body: { name },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
sendChat(body, surface = "cli") {
|
|
78
|
+
return this.request("/cli/chat", {
|
|
79
|
+
method: "POST",
|
|
80
|
+
body: { body, surface },
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
registerGateway(payload) {
|
|
85
|
+
return this.request("/cli/gateways/register", {
|
|
86
|
+
method: "POST",
|
|
87
|
+
body: payload,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
heartbeatGateway(gatewayId) {
|
|
92
|
+
return this.request(`/cli/gateways/${encodeURIComponent(gatewayId)}/heartbeat`, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
nextGatewayCommand(gatewayId) {
|
|
98
|
+
return this.request(`/cli/gateways/${encodeURIComponent(gatewayId)}/commands/next`, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
completeGatewayCommand(commandId, payload) {
|
|
104
|
+
return this.request(`/cli/gateways/commands/${encodeURIComponent(commandId)}/complete`, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
body: payload,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
startCodexOauth(providerModel = "") {
|
|
111
|
+
const suffix = providerModel ? `?provider_model=${encodeURIComponent(providerModel)}` : "";
|
|
112
|
+
return this.request(`/mobile/oauth/openai-codex/start${suffix}`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
completeCodexOauth(authorizationInput) {
|
|
118
|
+
return this.request(`/mobile/oauth/openai-codex/complete?authorization_input=${encodeURIComponent(authorizationInput)}`, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const PROJECT_CLI_ROOT = path.resolve(CURRENT_DIR, "..");
|
|
9
|
+
const CONFIG_ROOT = process.env.OPENAGORA_CLI_HOME
|
|
10
|
+
? path.resolve(process.env.OPENAGORA_CLI_HOME)
|
|
11
|
+
: path.join(PROJECT_CLI_ROOT, ".state");
|
|
12
|
+
const CONFIG_PATH = path.join(CONFIG_ROOT, "config.json");
|
|
13
|
+
const RUNTIME_ROOT = path.join(CONFIG_ROOT, "runtime");
|
|
14
|
+
|
|
15
|
+
export function getConfigRoot() {
|
|
16
|
+
fs.mkdirSync(CONFIG_ROOT, { recursive: true });
|
|
17
|
+
fs.mkdirSync(RUNTIME_ROOT, { recursive: true });
|
|
18
|
+
return CONFIG_ROOT;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getRuntimeRoot() {
|
|
22
|
+
getConfigRoot();
|
|
23
|
+
return RUNTIME_ROOT;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function loadConfig() {
|
|
27
|
+
getConfigRoot();
|
|
28
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
29
|
+
return {
|
|
30
|
+
apiBaseUrl: process.env.BLINK_API_BASE_URL || "https://api.blinkai.online/api/v1",
|
|
31
|
+
token: "",
|
|
32
|
+
gatewayId: crypto.randomUUID(),
|
|
33
|
+
gatewayName: os.hostname(),
|
|
34
|
+
machineId: `${os.hostname()}-${os.userInfo().username}`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function saveConfig(config) {
|
|
41
|
+
getConfigRoot();
|
|
42
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf8");
|
|
43
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import readline from "node:readline/promises";
|
|
6
|
+
|
|
7
|
+
import { BlinkApiClient } from "./api.js";
|
|
8
|
+
import { loadConfig, saveConfig } from "./config.js";
|
|
9
|
+
|
|
10
|
+
const HELP = [
|
|
11
|
+
"/help",
|
|
12
|
+
"/session",
|
|
13
|
+
"/quit",
|
|
14
|
+
"",
|
|
15
|
+
"Any other line is sent to the shared BlinkAI account agent session.",
|
|
16
|
+
].join("\n");
|
|
17
|
+
|
|
18
|
+
async function main() {
|
|
19
|
+
const config = loadConfig();
|
|
20
|
+
const api = new BlinkApiClient({ apiBaseUrl: config.apiBaseUrl, token: config.token });
|
|
21
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
22
|
+
let background = null;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await ensureAuthenticated(api, config, rl);
|
|
26
|
+
const session = await ensureAccountAgentReady(api, rl);
|
|
27
|
+
const runtime = createGatewayRuntime();
|
|
28
|
+
let gateway = null;
|
|
29
|
+
|
|
30
|
+
if (runtime.enabled) {
|
|
31
|
+
runtime.start({ autonomous: false });
|
|
32
|
+
gateway = await api.registerGateway({
|
|
33
|
+
gateway_id: config.gatewayId,
|
|
34
|
+
name: config.gatewayName,
|
|
35
|
+
machine_id: config.machineId,
|
|
36
|
+
platform: `${process.platform}/${os.release()}`,
|
|
37
|
+
current_workspace: process.cwd(),
|
|
38
|
+
capabilities: {
|
|
39
|
+
browser: true,
|
|
40
|
+
shell: true,
|
|
41
|
+
filesystem: true,
|
|
42
|
+
screen: true,
|
|
43
|
+
provider: runtime.provider,
|
|
44
|
+
model: runtime.client?.model || "",
|
|
45
|
+
localTools: runtime.tools.spec().map((tool) => ({
|
|
46
|
+
name: tool.name,
|
|
47
|
+
description: tool.description,
|
|
48
|
+
})),
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
config.gatewayId = gateway.gateway_id;
|
|
52
|
+
config.gatewayName = gateway.name;
|
|
53
|
+
saveConfig(config);
|
|
54
|
+
background = startBackgroundLoops(api, config, runtime);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
process.stdout.write(
|
|
58
|
+
[
|
|
59
|
+
`BlinkAI CLI connected as @${session.user.username}`,
|
|
60
|
+
`Agent: ${session.mobile_agent.profile.agentName || "[unnamed]"}`,
|
|
61
|
+
`API: ${config.apiBaseUrl}`,
|
|
62
|
+
runtime.enabled
|
|
63
|
+
? `Gateway: ${gateway.name} (${gateway.gateway_id})`
|
|
64
|
+
: "Gateway: disabled in standalone npm package",
|
|
65
|
+
"",
|
|
66
|
+
].join("\n"),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
process.stdout.write(`${HELP}\n\n`);
|
|
70
|
+
|
|
71
|
+
while (true) {
|
|
72
|
+
const line = (await rl.question("> ")).trim();
|
|
73
|
+
if (!line) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (line === "/quit" || line === "/exit") {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
if (line === "/help") {
|
|
80
|
+
process.stdout.write(`${HELP}\n`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (line === "/session") {
|
|
84
|
+
const next = await api.getSession();
|
|
85
|
+
process.stdout.write(JSON.stringify(next.mobile_agent.profile, null, 2) + "\n");
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const result = await api.sendChat(line, "cli");
|
|
90
|
+
const reply = result.assistantMessage?.body || "[empty reply]";
|
|
91
|
+
process.stdout.write(`\n${reply}\n\n`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
background?.stop();
|
|
95
|
+
} finally {
|
|
96
|
+
background?.stop();
|
|
97
|
+
rl.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function createGatewayRuntime() {
|
|
102
|
+
return {
|
|
103
|
+
enabled: false,
|
|
104
|
+
provider: "",
|
|
105
|
+
client: null,
|
|
106
|
+
start() {},
|
|
107
|
+
tools: {
|
|
108
|
+
spec() {
|
|
109
|
+
return [];
|
|
110
|
+
},
|
|
111
|
+
async execute() {
|
|
112
|
+
throw new Error("Local gateway runtime is not bundled with the standalone BlinkAI CLI package.");
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function ensureAuthenticated(api, config, rl) {
|
|
119
|
+
if (config.token) {
|
|
120
|
+
try {
|
|
121
|
+
await api.getSession();
|
|
122
|
+
return;
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
process.stdout.write("No valid BlinkAI session found.\n");
|
|
128
|
+
const mode = (await rl.question("Choose auth mode: [1] sign in, [2] register\n> ")).trim();
|
|
129
|
+
const email = (await rl.question("Email: ")).trim();
|
|
130
|
+
const password = (await rl.question("Password: ")).trim();
|
|
131
|
+
|
|
132
|
+
let auth;
|
|
133
|
+
if (mode === "2") {
|
|
134
|
+
const username = (await rl.question("Username: ")).trim();
|
|
135
|
+
auth = await api.register(email, username, password);
|
|
136
|
+
} else {
|
|
137
|
+
auth = await api.login(email, password);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
config.token = auth.access_token;
|
|
141
|
+
api.setToken(auth.access_token);
|
|
142
|
+
saveConfig(config);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function ensureAccountAgentReady(api, rl) {
|
|
146
|
+
let session = await api.getSession();
|
|
147
|
+
const profile = session.mobile_agent.profile;
|
|
148
|
+
|
|
149
|
+
if (!profile.providerName) {
|
|
150
|
+
process.stdout.write("No provider is connected to this BlinkAI account agent.\n");
|
|
151
|
+
const providerChoice = (await rl.question("Provider: [1] openai-codex, [2] local.starlidigital.com, [3] custom api-key provider\n> ")).trim();
|
|
152
|
+
if (providerChoice === "1") {
|
|
153
|
+
const start = await api.startCodexOauth("");
|
|
154
|
+
process.stdout.write(`Open this URL in a browser:\n${start.authUrl}\n\n`);
|
|
155
|
+
const authorizationInput = (await rl.question("Paste the final redirect URL or code: ")).trim();
|
|
156
|
+
await api.completeCodexOauth(authorizationInput);
|
|
157
|
+
} else if (providerChoice === "2") {
|
|
158
|
+
await api.connectProvider({
|
|
159
|
+
provider_name: "local.starlidigital.com",
|
|
160
|
+
provider_base_url: "https://local.starlidigital.com/v1",
|
|
161
|
+
provider_model: "joji",
|
|
162
|
+
provider_api_key: "local-starlidigital",
|
|
163
|
+
});
|
|
164
|
+
} else {
|
|
165
|
+
const providerName = (await rl.question("Provider name: ")).trim();
|
|
166
|
+
const providerBaseUrl = (await rl.question("Provider base URL (optional): ")).trim();
|
|
167
|
+
const providerModel = (await rl.question("Provider model (optional): ")).trim();
|
|
168
|
+
const providerApiKey = (await rl.question("Provider API key/token: ")).trim();
|
|
169
|
+
await api.connectProvider({
|
|
170
|
+
provider_name: providerName,
|
|
171
|
+
provider_base_url: providerBaseUrl || null,
|
|
172
|
+
provider_model: providerModel || null,
|
|
173
|
+
provider_api_key: providerApiKey || null,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
session = await api.getSession();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (session.mobile_agent.profile.onboardingRequired || !session.mobile_agent.profile.agentName) {
|
|
180
|
+
const agentName = (await rl.question("Agent name: ")).trim();
|
|
181
|
+
await api.setAgentName(agentName);
|
|
182
|
+
session = await api.getSession();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return session;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function startBackgroundLoops(api, config, runtime) {
|
|
189
|
+
let stopped = false;
|
|
190
|
+
|
|
191
|
+
const heartbeatTimer = setInterval(() => {
|
|
192
|
+
if (stopped) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
void api.heartbeatGateway(config.gatewayId).catch(() => {});
|
|
196
|
+
}, 30_000);
|
|
197
|
+
|
|
198
|
+
const commandTimer = setInterval(() => {
|
|
199
|
+
if (stopped) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
void pollGatewayCommand(api, config, runtime);
|
|
203
|
+
}, 3_000);
|
|
204
|
+
|
|
205
|
+
void pollGatewayCommand(api, config, runtime);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
stop() {
|
|
209
|
+
stopped = true;
|
|
210
|
+
clearInterval(heartbeatTimer);
|
|
211
|
+
clearInterval(commandTimer);
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let commandInFlight = false;
|
|
217
|
+
|
|
218
|
+
async function pollGatewayCommand(api, config, runtime) {
|
|
219
|
+
if (commandInFlight) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
commandInFlight = true;
|
|
223
|
+
try {
|
|
224
|
+
const next = await api.nextGatewayCommand(config.gatewayId);
|
|
225
|
+
const command = next.item;
|
|
226
|
+
if (!command) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const toolName = String(command.payload?.toolName || "").trim();
|
|
231
|
+
const toolInput = command.payload?.input && typeof command.payload.input === "object" ? command.payload.input : {};
|
|
232
|
+
const taskSummary = String(command.payload?.taskSummary || "").trim();
|
|
233
|
+
if (!toolName) {
|
|
234
|
+
await api.completeGatewayCommand(command.command_id, {
|
|
235
|
+
ok: false,
|
|
236
|
+
error: "Gateway command was missing a tool name.",
|
|
237
|
+
});
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
process.stdout.write(`\n[gateway:${command.gateway_name}] ${taskSummary || toolName}\n`);
|
|
242
|
+
try {
|
|
243
|
+
const result = await executeGatewayTool(runtime, toolName, toolInput);
|
|
244
|
+
await api.completeGatewayCommand(command.command_id, {
|
|
245
|
+
ok: true,
|
|
246
|
+
result: { output: result },
|
|
247
|
+
});
|
|
248
|
+
process.stdout.write(`[gateway:${command.gateway_name}] completed\n> `);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
const publicError = formatGatewayError(error);
|
|
251
|
+
await api.completeGatewayCommand(command.command_id, {
|
|
252
|
+
ok: false,
|
|
253
|
+
error: publicError,
|
|
254
|
+
});
|
|
255
|
+
process.stdout.write(`[gateway:${command.gateway_name}] failed\n> `);
|
|
256
|
+
}
|
|
257
|
+
} finally {
|
|
258
|
+
commandInFlight = false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function executeGatewayTool(runtime, toolName, toolInput) {
|
|
263
|
+
const normalizedTool = String(toolName || "").trim();
|
|
264
|
+
const normalizedInput = toolInput && typeof toolInput === "object" ? toolInput : {};
|
|
265
|
+
if (normalizedTool === "write_file") {
|
|
266
|
+
normalizedInput.path = normalizeGatewayPath(normalizedInput.path);
|
|
267
|
+
}
|
|
268
|
+
const result = await runtime.tools.execute(normalizedTool, normalizedInput);
|
|
269
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function formatGatewayError(error) {
|
|
273
|
+
const message = String(error?.message || error || "").trim();
|
|
274
|
+
if (!message) {
|
|
275
|
+
return "Gateway task failed.";
|
|
276
|
+
}
|
|
277
|
+
const normalized = message.toLowerCase();
|
|
278
|
+
if (
|
|
279
|
+
normalized.includes("configuration is incomplete") ||
|
|
280
|
+
normalized.includes("missing provider cookie") ||
|
|
281
|
+
normalized.includes("missing provider csrf token") ||
|
|
282
|
+
normalized.includes("missing provider conversation id") ||
|
|
283
|
+
normalized.includes("missing provider api url")
|
|
284
|
+
) {
|
|
285
|
+
return "Gateway runtime is not configured for that provider.";
|
|
286
|
+
}
|
|
287
|
+
return message;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function normalizeGatewayPath(value) {
|
|
291
|
+
const raw = String(value || "").trim();
|
|
292
|
+
if (!raw) {
|
|
293
|
+
return raw;
|
|
294
|
+
}
|
|
295
|
+
return raw
|
|
296
|
+
.replace(/\[desktop\]/gi, path.join(os.homedir(), "Desktop"))
|
|
297
|
+
.replace(/\[home\]/gi, os.homedir())
|
|
298
|
+
.replace(/\//g, path.sep);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
main().catch((error) => {
|
|
302
|
+
process.stderr.write(`${String(error?.message || error)}\n`);
|
|
303
|
+
process.exitCode = 1;
|
|
304
|
+
});
|