forkit-connect 0.1.23 → 0.1.25

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/README.md CHANGED
@@ -6,7 +6,8 @@ Naming convention:
6
6
 
7
7
  - Product name: Forkit Connect
8
8
  - Command name: `forkit-connect`
9
- - Package name: `@forkit/connect`
9
+ - Published package name: `forkit-connect`
10
+ - Workspace dependency alias: `@forkit/connect`
10
11
 
11
12
  ## Install Surface
12
13
 
@@ -60,10 +61,13 @@ If `~/.local/bin` is already on your `PATH`, the command is immediately accessib
60
61
  - `forkit-connect login` — link this device to Forkit.dev with the device flow
61
62
  - `forkit-connect scan` — discover local runtimes and AI models
62
63
  - `forkit-connect inbox` — review the Smart Registration Inbox
64
+ - `forkit-connect connect <modelNameOrDiscoveryHash>` — prepare or sync a passport draft for a detected model
63
65
  - `forkit-connect runtime register` — register or reuse the current repo/worktree as a governed runtime
64
66
  - `forkit-connect runtime observe --gaid <gaid>` — emit a repo-scoped runtime journal for the current runtime target
65
67
  - `forkit-connect status` — show public Connect readiness status
66
68
  - `forkit-connect changes` — view collected local evidence, runtime signal history, and pending sync items
69
+ - `forkit-connect doctor` — run local environment, secure-storage, backend-session, and account checks
70
+ - `forkit-connect update-check` — inspect release metadata without enabling auto-update
67
71
  - `forkit-connect start` — start the local daemon loop
68
72
  - `forkit-connect stop` — stop the background daemon without deleting local state
69
73
  - `forkit-connect uninstall` — preview a cautious uninstall plan and write local backup logs
@@ -72,7 +76,7 @@ If `~/.local/bin` is already on your `PATH`, the command is immediately accessib
72
76
 
73
77
  ## Advanced Commands
74
78
 
75
- Advanced flows remain available under the same binary, including `connect`, `review`, `daemon`, `config`, `pulse`, `c2`, `train`, `agent`, notification utilities, and runtime target management.
79
+ Advanced flows remain available under the same binary, including `review`, `daemon`, `config`, `pulse`, `c2`, `train`, `agent`, notification utilities, and runtime target management.
76
80
 
77
81
  Runtime registration notes:
78
82
 
@@ -117,7 +121,7 @@ Then install the generated tarball into a clean directory:
117
121
  mkdir -p /tmp/forkit-connect-smoke
118
122
  cd /tmp/forkit-connect-smoke
119
123
  npm init -y
120
- npm install /absolute/path/to/forkit-connect-0.1.1.tgz
124
+ npm install /absolute/path/to/forkit-connect-0.1.24.tgz
121
125
  npx forkit-connect --help
122
126
  npx forkit-connect status
123
127
  npx forkit-connect inbox
package/dist/cli.js CHANGED
@@ -13,7 +13,6 @@ 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");
17
16
  const runtime_activity_1 = require("./v1/runtime-activity");
18
17
  const runtime_editor_activity_1 = require("./v1/runtime-editor-activity");
19
18
  const runtime_observation_runner_1 = require("./v1/runtime-observation-runner");
