forkit-connect 0.1.21 → 0.1.23
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/assets/connect-notifier-512.png +0 -0
- package/dist/cli.js +508 -64
- package/dist/v1/native-shell.d.ts +23 -0
- package/dist/v1/native-shell.js +170 -0
- package/dist/v1/service.js +164 -8
- package/dist/v1/startup.d.ts +12 -0
- package/dist/v1/startup.js +229 -0
- package/package.json +3 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ReleaseCheckResult } from './update';
|
|
2
|
+
export interface NativeShellStatus {
|
|
3
|
+
supported: boolean;
|
|
4
|
+
installed: boolean;
|
|
5
|
+
platform: 'macos' | 'windows' | 'linux';
|
|
6
|
+
installationPath: string | null;
|
|
7
|
+
trayAvailable: boolean;
|
|
8
|
+
startupAvailable: boolean;
|
|
9
|
+
directInstallerAvailable: boolean;
|
|
10
|
+
downloadUrl: string | null;
|
|
11
|
+
releaseNotesUrl: string | null;
|
|
12
|
+
note: string;
|
|
13
|
+
}
|
|
14
|
+
export interface NativeShellOpenResult {
|
|
15
|
+
ok: boolean;
|
|
16
|
+
opened: 'installed' | 'download';
|
|
17
|
+
installationPath: string | null;
|
|
18
|
+
downloadUrl: string | null;
|
|
19
|
+
message: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function getNativeShellStatus(updateCheck?: ReleaseCheckResult): Promise<NativeShellStatus>;
|
|
22
|
+
export declare function openNativeShell(updateCheck?: ReleaseCheckResult): Promise<NativeShellOpenResult>;
|
|
23
|
+
//# sourceMappingURL=native-shell.d.ts.map
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getNativeShellStatus = getNativeShellStatus;
|
|
7
|
+
exports.openNativeShell = openNativeShell;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const node_child_process_1 = require("node:child_process");
|
|
12
|
+
const update_1 = require("./update");
|
|
13
|
+
const CONNECT_DESKTOP_FALLBACK_URL = 'https://www.forkit.dev/connect';
|
|
14
|
+
function normalizePlatform() {
|
|
15
|
+
if (process.platform === 'darwin')
|
|
16
|
+
return 'macos';
|
|
17
|
+
if (process.platform === 'win32')
|
|
18
|
+
return 'windows';
|
|
19
|
+
return 'linux';
|
|
20
|
+
}
|
|
21
|
+
function getCandidatePaths(platform) {
|
|
22
|
+
const home = node_os_1.default.homedir();
|
|
23
|
+
if (platform === 'macos') {
|
|
24
|
+
return [
|
|
25
|
+
'/Applications/Forkit Connect.app',
|
|
26
|
+
node_path_1.default.join(home, 'Applications', 'Forkit Connect.app'),
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
if (platform === 'windows') {
|
|
30
|
+
const localAppData = process.env.LOCALAPPDATA || node_path_1.default.join(home, 'AppData', 'Local');
|
|
31
|
+
const programFiles = process.env.ProgramFiles || 'C:\\Program Files';
|
|
32
|
+
const programFilesX86 = process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';
|
|
33
|
+
return [
|
|
34
|
+
node_path_1.default.join(localAppData, 'Programs', 'Forkit Connect', 'Forkit Connect.exe'),
|
|
35
|
+
node_path_1.default.join(programFiles, 'Forkit Connect', 'Forkit Connect.exe'),
|
|
36
|
+
node_path_1.default.join(programFilesX86, 'Forkit Connect', 'Forkit Connect.exe'),
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
return [
|
|
40
|
+
node_path_1.default.join(home, '.local', 'bin', 'forkit-connect-desktop'),
|
|
41
|
+
node_path_1.default.join(home, '.local', 'share', 'Forkit Connect', 'Forkit Connect.AppImage'),
|
|
42
|
+
'/usr/local/bin/forkit-connect-desktop',
|
|
43
|
+
'/opt/Forkit Connect/forkit-connect-desktop',
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
function findInstalledPath(platform) {
|
|
47
|
+
for (const candidate of getCandidatePaths(platform)) {
|
|
48
|
+
try {
|
|
49
|
+
if (node_fs_1.default.existsSync(candidate)) {
|
|
50
|
+
return candidate;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
function parseVersion(version) {
|
|
60
|
+
const cleaned = String(version || '').trim().replace(/^v/i, '').split('-')[0] || '0.0.0';
|
|
61
|
+
const [major, minor, patch] = cleaned.split('.').map((value) => Number.parseInt(value || '0', 10));
|
|
62
|
+
return [major || 0, minor || 0, patch || 0];
|
|
63
|
+
}
|
|
64
|
+
function compareVersions(left, right) {
|
|
65
|
+
const [leftMajor, leftMinor, leftPatch] = parseVersion(left);
|
|
66
|
+
const [rightMajor, rightMinor, rightPatch] = parseVersion(right);
|
|
67
|
+
if (leftMajor !== rightMajor)
|
|
68
|
+
return leftMajor - rightMajor;
|
|
69
|
+
if (leftMinor !== rightMinor)
|
|
70
|
+
return leftMinor - rightMinor;
|
|
71
|
+
return leftPatch - rightPatch;
|
|
72
|
+
}
|
|
73
|
+
function buildDownloadUrl(updateCheck) {
|
|
74
|
+
if (!updateCheck.ok) {
|
|
75
|
+
return CONNECT_DESKTOP_FALLBACK_URL;
|
|
76
|
+
}
|
|
77
|
+
if (compareVersions(updateCheck.manifest.latestVersion, updateCheck.currentVersion) < 0) {
|
|
78
|
+
return CONNECT_DESKTOP_FALLBACK_URL;
|
|
79
|
+
}
|
|
80
|
+
return updateCheck.downloadUrl || updateCheck.manifest.releaseNotesUrl || CONNECT_DESKTOP_FALLBACK_URL;
|
|
81
|
+
}
|
|
82
|
+
function hasDirectInstaller(updateCheck) {
|
|
83
|
+
if (!updateCheck.ok)
|
|
84
|
+
return false;
|
|
85
|
+
if (compareVersions(updateCheck.manifest.latestVersion, updateCheck.currentVersion) < 0) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return Boolean(updateCheck.downloadUrl);
|
|
89
|
+
}
|
|
90
|
+
function isInstallerOnlyRelease(updateCheck) {
|
|
91
|
+
if (!updateCheck.ok)
|
|
92
|
+
return false;
|
|
93
|
+
const manifest = updateCheck.manifest;
|
|
94
|
+
const integrity = manifest.integrity;
|
|
95
|
+
if (!integrity || typeof integrity !== 'object')
|
|
96
|
+
return false;
|
|
97
|
+
return integrity.installerOnly === true;
|
|
98
|
+
}
|
|
99
|
+
async function getNativeShellStatus(updateCheck) {
|
|
100
|
+
const platform = normalizePlatform();
|
|
101
|
+
const installationPath = findInstalledPath(platform);
|
|
102
|
+
const installed = Boolean(installationPath);
|
|
103
|
+
const resolvedUpdateCheck = updateCheck ?? await (0, update_1.checkForUpdates)();
|
|
104
|
+
const directInstallerAvailable = hasDirectInstaller(resolvedUpdateCheck);
|
|
105
|
+
const installerOnlyRelease = isInstallerOnlyRelease(resolvedUpdateCheck);
|
|
106
|
+
const downloadUrl = buildDownloadUrl(resolvedUpdateCheck);
|
|
107
|
+
const releaseNotesUrl = resolvedUpdateCheck.ok ? resolvedUpdateCheck.manifest.releaseNotesUrl : CONNECT_DESKTOP_FALLBACK_URL;
|
|
108
|
+
return {
|
|
109
|
+
supported: true,
|
|
110
|
+
installed,
|
|
111
|
+
platform,
|
|
112
|
+
installationPath,
|
|
113
|
+
trayAvailable: installed,
|
|
114
|
+
startupAvailable: installed,
|
|
115
|
+
directInstallerAvailable,
|
|
116
|
+
downloadUrl,
|
|
117
|
+
releaseNotesUrl,
|
|
118
|
+
note: installed
|
|
119
|
+
? 'Desktop app installed. Tray or menu-bar controls and launch at sign-in are handled there.'
|
|
120
|
+
: installerOnlyRelease
|
|
121
|
+
? 'Install the desktop app to get tray or menu-bar controls and launch at sign-in. In-app updates are not published for this release yet.'
|
|
122
|
+
: directInstallerAvailable
|
|
123
|
+
? 'Install the desktop app to get tray or menu-bar controls, launch at sign-in, and in-app updates.'
|
|
124
|
+
: 'The desktop app installer is not published for this platform yet. Use the Connect downloads page for the current release.',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function spawnDetached(command, args, cwd) {
|
|
128
|
+
const child = (0, node_child_process_1.spawn)(command, args, {
|
|
129
|
+
detached: true,
|
|
130
|
+
stdio: 'ignore',
|
|
131
|
+
windowsHide: true,
|
|
132
|
+
...(cwd ? { cwd } : {}),
|
|
133
|
+
});
|
|
134
|
+
child.unref();
|
|
135
|
+
}
|
|
136
|
+
function openInstalledShell(status) {
|
|
137
|
+
if (!status.installationPath) {
|
|
138
|
+
throw new Error('native_shell_missing');
|
|
139
|
+
}
|
|
140
|
+
if (status.platform === 'macos') {
|
|
141
|
+
spawnDetached('open', [status.installationPath]);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (status.platform === 'windows') {
|
|
145
|
+
spawnDetached(status.installationPath, [], node_path_1.default.dirname(status.installationPath));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
spawnDetached(status.installationPath, [], node_path_1.default.dirname(status.installationPath));
|
|
149
|
+
}
|
|
150
|
+
async function openNativeShell(updateCheck) {
|
|
151
|
+
const status = await getNativeShellStatus(updateCheck);
|
|
152
|
+
if (status.installed) {
|
|
153
|
+
openInstalledShell(status);
|
|
154
|
+
return {
|
|
155
|
+
ok: true,
|
|
156
|
+
opened: 'installed',
|
|
157
|
+
installationPath: status.installationPath,
|
|
158
|
+
downloadUrl: status.downloadUrl,
|
|
159
|
+
message: 'Opening the native Forkit Connect shell.',
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
opened: 'download',
|
|
165
|
+
installationPath: null,
|
|
166
|
+
downloadUrl: status.downloadUrl,
|
|
167
|
+
message: 'Install the Forkit Connect desktop app to enable tray or menu-bar controls and launch at sign-in.',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=native-shell.js.map
|
package/dist/v1/service.js
CHANGED
|
@@ -173,6 +173,33 @@ function notificationTargetUrl(candidate) {
|
|
|
173
173
|
}
|
|
174
174
|
return `${base}/?${params.toString()}`;
|
|
175
175
|
}
|
|
176
|
+
function notificationTargetCommand(candidate) {
|
|
177
|
+
const targetArgs = (() => {
|
|
178
|
+
if (!candidate) {
|
|
179
|
+
return ['inbox'];
|
|
180
|
+
}
|
|
181
|
+
if (candidate.suggested_action === 'reconnect_account' || candidate.type === 'credential_reconnect_needed') {
|
|
182
|
+
return ['login'];
|
|
183
|
+
}
|
|
184
|
+
if (candidate.suggested_action === 'sync_c2' || candidate.type === 'c2_sync_pending') {
|
|
185
|
+
return ['sync'];
|
|
186
|
+
}
|
|
187
|
+
if (candidate.suggested_action === 'view_pulse_history') {
|
|
188
|
+
return ['changes'];
|
|
189
|
+
}
|
|
190
|
+
return ['inbox'];
|
|
191
|
+
})();
|
|
192
|
+
const currentScriptPath = process.argv[1] && node_path_1.default.isAbsolute(process.argv[1])
|
|
193
|
+
? process.argv[1]
|
|
194
|
+
: process.argv[1]
|
|
195
|
+
? node_path_1.default.resolve(process.cwd(), process.argv[1])
|
|
196
|
+
: null;
|
|
197
|
+
if (currentScriptPath && node_fs_1.default.existsSync(currentScriptPath)) {
|
|
198
|
+
const quote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
199
|
+
return [quote(process.execPath), quote(currentScriptPath), ...targetArgs.map(quote)].join(' ');
|
|
200
|
+
}
|
|
201
|
+
return `forkit-connect ${targetArgs.join(' ')}`;
|
|
202
|
+
}
|
|
176
203
|
function hasLinuxGuiSession() {
|
|
177
204
|
return Boolean(normalizeSessionReference(process.env.DISPLAY)
|
|
178
205
|
|| normalizeSessionReference(process.env.WAYLAND_DISPLAY)
|
|
@@ -186,6 +213,129 @@ function resolveTerminalNotifierPath() {
|
|
|
186
213
|
const resolved = String(notifierPath.stdout || '').trim();
|
|
187
214
|
return resolved || null;
|
|
188
215
|
}
|
|
216
|
+
function resolveBundledTerminalNotifierBinary() {
|
|
217
|
+
try {
|
|
218
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
219
|
+
const notifierPackageJson = require.resolve('node-notifier/package.json');
|
|
220
|
+
const candidate = node_path_1.default.join(node_path_1.default.dirname(notifierPackageJson), 'vendor', 'mac.noindex', 'terminal-notifier.app', 'Contents', 'MacOS', 'terminal-notifier');
|
|
221
|
+
return node_fs_1.default.existsSync(candidate) ? candidate : null;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function resolveBrandedNotifierIconSource() {
|
|
228
|
+
const candidate = node_path_1.default.resolve(__dirname, '..', '..', 'assets', 'connect-notifier-512.png');
|
|
229
|
+
return node_fs_1.default.existsSync(candidate) ? candidate : null;
|
|
230
|
+
}
|
|
231
|
+
function ensureBrandedMacNotifierIcon(helperApp) {
|
|
232
|
+
const sourcePng = resolveBrandedNotifierIconSource();
|
|
233
|
+
if (!sourcePng) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const resourcesDir = node_path_1.default.join(helperApp, 'Contents', 'Resources');
|
|
237
|
+
const iconTarget = node_path_1.default.join(resourcesDir, 'Terminal.icns');
|
|
238
|
+
const iconMarker = node_path_1.default.join(resourcesDir, '.forkit-notifier-icon-v1');
|
|
239
|
+
if (node_fs_1.default.existsSync(iconMarker) && node_fs_1.default.existsSync(iconTarget)) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const iconsetDir = node_path_1.default.join(resourcesDir, 'ForkitConnect.iconset');
|
|
243
|
+
node_fs_1.default.rmSync(iconsetDir, { recursive: true, force: true });
|
|
244
|
+
node_fs_1.default.mkdirSync(iconsetDir, { recursive: true });
|
|
245
|
+
const variants = [
|
|
246
|
+
{ file: 'icon_16x16.png', size: 16 },
|
|
247
|
+
{ file: 'icon_16x16@2x.png', size: 32 },
|
|
248
|
+
{ file: 'icon_32x32.png', size: 32 },
|
|
249
|
+
{ file: 'icon_32x32@2x.png', size: 64 },
|
|
250
|
+
{ file: 'icon_128x128.png', size: 128 },
|
|
251
|
+
{ file: 'icon_128x128@2x.png', size: 256 },
|
|
252
|
+
{ file: 'icon_256x256.png', size: 256 },
|
|
253
|
+
{ file: 'icon_256x256@2x.png', size: 512 },
|
|
254
|
+
{ file: 'icon_512x512.png', size: 512 },
|
|
255
|
+
];
|
|
256
|
+
try {
|
|
257
|
+
for (const variant of variants) {
|
|
258
|
+
const outputPath = node_path_1.default.join(iconsetDir, variant.file);
|
|
259
|
+
const result = (0, node_child_process_1.spawnSync)('/usr/bin/sips', ['-z', String(variant.size), String(variant.size), sourcePng, '--out', outputPath], {
|
|
260
|
+
stdio: 'ignore',
|
|
261
|
+
});
|
|
262
|
+
if (result.status !== 0) {
|
|
263
|
+
throw new Error('icon_resize_failed');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
node_fs_1.default.copyFileSync(sourcePng, node_path_1.default.join(iconsetDir, 'icon_512x512@2x.png'));
|
|
267
|
+
const buildResult = (0, node_child_process_1.spawnSync)('/usr/bin/iconutil', ['-c', 'icns', iconsetDir, '-o', iconTarget], {
|
|
268
|
+
stdio: 'ignore',
|
|
269
|
+
});
|
|
270
|
+
if (buildResult.status !== 0) {
|
|
271
|
+
throw new Error('iconutil_failed');
|
|
272
|
+
}
|
|
273
|
+
node_fs_1.default.writeFileSync(iconMarker, 'forkit-connect-notifier-icon-v1\n', 'utf8');
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
node_fs_1.default.rmSync(iconsetDir, { recursive: true, force: true });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function notificationExecuteCommand(candidate) {
|
|
283
|
+
if (process.platform !== 'darwin') {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
const targetCommand = notificationTargetCommand(candidate);
|
|
287
|
+
const escapedCommand = `${targetCommand}; exec "\${SHELL:-/bin/zsh}" -l`
|
|
288
|
+
.replace(/\\/g, '\\\\')
|
|
289
|
+
.replace(/"/g, '\\"');
|
|
290
|
+
return [
|
|
291
|
+
'/usr/bin/osascript',
|
|
292
|
+
`-e 'tell application "Terminal" to activate'`,
|
|
293
|
+
`-e 'tell application "Terminal" to do script "${escapedCommand}"'`,
|
|
294
|
+
].join(' ');
|
|
295
|
+
}
|
|
296
|
+
function resolveBrandedMacNotifierBinary() {
|
|
297
|
+
if (process.platform !== 'darwin') {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
const bundledBinary = resolveBundledTerminalNotifierBinary();
|
|
301
|
+
if (!bundledBinary) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
const sourceApp = node_path_1.default.resolve(bundledBinary, '..', '..', '..');
|
|
305
|
+
const helperApp = node_path_1.default.join(node_os_1.default.homedir(), '.forkit-connect', 'vendor', 'Forkit Connect.app');
|
|
306
|
+
const helperBinary = node_path_1.default.join(helperApp, 'Contents', 'MacOS', 'terminal-notifier');
|
|
307
|
+
const helperPlist = node_path_1.default.join(helperApp, 'Contents', 'Info.plist');
|
|
308
|
+
const isAlreadyBranded = () => {
|
|
309
|
+
if (!node_fs_1.default.existsSync(helperBinary) || !node_fs_1.default.existsSync(helperPlist)) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
const result = (0, node_child_process_1.spawnSync)('plutil', ['-extract', 'CFBundleIdentifier', 'raw', '-o', '-', helperPlist], {
|
|
314
|
+
encoding: 'utf8',
|
|
315
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
316
|
+
});
|
|
317
|
+
return result.status === 0 && String(result.stdout || '').trim() === 'dev.forkit.connect.notifications';
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
if (!isAlreadyBranded()) {
|
|
324
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(helperApp), { recursive: true });
|
|
325
|
+
node_fs_1.default.rmSync(helperApp, { recursive: true, force: true });
|
|
326
|
+
node_fs_1.default.cpSync(sourceApp, helperApp, { recursive: true });
|
|
327
|
+
const replacements = [
|
|
328
|
+
['CFBundleIdentifier', 'dev.forkit.connect.notifications'],
|
|
329
|
+
['CFBundleName', 'Forkit Connect'],
|
|
330
|
+
['CFBundleDisplayName', 'Forkit Connect'],
|
|
331
|
+
];
|
|
332
|
+
for (const [key, value] of replacements) {
|
|
333
|
+
(0, node_child_process_1.spawnSync)('plutil', ['-replace', key, '-string', value, helperPlist], { stdio: 'ignore' });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
ensureBrandedMacNotifierIcon(helperApp);
|
|
337
|
+
return node_fs_1.default.existsSync(helperBinary) ? helperBinary : null;
|
|
338
|
+
}
|
|
189
339
|
function defaultNotificationSender(title, message, candidate) {
|
|
190
340
|
if (process.platform === 'linux') {
|
|
191
341
|
if (!hasLinuxGuiSession()) {
|
|
@@ -210,15 +360,21 @@ function defaultNotificationSender(title, message, candidate) {
|
|
|
210
360
|
}
|
|
211
361
|
}
|
|
212
362
|
if (process.platform === 'darwin') {
|
|
213
|
-
const
|
|
214
|
-
const resolvedNotifier = resolveTerminalNotifierPath();
|
|
363
|
+
const resolvedNotifier = resolveBrandedMacNotifierBinary() ?? resolveTerminalNotifierPath();
|
|
215
364
|
if (resolvedNotifier) {
|
|
216
|
-
const
|
|
217
|
-
const
|
|
365
|
+
const executeCommand = notificationExecuteCommand(candidate);
|
|
366
|
+
const args = [
|
|
218
367
|
'-title', title,
|
|
368
|
+
'-subtitle', 'Forkit Connect',
|
|
219
369
|
'-message', message,
|
|
220
|
-
'-execute', openCommand,
|
|
221
370
|
'-group', 'forkit-connect',
|
|
371
|
+
'-sender', 'dev.forkit.connect.notifications',
|
|
372
|
+
];
|
|
373
|
+
if (executeCommand) {
|
|
374
|
+
args.push('-execute', executeCommand);
|
|
375
|
+
}
|
|
376
|
+
const result = (0, node_child_process_1.spawnSync)(resolvedNotifier, [
|
|
377
|
+
...args,
|
|
222
378
|
], { stdio: 'ignore' });
|
|
223
379
|
if (result.status === 0) {
|
|
224
380
|
return true;
|
|
@@ -8925,13 +9081,13 @@ class ConnectV1Service {
|
|
|
8925
9081
|
: `Unavailable at ${ollama.endpoint}: ${ollama.error ?? 'unknown_error'}`,
|
|
8926
9082
|
});
|
|
8927
9083
|
if (process.platform === 'darwin') {
|
|
8928
|
-
const notifierPath = resolveTerminalNotifierPath();
|
|
9084
|
+
const notifierPath = resolveBrandedMacNotifierBinary() ?? resolveTerminalNotifierPath();
|
|
8929
9085
|
checks.push({
|
|
8930
9086
|
name: 'desktop_notifications',
|
|
8931
9087
|
status: notifierPath ? 'PASS' : 'WARN',
|
|
8932
9088
|
details: notifierPath
|
|
8933
|
-
?
|
|
8934
|
-
: '
|
|
9089
|
+
? 'Forkit Connect native notifications are ready with click-through support.'
|
|
9090
|
+
: 'Forkit Connect notifications are not ready yet on this Mac.',
|
|
8935
9091
|
});
|
|
8936
9092
|
}
|
|
8937
9093
|
const sessionRefSource = this.getSessionRefSource();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CliStartupStatus {
|
|
2
|
+
supported: boolean;
|
|
3
|
+
platform: 'macos' | 'windows' | 'linux';
|
|
4
|
+
enabled: boolean | null;
|
|
5
|
+
mode: 'launch_agent' | 'registry_run' | 'systemd_user' | 'unsupported';
|
|
6
|
+
configPath: string | null;
|
|
7
|
+
note: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function getCliStartupStatus(): CliStartupStatus;
|
|
10
|
+
export declare function enableCliStartup(processExecPath: string, cliPath: string): CliStartupStatus;
|
|
11
|
+
export declare function disableCliStartup(): CliStartupStatus;
|
|
12
|
+
//# sourceMappingURL=startup.d.ts.map
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getCliStartupStatus = getCliStartupStatus;
|
|
7
|
+
exports.enableCliStartup = enableCliStartup;
|
|
8
|
+
exports.disableCliStartup = disableCliStartup;
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
13
|
+
function normalizePlatform() {
|
|
14
|
+
if (process.platform === 'darwin')
|
|
15
|
+
return 'macos';
|
|
16
|
+
if (process.platform === 'win32')
|
|
17
|
+
return 'windows';
|
|
18
|
+
return 'linux';
|
|
19
|
+
}
|
|
20
|
+
function macLabel() {
|
|
21
|
+
return 'dev.forkit.connect.cli';
|
|
22
|
+
}
|
|
23
|
+
function macPlistPath() {
|
|
24
|
+
return node_path_1.default.join(node_os_1.default.homedir(), 'Library', 'LaunchAgents', `${macLabel()}.plist`);
|
|
25
|
+
}
|
|
26
|
+
function linuxServiceName() {
|
|
27
|
+
return 'forkit-connect.service';
|
|
28
|
+
}
|
|
29
|
+
function linuxServicePath() {
|
|
30
|
+
return node_path_1.default.join(node_os_1.default.homedir(), '.config', 'systemd', 'user', linuxServiceName());
|
|
31
|
+
}
|
|
32
|
+
function windowsRegistryKey() {
|
|
33
|
+
return 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run';
|
|
34
|
+
}
|
|
35
|
+
function windowsRegistryValueName() {
|
|
36
|
+
return 'ForkitConnectCLI';
|
|
37
|
+
}
|
|
38
|
+
function launchctlGuiTarget() {
|
|
39
|
+
return `gui/${process.getuid?.() ?? 0}`;
|
|
40
|
+
}
|
|
41
|
+
function quoteWindows(value) {
|
|
42
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
43
|
+
}
|
|
44
|
+
function startupCommand(processExecPath, cliPath) {
|
|
45
|
+
return {
|
|
46
|
+
executable: processExecPath,
|
|
47
|
+
args: [cliPath, 'daemon', 'run-loop'],
|
|
48
|
+
display: `${quoteWindows(processExecPath)} ${quoteWindows(cliPath)} daemon run-loop`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function writeMacPlist(processExecPath, cliPath) {
|
|
52
|
+
const command = startupCommand(processExecPath, cliPath);
|
|
53
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
54
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
55
|
+
<plist version="1.0">
|
|
56
|
+
<dict>
|
|
57
|
+
<key>Label</key>
|
|
58
|
+
<string>${macLabel()}</string>
|
|
59
|
+
<key>ProgramArguments</key>
|
|
60
|
+
<array>
|
|
61
|
+
<string>${command.executable}</string>
|
|
62
|
+
<string>${command.args[0]}</string>
|
|
63
|
+
<string>${command.args[1]}</string>
|
|
64
|
+
<string>${command.args[2]}</string>
|
|
65
|
+
</array>
|
|
66
|
+
<key>RunAtLoad</key>
|
|
67
|
+
<true/>
|
|
68
|
+
<key>KeepAlive</key>
|
|
69
|
+
<true/>
|
|
70
|
+
<key>WorkingDirectory</key>
|
|
71
|
+
<string>${process.cwd()}</string>
|
|
72
|
+
</dict>
|
|
73
|
+
</plist>
|
|
74
|
+
`;
|
|
75
|
+
const plistPath = macPlistPath();
|
|
76
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(plistPath), { recursive: true });
|
|
77
|
+
node_fs_1.default.writeFileSync(plistPath, plist, 'utf8');
|
|
78
|
+
return plistPath;
|
|
79
|
+
}
|
|
80
|
+
function writeLinuxService(processExecPath, cliPath) {
|
|
81
|
+
const command = startupCommand(processExecPath, cliPath);
|
|
82
|
+
const unit = `[Unit]
|
|
83
|
+
Description=Forkit Connect CLI background daemon
|
|
84
|
+
After=default.target
|
|
85
|
+
|
|
86
|
+
[Service]
|
|
87
|
+
Type=simple
|
|
88
|
+
ExecStart=${command.executable} ${command.args.join(' ')}
|
|
89
|
+
Restart=always
|
|
90
|
+
RestartSec=3
|
|
91
|
+
WorkingDirectory=${process.cwd()}
|
|
92
|
+
|
|
93
|
+
[Install]
|
|
94
|
+
WantedBy=default.target
|
|
95
|
+
`;
|
|
96
|
+
const servicePath = linuxServicePath();
|
|
97
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(servicePath), { recursive: true });
|
|
98
|
+
node_fs_1.default.writeFileSync(servicePath, unit, 'utf8');
|
|
99
|
+
return servicePath;
|
|
100
|
+
}
|
|
101
|
+
function getMacStatus() {
|
|
102
|
+
const plistPath = macPlistPath();
|
|
103
|
+
const exists = node_fs_1.default.existsSync(plistPath);
|
|
104
|
+
return {
|
|
105
|
+
supported: true,
|
|
106
|
+
platform: 'macos',
|
|
107
|
+
enabled: exists,
|
|
108
|
+
mode: 'launch_agent',
|
|
109
|
+
configPath: plistPath,
|
|
110
|
+
note: exists
|
|
111
|
+
? 'Forkit Connect CLI will start at sign-in through a LaunchAgent.'
|
|
112
|
+
: 'Forkit Connect CLI is not set to start at sign-in on this Mac.',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function getWindowsStatus() {
|
|
116
|
+
const query = (0, node_child_process_1.spawnSync)('reg', ['query', windowsRegistryKey(), '/v', windowsRegistryValueName()], {
|
|
117
|
+
encoding: 'utf8',
|
|
118
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
119
|
+
});
|
|
120
|
+
const enabled = query.status === 0;
|
|
121
|
+
return {
|
|
122
|
+
supported: true,
|
|
123
|
+
platform: 'windows',
|
|
124
|
+
enabled,
|
|
125
|
+
mode: 'registry_run',
|
|
126
|
+
configPath: windowsRegistryKey(),
|
|
127
|
+
note: enabled
|
|
128
|
+
? 'Forkit Connect CLI will start after sign-in through the Windows Run registry.'
|
|
129
|
+
: 'Forkit Connect CLI is not set to start after sign-in on Windows.',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function getLinuxStatus() {
|
|
133
|
+
const servicePath = linuxServicePath();
|
|
134
|
+
const exists = node_fs_1.default.existsSync(servicePath);
|
|
135
|
+
return {
|
|
136
|
+
supported: true,
|
|
137
|
+
platform: 'linux',
|
|
138
|
+
enabled: exists,
|
|
139
|
+
mode: 'systemd_user',
|
|
140
|
+
configPath: servicePath,
|
|
141
|
+
note: exists
|
|
142
|
+
? 'Forkit Connect CLI will start in the user session through systemd.'
|
|
143
|
+
: 'Forkit Connect CLI is not set to start in the user session on Linux.',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function getCliStartupStatus() {
|
|
147
|
+
const platform = normalizePlatform();
|
|
148
|
+
if (platform === 'macos')
|
|
149
|
+
return getMacStatus();
|
|
150
|
+
if (platform === 'windows')
|
|
151
|
+
return getWindowsStatus();
|
|
152
|
+
if (platform === 'linux')
|
|
153
|
+
return getLinuxStatus();
|
|
154
|
+
return {
|
|
155
|
+
supported: false,
|
|
156
|
+
platform,
|
|
157
|
+
enabled: null,
|
|
158
|
+
mode: 'unsupported',
|
|
159
|
+
configPath: null,
|
|
160
|
+
note: 'Startup management is not available on this platform.',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function enableCliStartup(processExecPath, cliPath) {
|
|
164
|
+
const platform = normalizePlatform();
|
|
165
|
+
if (platform === 'macos') {
|
|
166
|
+
const plistPath = writeMacPlist(processExecPath, cliPath);
|
|
167
|
+
(0, node_child_process_1.spawnSync)('launchctl', ['bootout', launchctlGuiTarget(), plistPath], { stdio: 'ignore' });
|
|
168
|
+
const bootstrap = (0, node_child_process_1.spawnSync)('launchctl', ['bootstrap', launchctlGuiTarget(), plistPath], { stdio: 'ignore' });
|
|
169
|
+
if (bootstrap.status !== 0) {
|
|
170
|
+
throw new Error('cli_startup_enable_failed_macos');
|
|
171
|
+
}
|
|
172
|
+
(0, node_child_process_1.spawnSync)('launchctl', ['kickstart', '-k', `${launchctlGuiTarget()}/${macLabel()}`], { stdio: 'ignore' });
|
|
173
|
+
return getMacStatus();
|
|
174
|
+
}
|
|
175
|
+
if (platform === 'windows') {
|
|
176
|
+
const command = startupCommand(processExecPath, cliPath);
|
|
177
|
+
const add = (0, node_child_process_1.spawnSync)('reg', [
|
|
178
|
+
'add',
|
|
179
|
+
windowsRegistryKey(),
|
|
180
|
+
'/v',
|
|
181
|
+
windowsRegistryValueName(),
|
|
182
|
+
'/t',
|
|
183
|
+
'REG_SZ',
|
|
184
|
+
'/d',
|
|
185
|
+
command.display,
|
|
186
|
+
'/f',
|
|
187
|
+
], { stdio: 'ignore' });
|
|
188
|
+
if (add.status !== 0) {
|
|
189
|
+
throw new Error('cli_startup_enable_failed_windows');
|
|
190
|
+
}
|
|
191
|
+
return getWindowsStatus();
|
|
192
|
+
}
|
|
193
|
+
if (platform === 'linux') {
|
|
194
|
+
const servicePath = writeLinuxService(processExecPath, cliPath);
|
|
195
|
+
const reload = (0, node_child_process_1.spawnSync)('systemctl', ['--user', 'daemon-reload'], { stdio: 'ignore' });
|
|
196
|
+
const enable = (0, node_child_process_1.spawnSync)('systemctl', ['--user', 'enable', '--now', linuxServiceName()], { stdio: 'ignore' });
|
|
197
|
+
if (reload.status !== 0 || enable.status !== 0 || !node_fs_1.default.existsSync(servicePath)) {
|
|
198
|
+
throw new Error('cli_startup_enable_failed_linux');
|
|
199
|
+
}
|
|
200
|
+
return getLinuxStatus();
|
|
201
|
+
}
|
|
202
|
+
throw new Error('cli_startup_unsupported');
|
|
203
|
+
}
|
|
204
|
+
function disableCliStartup() {
|
|
205
|
+
const platform = normalizePlatform();
|
|
206
|
+
if (platform === 'macos') {
|
|
207
|
+
const plistPath = macPlistPath();
|
|
208
|
+
(0, node_child_process_1.spawnSync)('launchctl', ['bootout', launchctlGuiTarget(), plistPath], { stdio: 'ignore' });
|
|
209
|
+
if (node_fs_1.default.existsSync(plistPath)) {
|
|
210
|
+
node_fs_1.default.rmSync(plistPath, { force: true });
|
|
211
|
+
}
|
|
212
|
+
return getMacStatus();
|
|
213
|
+
}
|
|
214
|
+
if (platform === 'windows') {
|
|
215
|
+
(0, node_child_process_1.spawnSync)('reg', ['delete', windowsRegistryKey(), '/v', windowsRegistryValueName(), '/f'], { stdio: 'ignore' });
|
|
216
|
+
return getWindowsStatus();
|
|
217
|
+
}
|
|
218
|
+
if (platform === 'linux') {
|
|
219
|
+
(0, node_child_process_1.spawnSync)('systemctl', ['--user', 'disable', '--now', linuxServiceName()], { stdio: 'ignore' });
|
|
220
|
+
(0, node_child_process_1.spawnSync)('systemctl', ['--user', 'daemon-reload'], { stdio: 'ignore' });
|
|
221
|
+
const servicePath = linuxServicePath();
|
|
222
|
+
if (node_fs_1.default.existsSync(servicePath)) {
|
|
223
|
+
node_fs_1.default.rmSync(servicePath, { force: true });
|
|
224
|
+
}
|
|
225
|
+
return getLinuxStatus();
|
|
226
|
+
}
|
|
227
|
+
throw new Error('cli_startup_unsupported');
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=startup.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forkit-connect",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"description": "Forkit Connect Local Engine - The Global AI Governance Fabric",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
|
+
"assets",
|
|
13
14
|
"README.md",
|
|
14
15
|
"QUICKSTART.md"
|
|
15
16
|
],
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
"better-sqlite3": "^12.10.0",
|
|
34
35
|
"cors": "^2.8.6",
|
|
35
36
|
"express": "^4.19.2",
|
|
37
|
+
"node-notifier": "^10.0.1",
|
|
36
38
|
"ps-list": "^8.1.1",
|
|
37
39
|
"uuid": "^10.0.0",
|
|
38
40
|
"ws": "^8.16.0",
|