forkit-connect 0.1.22 → 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.
Binary file
package/dist/cli.js CHANGED
@@ -13,6 +13,7 @@ const daemon_1 = require("./v1/daemon");
13
13
  const service_1 = require("./v1/service");
14
14
  const discovery_1 = require("./v1/discovery");
15
15
  const heartbeat_1 = require("./v1/heartbeat");
16
+ const startup_1 = require("./v1/startup");
16
17
  const runtime_activity_1 = require("./v1/runtime-activity");
17
18
  const runtime_editor_activity_1 = require("./v1/runtime-editor-activity");
18
19
  const runtime_observation_runner_1 = require("./v1/runtime-observation-runner");
@@ -81,6 +82,7 @@ const PUBLIC_COMMANDS = [
81
82
  ['login', 'Link this device to your Forkit.dev account'],
82
83
  ['logout', 'Remove the stored Forkit.dev session from this device'],
83
84
  ['status', 'Show account, scope, daemon, and queue status'],
85
+ ['startup', 'Enable, disable, or inspect CLI startup on login'],
84
86
  ['changes', 'View all collected local changes and queued sync items'],
85
87
  ['start', 'Open the interactive launcher or start foreground sync'],
86
88
  ['stop', 'Stop the local Connect daemon'],
@@ -117,14 +119,28 @@ const ADVANCED_COMMAND_GROUPS = [
117
119
  ['notify', 'Notification preview and delivery controls'],
118
120
  ];
119
121
  function usage() {
120
- printCliHeader('CLI', 'Precise local review, runtime detection, and registration handoff.');
121
- console.log(cliKeyLine('usage', 'forkit-connect <command> [options]'));
122
- console.log(cliKeyLine('scope', 'forkit-connect workspace <list|select|create|status>'));
122
+ printCliHeader('CLI', 'Local AI discovery, review, and registration from your terminal.');
123
+ console.log(cliKeyLine('command', 'forkit-connect <command> [options]'));
124
+ console.log(cliKeyLine('always on', 'forkit-connect startup enable'));
125
+ console.log(cliKeyLine('review', 'forkit-connect inbox'));
123
126
  console.log(cliKeyLine('runtime', 'forkit-connect runtime <register|review|status>'));
124
- printCliSection('Core');
125
- for (const [command, description] of PUBLIC_COMMANDS) {
126
- console.log(cliKeyLine(command, description));
127
- }
127
+ printCliSection('Start Here');
128
+ console.log(cliKeyLine('login', 'Connect this device to your Forkit.dev account'));
129
+ console.log(cliKeyLine('scan', 'Detect local models, runtimes, and agents'));
130
+ console.log(cliKeyLine('inbox', 'Review what needs action'));
131
+ console.log(cliKeyLine('startup', 'Keep Connect running from sign-in'));
132
+ printCliSection('Daily');
133
+ console.log(cliKeyLine('status', 'See account, queue, and daemon state'));
134
+ console.log(cliKeyLine('start', 'Open the interactive local console'));
135
+ console.log(cliKeyLine('sync', 'Flush queued local metadata'));
136
+ console.log(cliKeyLine('stop', 'Stop the background daemon'));
137
+ printCliSection('Governed');
138
+ console.log(cliKeyLine('workspace', 'Optional workspace and project scope'));
139
+ console.log(cliKeyLine('register', 'Register ready local models'));
140
+ console.log(cliKeyLine('runtime', 'Register or review local runtimes'));
141
+ printCliSection('Safety');
142
+ console.log(cliKeyLine('ignore', 'Deny one detected local model on this device'));
143
+ console.log(cliKeyLine('doctor', 'Check notifications, storage, and runtime wiring'));
128
144
  printCliSection('Flags');
129
145
  console.log(cliKeyLine('--json', 'Machine-readable output when supported'));
130
146
  console.log(cliKeyLine('--all-ready', 'Register every ready local model in the current scope'));
@@ -2130,6 +2146,7 @@ function printPublicStatusOverview(status) {
2130
2146
  console.log(cliKeyLine('device', status.device_paired ? 'paired' : 'approval pending'));
2131
2147
  console.log(cliKeyLine('scope', formatCliScopeLabel(status.workspace_id, status.project_id)));
2132
2148
  console.log(cliKeyLine('daemon', status.daemon_status));
2149
+ console.log(cliKeyLine('background', status.daemon_status === 'running' ? 'always on' : 'start required'));
2133
2150
  console.log(cliKeyLine('privacy', status.privacy_mode));
2134
2151
  console.log(cliKeyLine('inventory', joinCliSummary([
2135
2152
  formatCliCompactCount(status.models_discovered, 'model'),
@@ -2150,6 +2167,47 @@ function printPublicStatusOverview(status) {
2150
2167
  console.log(cliKeyLine('warning', 'Local governed scope exists, but login is still required.'));
2151
2168
  }
2152
2169
  }
2170
+ function formatStartupSummary(status) {
2171
+ if (!status.supported)
2172
+ return 'not supported';
2173
+ if (status.enabled === true)
2174
+ return 'enabled';
2175
+ if (status.enabled === false)
2176
+ return 'disabled';
2177
+ return 'unknown';
2178
+ }
2179
+ function printStartupStatusLine(status) {
2180
+ console.log(cliKeyLine('startup', formatStartupSummary(status)));
2181
+ }
2182
+ function printPublicStartupStatus(status) {
2183
+ const enabled = status.enabled === true;
2184
+ const platform = status.mode === 'launch_agent'
2185
+ ? 'macOS LaunchAgent'
2186
+ : status.mode === 'registry_run'
2187
+ ? 'Windows sign-in registry'
2188
+ : status.mode === 'systemd_user'
2189
+ ? 'Linux user service'
2190
+ : 'not available';
2191
+ printCliHeader('Startup', 'Keep Forkit Connect running from sign-in without a tray app.');
2192
+ console.log(cliKeyLine('auto start', enabled ? 'on' : status.enabled === false ? 'off' : 'unknown'));
2193
+ console.log(cliKeyLine('behavior', enabled
2194
+ ? 'Forkit Connect launches its background daemon when you sign in.'
2195
+ : 'Forkit Connect starts only when you run it manually.'));
2196
+ console.log(cliKeyLine('platform', platform));
2197
+ console.log(cliKeyLine('next', enabled ? 'forkit-connect startup disable' : 'forkit-connect startup enable'));
2198
+ if (hasFlag('--advanced-help') && status.configPath) {
2199
+ console.log(cliKeyLine('config', status.configPath));
2200
+ }
2201
+ }
2202
+ function getNotificationReadiness() {
2203
+ if (process.platform === 'darwin') {
2204
+ return 'Forkit Connect click-through ready';
2205
+ }
2206
+ if (process.platform === 'win32') {
2207
+ return 'native Windows toasts ready';
2208
+ }
2209
+ return 'native desktop alerts when a GUI session is available';
2210
+ }
2153
2211
  function getOtherReadyCount(readyToConnectCount, draftFirstCount) {
2154
2212
  return Math.max(0, readyToConnectCount - draftFirstCount);
2155
2213
  }
@@ -2630,6 +2688,7 @@ async function run() {
2630
2688
  const runPublicConnectStatus = async () => {
2631
2689
  const sessionState = await checkBackendSessionState(service);
2632
2690
  const secureStorage = service.getCredentialStoreStatus();
2691
+ const startupStatus = (0, startup_1.getCliStartupStatus)();
2633
2692
  if (service.readSessionRef()) {
2634
2693
  await withTimeout(service.refreshEffectiveBinding(), STATUS_BINDING_TIMEOUT_MS, undefined);
2635
2694
  }
@@ -2655,6 +2714,10 @@ async function run() {
2655
2714
  session_truth: accountTrusted ? 'account_verified_or_offline' : 'local_scope_cached_login_required',
2656
2715
  local_scope_cached: !accountTrusted && localScopeCached,
2657
2716
  binding_truth: accountTrusted ? 'account_binding_active' : 'account_login_required',
2717
+ startup_on_login: startupStatus,
2718
+ notifications: {
2719
+ readiness: getNotificationReadiness(),
2720
+ },
2658
2721
  secure_storage: {
2659
2722
  backend: secureStorage.backend,
2660
2723
  available: secureStorage.available,
@@ -2670,8 +2733,28 @@ async function run() {
2670
2733
  if (!secureStorage.available || secureStorage.plaintextFallbackActive) {
2671
2734
  console.log(cliKeyLine('secure note', secureStorage.detail));
2672
2735
  }
2736
+ console.log(cliKeyLine('notifications', getNotificationReadiness()));
2737
+ printStartupStatusLine(startupStatus);
2673
2738
  printPublicStatusGuidance(displayOverview, sessionState);
2674
2739
  };
2740
+ const runPublicStartupStatus = () => {
2741
+ const startupStatus = (0, startup_1.getCliStartupStatus)();
2742
+ if (hasFlag('--json')) {
2743
+ printJson(startupStatus);
2744
+ return;
2745
+ }
2746
+ printPublicStartupStatus(startupStatus);
2747
+ };
2748
+ const runPublicStartupEnable = () => {
2749
+ const startupStatus = (0, startup_1.enableCliStartup)(process.execPath, __filename);
2750
+ printPublicStartupStatus(startupStatus);
2751
+ console.log('[forkit-connect] CLI startup on login is enabled. The background daemon will come up when your session starts.');
2752
+ };
2753
+ const runPublicStartupDisable = () => {
2754
+ const startupStatus = (0, startup_1.disableCliStartup)();
2755
+ printPublicStartupStatus(startupStatus);
2756
+ console.log('[forkit-connect] CLI startup on login is disabled. Use forkit-connect stop if you also want to stop the daemon right now.');
2757
+ };
2675
2758
  const runPublicConnectInbox = async () => {
2676
2759
  const forceRefresh = hasFlag('--refresh');
2677
2760
  const inbox = service.getSmartRegistrationInbox({
@@ -4934,6 +5017,24 @@ async function run() {
4934
5017
  await runPublicConnectStatus();
4935
5018
  return;
4936
5019
  }
5020
+ if (command === 'startup') {
5021
+ const subcommand = args[1] || 'status';
5022
+ if (subcommand === 'status') {
5023
+ runPublicStartupStatus();
5024
+ return;
5025
+ }
5026
+ if (subcommand === 'enable') {
5027
+ runPublicStartupEnable();
5028
+ return;
5029
+ }
5030
+ if (subcommand === 'disable') {
5031
+ runPublicStartupDisable();
5032
+ return;
5033
+ }
5034
+ showUsage();
5035
+ process.exitCode = 1;
5036
+ return;
5037
+ }
4937
5038
  if (command === 'changes') {
4938
5039
  runPublicCollectedChanges();
4939
5040
  return;
@@ -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
@@ -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 targetUrl = notificationTargetUrl(candidate);
214
- const resolvedNotifier = resolveTerminalNotifierPath();
363
+ const resolvedNotifier = resolveBrandedMacNotifierBinary() ?? resolveTerminalNotifierPath();
215
364
  if (resolvedNotifier) {
216
- const openCommand = `/usr/bin/open '${targetUrl.replace(/'/g, "'\\''")}'`;
217
- const result = (0, node_child_process_1.spawnSync)(resolvedNotifier, [
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
- ? `terminal-notifier available at ${notifierPath}`
8934
- : 'terminal-notifier is not installed. Notifications will be recorded locally but click-through will not work on macOS.',
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.22",
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",