@@ -55,17 +54,17 @@ const CLI_FALLBACK_PLAN_LIMITS = {
55
54
  signal: {
56
55
  privatePassports: 25,
57
56
  draftPassports: 25,
58
- maxWorkspaces: 3,
59
- maxProjects: 3,
60
- maxGovernedPassports: 3,
57
+ maxWorkspaces: 1,
58
+ maxProjects: 1,
59
+ maxGovernedPassports: 25,
61
60
  runtimeSignalsPerMonth: 10000,
62
61
  },
63
62
  protocol: {
64
63
  privatePassports: 20,
65
64
  draftPassports: 20,
66
- maxWorkspaces: null,
67
- maxProjects: null,
68
- maxGovernedPassports: null,
65
+ maxWorkspaces: 10,
66
+ maxProjects: 10,
67
+ maxGovernedPassports: 20,
69
68
  runtimeSignalsPerMonth: 250000,
70
69
  },
71
70
  sovereign: {
@@ -82,7 +81,6 @@ const PUBLIC_COMMANDS = [
82
81
  ['login', 'Link this device to your Forkit.dev account'],
83
82
  ['logout', 'Remove the stored Forkit.dev session from this device'],
84
83
  ['status', 'Show account, scope, daemon, and queue status'],
85
- ['startup', 'Enable, disable, or inspect CLI startup on login'],
86
84
  ['changes', 'View all collected local changes and queued sync items'],
87
85
  ['start', 'Open the interactive launcher or start foreground sync'],
88
86
  ['stop', 'Stop the local Connect daemon'],
@@ -119,28 +117,14 @@ const ADVANCED_COMMAND_GROUPS = [
119
117
  ['notify', 'Notification preview and delivery controls'],
120
118
  ];
121
119
  function usage() {
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'));
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>'));
126
123
  console.log(cliKeyLine('runtime', 'forkit-connect runtime <register|review|status>'));
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'));
124
+ printCliSection('Core');
125
+ for (const [command, description] of PUBLIC_COMMANDS) {
126
+ console.log(cliKeyLine(command, description));
127
+ }
144
128
  printCliSection('Flags');
145
129
  console.log(cliKeyLine('--json', 'Machine-readable output when supported'));
146
130
  console.log(cliKeyLine('--all-ready', 'Register every ready local model in the current scope'));
@@ -269,10 +253,10 @@ function printDeviceLoginInstructions(start) {
269
253
  }
270
254
  function printSessionExportFallback(token) {
271
255
  void token;
272
- console.log('[forkit-connect] Approval succeeded, but secure credential storage is unavailable in this Linux session.');
273
- console.log('[forkit-connect] Forkit Connect can keep using this approved session in the current interactive run.');
274
- console.log('[forkit-connect] For persistent login, install libsecret-tools and run forkit-connect login again.');
275
- console.log('[forkit-connect] Headless automation may pass FORKIT_CONNECT_SESSION_REF explicitly, but Connect will not print session tokens.');
256
+ console.log('[forkit-connect] Approval succeeded, but secure credential storage is unavailable or timed out on this machine.');
257
+ console.log('[forkit-connect] Forkit Connect will not print session tokens. Persistent login requires working secure storage.');
258
+ console.log('[forkit-connect] macOS: unlock Keychain and run forkit-connect login again. Linux: install libsecret-tools and run inside a DBus user session.');
259
+ console.log('[forkit-connect] Headless automation may pass FORKIT_CONNECT_SESSION_REF explicitly from a secret manager.');
276
260
  }
277
261
  function hasLinuxGuiSession() {
278
262
  if (process.platform !== 'linux') {
@@ -951,7 +935,7 @@ function formatCliScopeLabel(workspaceId, projectId) {
951
935
  if (workspace) {
952
936
  return `${shortId(workspace)} / project needed`;
953
937
  }
954
- return 'solo / no workspace';
938
+ return 'personal device / workspace not selected';
955
939
  }
956
940
  function summarizeReviewQueueCounts(values) {
957
941
  const parts = [
@@ -1984,7 +1968,14 @@ async function maybeShowInteractiveAgentReviewTable(snapshot) {
1984
1968
  });
1985
1969
  }
1986
1970
  function formatWorkspaceStateCell(workspace) {
1987
- return [String(workspace.visibility || '').trim(), String(workspace.lifecycleStatus || '').trim()]
1971
+ const projectCount = Number(workspace.projectCountHint);
1972
+ const passportCount = Number(workspace.passportCountHint);
1973
+ return [
1974
+ String(workspace.visibility || '').trim(),
1975
+ String(workspace.lifecycleStatus || '').trim(),
1976
+ Number.isFinite(projectCount) && projectCount >= 0 ? `${projectCount} project${projectCount === 1 ? '' : 's'}` : null,
1977
+ Number.isFinite(passportCount) && passportCount >= 0 ? `${passportCount} passport${passportCount === 1 ? '' : 's'}` : null,
1978
+ ]
1988
1979
  .filter(Boolean)
1989
1980
  .join(' · ') || 'active';
1990
1981
  }
@@ -2146,7 +2137,6 @@ function printPublicStatusOverview(status) {
2146
2137
  console.log(cliKeyLine('device', status.device_paired ? 'paired' : 'approval pending'));
2147
2138
  console.log(cliKeyLine('scope', formatCliScopeLabel(status.workspace_id, status.project_id)));
2148
2139
  console.log(cliKeyLine('daemon', status.daemon_status));
2149
- console.log(cliKeyLine('background', status.daemon_status === 'running' ? 'always on' : 'start required'));
2150
2140
  console.log(cliKeyLine('privacy', status.privacy_mode));
2151
2141
  console.log(cliKeyLine('inventory', joinCliSummary([
2152
2142
  formatCliCompactCount(status.models_discovered, 'model'),
@@ -2167,47 +2157,6 @@ function printPublicStatusOverview(status) {
2167
2157
  console.log(cliKeyLine('warning', 'Local governed scope exists, but login is still required.'));
2168
2158
  }
2169
2159
  }
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
- }
2211
2160
  function getOtherReadyCount(readyToConnectCount, draftFirstCount) {
2212
2161
  return Math.max(0, readyToConnectCount - draftFirstCount);
2213
2162
  }
@@ -2688,7 +2637,6 @@ async function run() {
2688
2637
  const runPublicConnectStatus = async () => {
2689
2638
  const sessionState = await checkBackendSessionState(service);
2690
2639
  const secureStorage = service.getCredentialStoreStatus();
2691
- const startupStatus = (0, startup_1.getCliStartupStatus)();
2692
2640
  if (service.readSessionRef()) {
2693
2641
  await withTimeout(service.refreshEffectiveBinding(), STATUS_BINDING_TIMEOUT_MS, undefined);
2694
2642
  }
@@ -2696,7 +2644,12 @@ async function run() {
2696
2644
  const overview = withSmartInboxSnapshotCounts(service.getConnectStatusOverview({ includeInbox: false }));
2697
2645
  const localScopeCached = Boolean(String(overview.workspace_id || '').trim() || String(overview.project_id || '').trim());
2698
2646
  const displayOverview = accountTrusted
2699
- ? overview
2647
+ ? {
2648
+ ...overview,
2649
+ lifecycle_note: !localScopeCached && overview.device_paired
2650
+ ? 'Account connected. Personal scanning works; select workspace/project later for C2.'
2651
+ : overview.lifecycle_note,
2652
+ }
2700
2653
  : {
2701
2654
  ...overview,
2702
2655
  workspace_id: null,
@@ -2714,10 +2667,6 @@ async function run() {
2714
2667
  session_truth: accountTrusted ? 'account_verified_or_offline' : 'local_scope_cached_login_required',
2715
2668
  local_scope_cached: !accountTrusted && localScopeCached,
2716
2669
  binding_truth: accountTrusted ? 'account_binding_active' : 'account_login_required',
2717
- startup_on_login: startupStatus,
2718
- notifications: {
2719
- readiness: getNotificationReadiness(),
2720
- },
2721
2670
  secure_storage: {
2722
2671
  backend: secureStorage.backend,
2723
2672
  available: secureStorage.available,
@@ -2733,28 +2682,8 @@ async function run() {
2733
2682
  if (!secureStorage.available || secureStorage.plaintextFallbackActive) {
2734
2683
  console.log(cliKeyLine('secure note', secureStorage.detail));
2735
2684
  }
2736
- console.log(cliKeyLine('notifications', getNotificationReadiness()));
2737
- printStartupStatusLine(startupStatus);
2738
2685
  printPublicStatusGuidance(displayOverview, sessionState);
2739
2686
  };
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
- };
2758
2687
  const runPublicConnectInbox = async () => {
2759
2688
  const forceRefresh = hasFlag('--refresh');
2760
2689
  const inbox = service.getSmartRegistrationInbox({
@@ -2850,7 +2779,8 @@ async function run() {
2850
2779
  }
2851
2780
  console.log(`[forkit-connect] Queue processed=${result.sync.processed} succeeded=${result.sync.succeeded} failed=${result.sync.failed}`);
2852
2781
  if (!result.ok) {
2853
- process.exitCode = 2;
2782
+ console.log('[forkit-connect] No runtime passport was created. Start Ollama, LM Studio, an OpenAI-compatible local server, or add local model directories, then rerun scan.');
2783
+ process.exitCode = 0;
2854
2784
  }
2855
2785
  };
2856
2786
  const runPublicUpdateCheck = async () => {
@@ -3009,12 +2939,14 @@ async function run() {
3009
2939
  }
3010
2940
  const operatingMode = resolveOperatingMode(service);
3011
2941
  const accountTrusted = sessionState === 'authorized';
2942
+ const accountLimits = accountTrusted ? await loadCliAccountLimits().catch(() => null) : null;
3012
2943
  const overview = withSmartInboxSnapshotCounts(service.getConnectStatusOverview({ includeInbox: false }));
3013
2944
  const localScopeCached = Boolean(String(overview.workspace_id || '').trim() || String(overview.project_id || '').trim());
3014
2945
  return {
3015
2946
  session_state: sessionState,
3016
2947
  operating_mode: operatingMode.mode,
3017
- tier: operatingMode.tier,
2948
+ tier: accountLimits?.planKey ?? operatingMode.tier,
2949
+ plan_name: accountLimits?.planName ?? (operatingMode.tier ? getCliPlanName(resolveCliPlanKey(operatingMode.tier)) : null),
3018
2950
  workspace_id: accountTrusted ? overview.workspace_id : null,
3019
2951
  project_id: accountTrusted ? overview.project_id : null,
3020
2952
  binding_state: accountTrusted ? overview.binding_state : 'login_required',
@@ -4290,7 +4222,7 @@ async function run() {
4290
4222
  ? draftsResult.body.drafts
4291
4223
  : [];
4292
4224
  const passports = passportsResult?.ok ? readPassportList(passportsResult.body) : [];
4293
- const planKey = resolveCliPlanKey(summaryPayload?.planCapabilities?.planKey, summaryPayload?.package?.slug, summaryPayload?.tier, accessPayload?.summary?.tier, service.getServiceEntitlements().tier);
4225
+ const planKey = resolveCliPlanKey(summaryPayload?.planCapabilities?.planKey, summaryPayload?.package?.slug, accessPayload?.summary?.planKey, accessPayload?.summary?.packageSlug, summaryPayload?.tier, accessPayload?.summary?.tier, service.getServiceEntitlements().tier);
4294
4226
  const fallback = CLI_FALLBACK_PLAN_LIMITS[planKey];
4295
4227
  let projectsUsed = typeof summaryPayload?.usage?.projects === 'number' ? summaryPayload.usage.projects : null;
4296
4228
  if (projectsUsed === null && workspaces.length > 0) {
@@ -4695,6 +4627,7 @@ async function run() {
4695
4627
  console.log('[forkit-connect] Workspace status');
4696
4628
  console.log(`- mode=${payload.operating_mode}`);
4697
4629
  console.log(`- tier=${payload.tier || 'unknown'}`);
4630
+ console.log(`- plan=${payload.plan_name || 'unknown'}`);
4698
4631
  console.log(`- workspace=${payload.workspace_id || 'not selected'}`);
4699
4632
  console.log(`- project=${payload.project_id || 'not selected'}`);
4700
4633
  console.log(`- session=${payload.session_state}`);
@@ -5017,24 +4950,6 @@ async function run() {
5017
4950
  await runPublicConnectStatus();
5018
4951
  return;
5019
4952
  }
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
- }
5038
4953
  if (command === 'changes') {
5039
4954
  runPublicCollectedChanges();
5040
4955
  return;
@@ -6293,12 +6208,14 @@ async function run() {
6293
6208
  ? tokenPayload.role
6294
6209
  : null;
6295
6210
  const workspaces = Array.isArray(payload.workspaces) ? payload.workspaces : [];
6211
+ const accountLimits = await loadCliAccountLimits().catch(() => null);
6296
6212
  if (await maybeShowInteractiveWorkspaceTable(workspaces)) {
6297
6213
  return;
6298
6214
  }
6299
6215
  printWorkspaceListSurface(workspaces, {
6300
6216
  accountEmail: email,
6301
6217
  platformRole,
6218
+ accountLimits,
6302
6219
  });
6303
6220
  return;
6304
6221
  }
package/dist/v1/api.d.ts CHANGED
@@ -62,6 +62,8 @@ export interface ProfileAccessWorkspace {
62
62
  visibility?: 'private' | 'public' | string;
63
63
  verificationStatus?: 'unverified' | 'verified' | 'revoked' | string;
64
64
  lifecycleStatus?: 'active' | 'deactivated' | string;
65
+ projectCountHint?: number | null;
66
+ passportCountHint?: number | null;
65
67
  createdAt?: string | null;
66
68
  updatedAt?: string | null;
67
69
  }
@@ -69,6 +71,9 @@ export interface ProfileAccessResponse {
69
71
  summary?: {
70
72
  platformRole?: string;
71
73
  tier?: string;
74
+ planKey?: string;
75
+ planLabel?: string;
76
+ packageSlug?: string;
72
77
  };
73
78
  workspaces?: ProfileAccessWorkspace[];
74
79
  }
package/dist/v1/api.js CHANGED
@@ -112,7 +112,7 @@ class ConnectApiClient {
112
112
  };
113
113
  }
114
114
  async getProductSummary() {
115
- const url = `${this.config.baseUrl}/product/summary`;
115
+ const url = `${this.config.baseUrl}/api/product/summary`;
116
116
  const res = await fetch(url, {
117
117
  method: 'GET',
118
118
  headers: this.getHeaders(),
@@ -20,6 +20,7 @@ exports.ConnectCredentialStoreError = ConnectCredentialStoreError;
20
20
  const SERVICE_BASE_NAME = 'ForkitConnect';
21
21
  const LEGACY_SERVICE_NAME = SERVICE_BASE_NAME;
22
22
  const LINUX_SECRET_TOOL_TIMEOUT_MS = 1200;
23
+ const MACOS_SECURITY_TIMEOUT_MS = Number(process.env.FORKIT_CONNECT_MACOS_SECURITY_TIMEOUT_MS || 15000);
23
24
  function nowIso() {
24
25
  return new Date().toISOString();
25
26
  }
@@ -187,6 +188,7 @@ class MacOsKeychainBackend {
187
188
  '-w',
188
189
  ], {
189
190
  encoding: 'utf8',
191
+ timeout: MACOS_SECURITY_TIMEOUT_MS,
190
192
  });
191
193
  if (result.error) {
192
194
  throw new ConnectCredentialStoreError('secure_storage_read_failed', `Forkit Connect could not read secure credentials from macOS Keychain: ${result.error.message}`);
@@ -211,6 +213,7 @@ class MacOsKeychainBackend {
211
213
  value,
212
214
  ], {
213
215
  encoding: 'utf8',
216
+ timeout: MACOS_SECURITY_TIMEOUT_MS,
214
217
  });
215
218
  if (result.error) {
216
219
  throw new ConnectCredentialStoreError('secure_storage_write_failed', `Forkit Connect could not write secure credentials to macOS Keychain: ${result.error.message}`);
@@ -229,6 +232,7 @@ class MacOsKeychainBackend {
229
232
  account,
230
233
  ], {
231
234
  encoding: 'utf8',
235
+ timeout: MACOS_SECURITY_TIMEOUT_MS,
232
236
  });
233
237
  if (result.error) {
234
238
  throw new ConnectCredentialStoreError('secure_storage_delete_failed', `Forkit Connect could not remove secure credentials from macOS Keychain: ${result.error.message}`);
@@ -241,6 +245,7 @@ class MacOsKeychainBackend {
241
245
  getStatus() {
242
246
  const result = (0, node_child_process_1.spawnSync)('security', ['list-keychains'], {
243
247
  encoding: 'utf8',
248
+ timeout: MACOS_SECURITY_TIMEOUT_MS,
244
249
  });
245
250
  if (result.error) {
246
251
  return {
@@ -173,33 +173,6 @@ 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
- }
203
176
  function hasLinuxGuiSession() {
204
177
  return Boolean(normalizeSessionReference(process.env.DISPLAY)
205
178
  || normalizeSessionReference(process.env.WAYLAND_DISPLAY)
@@ -213,129 +186,6 @@ function resolveTerminalNotifierPath() {
213
186
  const resolved = String(notifierPath.stdout || '').trim();
214
187
  return resolved || null;
215
188
  }
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
- }
339
189
  function defaultNotificationSender(title, message, candidate) {
340
190
  if (process.platform === 'linux') {
341
191
  if (!hasLinuxGuiSession()) {
@@ -360,21 +210,15 @@ function defaultNotificationSender(title, message, candidate) {
360
210
  }
361
211
  }
362
212
  if (process.platform === 'darwin') {
363
- const resolvedNotifier = resolveBrandedMacNotifierBinary() ?? resolveTerminalNotifierPath();
213
+ const targetUrl = notificationTargetUrl(candidate);
214
+ const resolvedNotifier = resolveTerminalNotifierPath();
364
215
  if (resolvedNotifier) {
365
- const executeCommand = notificationExecuteCommand(candidate);
366
- const args = [
216
+ const openCommand = `/usr/bin/open '${targetUrl.replace(/'/g, "'\\''")}'`;
217
+ const result = (0, node_child_process_1.spawnSync)(resolvedNotifier, [
367
218
  '-title', title,
368
- '-subtitle', 'Forkit Connect',
369
219
  '-message', message,
220
+ '-execute', openCommand,
370
221
  '-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,
378
222
  ], { stdio: 'ignore' });
379
223
  if (result.status === 0) {
380
224
  return true;
@@ -9081,13 +8925,13 @@ class ConnectV1Service {
9081
8925
  : `Unavailable at ${ollama.endpoint}: ${ollama.error ?? 'unknown_error'}`,
9082
8926
  });
9083
8927
  if (process.platform === 'darwin') {
9084
- const notifierPath = resolveBrandedMacNotifierBinary() ?? resolveTerminalNotifierPath();
8928
+ const notifierPath = resolveTerminalNotifierPath();
9085
8929
  checks.push({
9086
8930
  name: 'desktop_notifications',
9087
8931
  status: notifierPath ? 'PASS' : 'WARN',
9088
8932
  details: notifierPath
9089
- ? 'Forkit Connect native notifications are ready with click-through support.'
9090
- : 'Forkit Connect notifications are not ready yet on this Mac.',
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.',
9091
8935
  });
9092
8936
  }
9093
8937
  const sessionRefSource = this.getSessionRefSource();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forkit-connect",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Forkit Connect Local Engine - The Global AI Governance Fabric",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -10,7 +10,6 @@
10
10
  },
11
11
  "files": [
12
12
  "dist",
13
- "assets",
14
13
  "README.md",
15
14
  "QUICKSTART.md"
16
15
  ],
@@ -34,7 +33,6 @@
34
33
  "better-sqlite3": "^12.10.0",
35
34
  "cors": "^2.8.6",
36
35
  "express": "^4.19.2",
37
- "node-notifier": "^10.0.1",
38
36
  "ps-list": "^8.1.1",
39
37
  "uuid": "^10.0.0",
40
38
  "ws": "^8.16.0",
Binary file
@@ -1,12 +0,0 @@
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
@@ -1,229 +0,0 @@
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