forkit-ai-footprints 0.2.4 → 0.2.6

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/PRIVACY.md CHANGED
@@ -61,7 +61,7 @@ AI Footprints may call HTTP(S) endpoints only when the hostname is one of:
61
61
  - `::1`
62
62
  - `www.forkit.dev`, limited to `/api/v1/ai-footprints/challenges` and
63
63
  `/api/v1/ai-footprints/contributions` after explicit Preview consent and the
64
- one-hour eligibility gate.
64
+ ten-minute eligibility gate.
65
65
 
66
66
  Other remote endpoints and endpoints containing embedded credentials are rejected.
67
67
  AI Footprints contains no Forkit.dev login, registry-write, passport-publish,
@@ -98,11 +98,13 @@ generate one App Attest key and store only its opaque key identifier in the
98
98
  device-only Keychain. The current UI and CLI do not invoke enrollment,
99
99
  attestation, assertion generation, or upload.
100
100
 
101
- On first launch of the v2 Preview consent, the native app states that an
102
- anonymous aggregate will be sent after one valid observed hour and refreshed
103
- when internet is available. The answer and consent version are stored locally
104
- in macOS user defaults. A user who chose “Not now” can reopen the same
105
- permission from the comparison view. No request is made before permission.
101
+ On first launch of the v3 Preview consent, the native app states that an
102
+ anonymous aggregate will be sent after ten valid observed minutes and refreshed
103
+ at most once per hour when internet is available. The answer and consent version
104
+ are stored locally in macOS user defaults. A user who chose “Not now” can reopen
105
+ the same permission from the comparison view. No request is made before
106
+ permission. Existing v2 consent retains its original one-hour threshold until
107
+ the person upgrades and accepts the v3 notice.
106
108
 
107
109
  ## Filesystem identity
108
110
 
@@ -144,14 +146,16 @@ used as automatic evidence of ownership, safety, provenance, or passport status.
144
146
  ## Anonymous contribution boundary
145
147
 
146
148
  The local app can create and send an allowlisted schema `2.0` Preview only after
147
- at least 3,600 valid observed seconds and explicit v2 native permission. The Preview
149
+ at least 600 valid observed seconds and explicit v3 native permission. The Preview
148
150
  contains valid seconds, AI-active seconds, activity ratio, supported app count
149
151
  and categories, model/loaded/runtime counts, recognized model-file bytes,
150
152
  macOS major, architecture, and scanner/runtime schema versions. It excludes
151
153
  names, process counts, CPU/RAM values, guess, device identity, local scan ID,
152
154
  timestamps, and all item-level records. The native app generates one owner-only
153
155
  local Ed25519 key, signs the exact aggregate against a one-time server challenge,
154
- and stores a small local sync receipt. The server persists only a keyed hash of
156
+ and stores a small local sync receipt. The first eligible payload may be sent
157
+ immediately; later refreshes are limited to no more than once per hour. The
158
+ server persists only a keyed hash of
155
159
  the public contribution identity and one latest aggregate. Community Preview
156
160
  signatures prevent undetected payload changes and simple replay; they do not
157
161
  prove unique hardware or truthful measurement. Public Preview copy must say
package/README.md CHANGED
@@ -1,8 +1,17 @@
1
1
  # Forkit AI Footprints
2
2
 
3
- Forkit AI Footprints is a private, metadata-only view of the models, runtimes,
4
- and AI agents present on a Mac. The prepared package and command are
5
- `forkit-ai-footprints`.
3
+ [![CI](https://github.com/arpitasarker01/forkit-ai-footprints/actions/workflows/ci.yml/badge.svg)](https://github.com/arpitasarker01/forkit-ai-footprints/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/forkit-ai-footprints)](https://www.npmjs.com/package/forkit-ai-footprints)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-2f2361.svg)](./LICENSE)
6
+
7
+ Forkit AI Footprints is an open-source, private-by-default, metadata-only view
8
+ of the models, runtimes, and AI agents present on a device. The public package
9
+ and command are `forkit-ai-footprints`.
10
+
11
+ [Product page](https://www.forkit.dev/ai-footprint) ·
12
+ [npm](https://www.npmjs.com/package/forkit-ai-footprints) ·
13
+ [Releases](https://github.com/arpitasarker01/forkit-ai-footprints/releases) ·
14
+ [Contributing](./CONTRIBUTING.md) · [Security](./SECURITY.md)
6
15
 
7
16
  It is derived from the discovery lessons in Forkit Connect, but it has a
8
17
  different safety boundary: AI Footprints does not authenticate to Forkit.dev and has
@@ -58,7 +67,7 @@ git clone https://github.com/arpitasarker01/forkit-ai-footprints.git
58
67
  cd forkit-ai-footprints
59
68
  npm ci
60
69
  npm pack
61
- npm install -g ./forkit-ai-footprints-0.2.4.tgz
70
+ npm install -g ./forkit-ai-footprints-0.2.6.tgz
62
71
  forkit-ai-footprints serve
63
72
  ```
64
73
 
@@ -185,13 +194,37 @@ forkit-ai-footprints scan --no-mcp
185
194
  ```
186
195
 
187
196
  CLI flags cannot bypass the required monitoring session. The local UI enables
188
- an exact schema `2.0` aggregate preview only after 3,600 valid observed seconds;
189
- the installed app asks with versioned consent whether the anonymous aggregate
190
- may join the Global AI Preview. When allowed, the app sends one latest signed
191
- aggregate after the one-hour gate and refreshes it when internet is available. The
192
- payload excludes app/model names, process counts, CPU/RAM, chat, workspace,
197
+ an exact schema `2.0` aggregate preview after 600 valid observed seconds; the
198
+ installed app asks with versioned consent whether the anonymous aggregate may
199
+ join the Global AI Preview. When allowed, the app sends one latest signed
200
+ aggregate after the ten-minute eligibility gate and refreshes that same row at
201
+ most once per hour. The server does not count refreshes as new installations.
202
+ The payload excludes app/model names, process counts, CPU/RAM, chat, workspace,
193
203
  repository, device, guess, local scan, and account identifiers.
194
204
 
205
+ ### How the community Preview is calculated
206
+
207
+ - An **active contributor** is an opted-in local installation that has reached
208
+ at least 600 valid observed seconds and successfully submitted a signed
209
+ aggregate. Installing or downloading the package alone does not count.
210
+ - Repeated syncs use the same private local contribution key and replace that
211
+ installation's previous aggregate. They do not increase the participant
212
+ total.
213
+ - The global AI-active rate is duration-weighted:
214
+ `sum(AI-active seconds) / sum(valid observed seconds)` across eligible latest
215
+ rows. A long observation therefore contributes proportionally more evidence
216
+ than a short observation.
217
+ - npm downloads are reported separately as registry download events. They are
218
+ not unique people, devices, installations, or consenting participants.
219
+ - Percentile placement stays unavailable until at least 30 contributors with
220
+ one valid observed hour. Regional output stays hidden until its privacy
221
+ threshold is met.
222
+
223
+ The service evaluates Preview eligibility once a minute. The first eligible
224
+ aggregate syncs when internet is available; subsequent submissions are locally
225
+ throttled to no more than one per hour. See [the privacy boundary](./PRIVACY.md)
226
+ for the exact allowlist and exclusions.
227
+
195
228
  ## Shared core API
196
229
 
197
230
  Forkit Connect should delegate to this package's public, read-only core rather
@@ -294,6 +327,18 @@ Storage, runtime-freshness, resource-attribution, and Developer ID truth
294
327
  boundaries are defined in
295
328
  [`docs/MACOS_MEASUREMENT_FOUNDATION.md`](./docs/MACOS_MEASUREMENT_FOUNDATION.md).
296
329
 
330
+ ## Contributing and security
331
+
332
+ Forkit AI Footprints is an MIT-licensed open-source client. Bug reports, focused
333
+ feature proposals, tests, accessibility and localization improvements, and
334
+ privacy-preserving detector fixes are welcome through GitHub issues and pull
335
+ requests. Maintainers review every accepted change; pull requests cannot publish
336
+ an npm release automatically.
337
+
338
+ Read [`CONTRIBUTING.md`](./CONTRIBUTING.md) before opening a pull request. Report
339
+ security or privacy vulnerabilities privately through the repository Security
340
+ tab as described in [`SECURITY.md`](./SECURITY.md), never in a public issue.
341
+
297
342
  ## Accuracy boundary
298
343
 
299
344
  AI Footprints has no defensible global accuracy percentage yet. Runtime API evidence is
package/STATUS.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # Forkit AI Footprints Status
2
2
 
3
- Last updated: 2026-08-28
3
+ Last updated: 2026-08-29
4
4
 
5
- Version: `0.2.4`
5
+ Version: `0.2.6`
6
6
 
7
7
  Production classification: `current` for Apple Silicon macOS
8
8
 
9
- Release state: v0.2.4 npm-first community Preview candidate. The npm
9
+ Release state: v0.2.6 npm-first community Preview is public. The npm
10
10
  bootstrap installs a persistent unsigned local app; Developer ID signing and
11
11
  notarization remain future work.
12
12
 
@@ -91,9 +91,10 @@ notarization remain future work.
91
91
  - A schema `2.0` Global AI Preview aggregate based on valid observed seconds and
92
92
  AI-active seconds. It excludes names, process counts, paths, commands, chat,
93
93
  workspace, device identity, guess, and local scan ID. Review and consent are
94
- separate. After explicit v2 native permission and one valid observed hour,
94
+ separate. After explicit v3 native permission and ten valid observed minutes,
95
95
  the app signs and syncs the allowlisted aggregate to Forkit.dev, keeping one
96
- latest community-reported Preview row per stable local contribution key.
96
+ latest community-reported Preview row per stable local contribution key and
97
+ refreshing it no more than once per hour.
97
98
  - A 1080×1080 local share card with Share, Save PNG, Copy Image, and Copy
98
99
  Caption controls. It contains only measured totals and supported tool names.
99
100
  - A single Forkit.dev `/ai-footprint` page with an honest benchmark-forming
@@ -124,14 +125,14 @@ notarization remain future work.
124
125
  valid time and **62.1 seconds** AI-active (ratio **0.981**). The deterministic
125
126
  evidence reports the supported app activity share, longest continuous signal,
126
127
  and stored local model records without attributing prompts or tasks.
127
- - A separate earlier real session crossed the previous 10-minute gate at exactly
128
- **600 valid seconds** and **598 AI-active seconds**. The current global
129
- comparison requires **3,600 valid seconds** before a consented community
130
- Preview aggregate becomes eligible for sync.
128
+ - A separate earlier real session crossed the **600 valid-second** active
129
+ contributor gate at **598 AI-active seconds**. Personal percentile ranking
130
+ still requires **3,600 valid seconds** when the community population is large
131
+ enough; contribution itself does not.
131
132
 
132
133
  ## Validation completed locally
133
134
 
134
- - TypeScript build and the complete local test suite: **130/130** tests passed.
135
+ - TypeScript build and the complete local test suite: **135/135** tests passed.
135
136
  - Deterministic active → idle/locked → resumed lifecycle coverage confirms that
136
137
  paused time changes neither observed nor AI-active seconds and cannot bridge
137
138
  the longest continuous activity block.
@@ -187,5 +188,5 @@ claim that a cloud model is running locally.
187
188
 
188
189
  Future expansion requires representative multi-device field accuracy, Intel Mac
189
190
  validation, and Apple signing/notarization. Community Preview contribution is
190
- open under the v2 consent/integrity boundary; verified global ranking remains
191
+ open under the v3 consent/integrity boundary; verified global ranking remains
191
192
  closed until its separate hardware-verification and abuse-control gate passes.
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderActivityApplicationPage = renderActivityApplicationPage;
4
4
  const localization_1 = require("./localization");
5
+ const sharing_1 = require("./sharing");
5
6
  function escapeHtml(value) {
6
7
  return String(value).replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#039;');
7
8
  }
@@ -11,6 +12,10 @@ function renderActivityApplicationPage(input) {
11
12
  const t = (key, values = {}) => escapeHtml((0, localization_1.uiText)(locale, key, values));
12
13
  const config = rescan ?? null;
13
14
  const globalUrl = rescan ? 'https://www.forkit.dev/ai-footprint#global-vision' : null;
15
+ const clientCopy = Object.fromEntries(Object.keys(localization_1.UI_COPY).map((copyLocale) => [
16
+ copyLocale,
17
+ Object.fromEntries(Object.keys(localization_1.UI_COPY[copyLocale]).map((key) => [key, (0, localization_1.uiText)(copyLocale, key)])),
18
+ ]));
14
19
  return `<!doctype html>
15
20
  <html lang="${locale}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="light dark"><title>Forkit AI Footprint</title>
16
21
  <style>
@@ -38,19 +43,20 @@ body{font-size:var(--ui-md);line-height:1.45}.brand-copy strong,.inspector-head
38
43
  @media(max-width:1320px){.workspace{grid-template-columns:minmax(210px,.28fr) minmax(0,1fr)}.workspace>.system-panel{grid-column:1;grid-row:1}.workspace>.explorer{grid-column:2;grid-row:1}.workspace>.insight-rail{grid-column:1/-1;grid-row:2;max-height:none}.observation-tools{grid-template-columns:repeat(4,1fr)}.below-timeline{grid-template-columns:1fr 1fr}.observation-metrics{grid-column:1/-1}.below-metrics{grid-template-columns:repeat(3,1fr)}}
39
44
  @media(max-width:760px){.workspace{grid-template-columns:1fr}.workspace>.system-panel,.workspace>.explorer,.workspace>.insight-rail{grid-column:1;grid-row:auto;max-height:none}.workspace>.explorer{order:-1}.below-timeline{grid-template-columns:1fr}.observation-metrics{grid-column:1}.below-metrics{grid-template-columns:1fr 1fr}.timeline-axis,.timeline-row{grid-template-columns:82px minmax(0,1fr)}}
40
45
  @media(max-width:760px){.timeline-tick:not(:first-child):not(:last-child){display:none}}
46
+ .footprint-strip.global-contribution{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:18px;border-left:4px solid var(--accent-system,var(--brand));background:linear-gradient(110deg,color-mix(in srgb,var(--brand-soft) 45%,var(--paper)),var(--paper) 58%)}.global-contribution-copy{min-width:0}.global-contribution-copy>strong{display:block;font-size:var(--ui-lg);letter-spacing:-.02em}.global-contribution-copy>span{display:block;margin-top:3px;color:var(--muted);font-size:var(--ui-xs);line-height:1.4}.eligibility-meter{height:7px;margin-top:9px;overflow:hidden;border-radius:999px;background:var(--surface-2)}.eligibility-meter i{display:block;width:0;height:100%;border-radius:inherit;background:var(--accent-system,var(--brand));transition:width .35s ease}.global-meta{display:flex;justify-content:space-between;gap:10px;margin-top:6px;color:var(--muted);font-size:var(--ui-xs)}.global-action{flex-wrap:wrap;justify-content:flex-end}.global-action .button{white-space:nowrap}.observation-metrics{display:none!important}.monitor-line{display:none}@media(max-width:760px){.footprint-strip.global-contribution{grid-template-columns:1fr}.global-action{justify-content:flex-start}.global-meta{align-items:flex-start;flex-direction:column}}
41
47
  @media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}
42
48
  </style></head><body><main class="shell">
43
49
  <header class="surface command"><div class="brand">${brand}<div class="brand-copy"><strong>Forkit AI Footprint</strong><span class="status stopped" id="monitor-status"><i class="status-dot"></i><span id="monitor-status-text">${t('stopped')}</span></span></div></div><div class="command-actions"><span class="privacy-chip"><b>${t('localOnly')}</b> · ${t('nothingUploaded')}</span><div class="locale" aria-label="Language"><button id="locale-en" class="${locale === 'en' ? 'active' : ''}" type="button">EN</button><button id="locale-de" class="${locale === 'de' ? 'active' : ''}" type="button">DE</button></div><button class="button primary" id="share-button" type="button">${t('share')}</button>${rescan ? `<button class="button" id="monitor-button" type="button">${t('startMonitoring')}</button><details class="more"><summary>${t('more')}</summary><div class="more-menu"><button class="button" id="clear-button" type="button">${t('clearHistory')}</button><button class="button" id="scan-button" type="button">${t('scanAgain')}</button><span class="scan-status" id="scan-status"></span></div></details>` : ''}</div></header>
44
50
  <section class="surface metric-rail" aria-label="${t('scorecard')}"><div class="headline"><span class="metric-label">${t('activityRatio')}</span><strong class="metric-value" id="metric-ratio">—</strong><span class="metric-sub" id="metric-active">0s</span></div><div class="headline"><span class="metric-label">${t('activeAiTools')}</span><strong class="metric-value" id="metric-systems">0</strong><span class="metric-sub" id="ratio-label">${t('earlyObservation')}</span></div><div class="headline"><span class="metric-label">${t('localAi')}</span><strong class="metric-value" id="metric-local">${snapshot.model_record_count} · ${snapshot.model_storage_decimal_display}</strong><span class="metric-sub" id="metric-local-running">${snapshot.confirmed_running_model_count} ${t('runningShort')}</span></div></section>
45
51
  <section class="workspace"><article class="surface explorer"><div class="panel-head"><div><p class="eyebrow" id="explorer-eyebrow">${t('currentSession')}</p><h1 id="explorer-title">${t('activityExplorer')}</h1><p class="panel-copy" id="explorer-copy">${t('activityExplorerCopy')}</p></div><div class="chart-controls"><div class="resource-modes" aria-label="${t('resourcePulse')}"><button class="active" data-resource-mode="both" type="button">${t('both')}</button><button data-resource-mode="cpu" type="button">${t('cpu')}</button><button data-resource-mode="memory" type="button">${t('memory')}</button></div><button class="icon-button" id="zoom-out" type="button" title="${t('zoomOut')}" aria-label="${t('zoomOut')}">−</button><button class="icon-button" id="zoom-in" type="button" title="${t('zoomIn')}" aria-label="${t('zoomIn')}">+</button><button class="icon-button" id="reset-view" type="button">${t('resetView')}</button></div></div><div class="temporal"><div class="temporal-main"><details class="date-picker" id="date-picker"><summary><span>${t('dateHistory')}</span> <strong id="observation-date">—</strong></summary><div class="date-popover" id="date-options"></div></details><span id="observation-span"></span></div><span id="resource-history-state">${t('resourceCollecting')}</span></div><section class="focus-summary" id="focus-summary" aria-live="polite"><div class="focus-copy"><strong id="focus-title">${t('pulseDetailTitle')}</strong><span id="focus-copy">${t('pulseDetailCopy')}</span></div><div class="focus-stats" id="focus-stats"></div></section><div class="chart-wrap"><div class="system-timeline" id="system-timeline" aria-label="${t('mainChartDescription')}"><div class="timeline-empty">${t('earlyObservationCopy')}</div></div><canvas class="chart-canvas legacy-timeline" id="activity-explorer" tabindex="0" role="img" aria-label="${t('mainChartDescription')}"></canvas><canvas class="navigator" id="activity-navigator" aria-hidden="true"></canvas><div class="chart-tooltip" id="chart-tooltip" hidden></div></div><div class="chart-help"><div class="legend"><span><i class="active"></i>${t('working')}</span><span><i></i>${t('idleTimeLabel')}</span><span><i class="excluded"></i>${t('excluded')}</span></div><span>${t('timelineHelp')}</span></div><section class="below-timeline"><article class="below-card observation-metrics" data-center-view="pulse"><h2>${t('observationMetrics')}</h2><div class="below-metrics"><div class="below-stat"><span>${t('observed')}</span><strong id="below-observed">0s</strong></div><div class="below-stat"><span>${t('aiActive')}</span><strong id="below-active">0s</strong></div><div class="below-stat"><span>${t('activityRatio')}</span><strong id="below-ratio">—</strong></div><div class="below-stat"><span>${t('activityPeriods')}</span><strong id="below-periods">0</strong></div><div class="below-stat"><span>${t('longestActive')}</span><strong id="below-longest">0s</strong></div><div class="below-stat"><span>${t('systems')}</span><strong id="below-systems">0</strong></div></div></article><article class="below-card" data-center-view="runtime"><h2>${t('runtimeFootprint')}</h2><div class="runtime-footprint-list" id="runtime-footprint-list"></div></article><article class="below-card" data-center-view="resources"><h2>${t('liveResourceFootprint')}</h2><div class="resource-summary"><div class="below-stat"><span>${t('cpu')}</span><strong id="below-cpu">—</strong></div><div class="below-stat"><span>${t('memory')}</span><strong id="below-memory">—</strong></div><div class="below-stat"><span>${t('gpu')}</span><strong>${t('notMeasured')}</strong></div><div class="below-stat"><span>${t('resourcePulse')}</span><strong id="resource-history-label">${t('resourceCollecting')}</strong></div></div><h3 class="resource-history-title">${t('resourceHistory')}</h3><div class="resource-history-list" id="center-resource-history"></div><p class="truth-note" id="resource-history-note">${t('resourceCollecting')}</p></article><article class="below-card" data-center-view="apps"><h2>${t('appsDetailTitle')}</h2><div class="center-app-list" id="center-apps-list"></div></article><article class="below-card" data-center-view="pulse"><h2>${t('localAiFootprint')}</h2><div class="local-summary"><div class="below-stat"><span>${t('localRuntimes')}</span><strong id="below-runtimes">0</strong></div><div class="below-stat"><span>${t('modelRecordsShort')}</span><strong id="below-models">${snapshot.model_record_count}</strong></div><div class="below-stat"><span>${t('hostedModelsRunning')}</span><strong id="below-running">${snapshot.confirmed_running_model_count}</strong></div><div class="below-stat"><span>${t('recognizedModelSpace')}</span><strong id="below-space">${snapshot.model_storage_decimal_display}</strong></div></div><p class="statement" id="below-local-insight">${t('localAiNone')}</p></article></section></article>
46
52
  <aside class="surface insight-rail"><div class="device-identity"><div><p class="eyebrow">${t('deviceIdentity')}</p><strong>${escapeHtml(device)}</strong></div><span class="observation-number" id="observation-identity">${t('observationIdentity', { number: '—' })}</span></div><nav class="observation-tools" aria-label="${t('observationTools')}"><button class="feature-tool" data-explorer-mode="pulse" data-tip="${t('inspectPulseHint')}" title="${t('inspectPulseHint')}" type="button" aria-pressed="true"><span class="feature-icon" aria-hidden="true">⌁</span><span>${t('pulseView')}</span></button><button class="feature-tool" data-explorer-mode="runtime" data-tip="${t('inspectRuntimeHint')}" title="${t('inspectRuntimeHint')}" type="button" aria-pressed="false"><span class="feature-icon" aria-hidden="true">◷</span><span>${t('runtimeView')}</span></button><button class="feature-tool" data-explorer-mode="resources" data-tip="${t('inspectResourcesHint')}" title="${t('inspectResourcesHint')}" type="button" aria-pressed="false"><span class="feature-icon" aria-hidden="true">◉</span><span>${t('resourcesView')}</span></button><button class="feature-tool" data-explorer-mode="apps" data-tip="${t('inspectAppsHint')}" title="${t('inspectAppsHint')}" type="button" aria-pressed="false"><span class="feature-icon" aria-hidden="true">▦</span><span>${t('appsView')}</span></button></nav><section class="rail-section" data-view="pulse"><div id="inspector-headline"><h2>${t('observationShows')}</h2><p class="insight-copy">${t('selectTimelineBlock')}</p></div><div class="selected-evidence" id="selected-evidence" aria-live="polite">${t('noSelection')}</div><div class="insight-list" id="insight-list"></div></section><section class="rail-section" data-view="runtime"><div class="rail-heading"><strong>${t('observationOverview')}</strong><span>${t('measuredCurrentOnly')}</span></div><div class="observation-grid"><div class="rail-stat"><span>${t('observed')}</span><strong id="rail-observed">0s</strong></div><div class="rail-stat"><span>${t('aiActive')}</span><strong id="rail-active">0s</strong></div><div class="rail-stat"><span>${t('activityRatio')}</span><strong id="rail-ratio">—</strong></div><div class="rail-stat"><span>${t('longestActive')}</span><strong id="rail-longest">0s</strong></div></div></section><section class="rail-section" data-view="resources"><div class="rail-heading"><strong>${t('resourcesNow')}</strong><span>${t('resourceLiveOnly')}</span></div><div class="resource-grid"><div class="rail-stat"><span>${t('cpu')}</span><strong id="rail-cpu">—</strong></div><div class="rail-stat"><span>${t('memory')}</span><strong id="rail-memory">—</strong></div><div class="rail-stat"><span>${t('gpu')}</span><strong>${t('notMeasured')}</strong></div><div class="rail-stat"><span>${t('recognizedModelSpace')}</span><strong id="rail-space">${snapshot.model_storage_decimal_display}</strong></div></div></section><section class="rail-section" data-view="runtime"><div class="rail-heading"><strong>${t('localRuntimes')}</strong><span>${t('localOnly')}</span></div><div class="rail-list" id="rail-runtimes"></div></section><section class="rail-section" data-view="apps"><div class="rail-heading"><strong>${t('appsView')}</strong><span>${t('measuredCurrentOnly')}</span></div><div class="inspector-app-list" id="inspector-apps-list"></div></section><section class="system-mix rail-section"><div class="rail-heading"><strong>${t('aiAppsOnDevice')}</strong></div><p class="insight-copy">${t('focusSystemHelp')}</p><div class="mix-bar" id="mix-bar"></div><div class="mix-list" id="mix-list"></div><div class="system-actions"><button class="button all-apps" id="all-apps" type="button">${t('allApps')}</button><button class="button" id="focus-system" type="button" disabled>${t('focusSystem')}</button></div><div class="app-inspector" id="app-inspector" hidden></div></section></aside></section>
47
- <section class="surface footprint-strip"><div class="local-strip"><div><p class="eyebrow">${t('localAi')}</p><strong><span id="models-found">${snapshot.model_record_count}</span> ${t('modelRecordsShort').toLowerCase()} · <span id="device-storage">${snapshot.model_storage_decimal_display}</span> · <span id="models-running">${snapshot.confirmed_running_model_count}</span> ${t('runningShort')}</strong><span id="hosted-insight">${t('localAiNone')}</span></div></div><div class="global-action"><div><strong>${t('globalPosition')} · ${t('notAvailableYet')}</strong><span class="scan-status" id="compare-progress"></span><span class="scan-status" id="global-result"></span></div>${rescan ? `<button class="button" id="review-payload" type="button" disabled>${t('openGlobal')}</button>` : ''}</div></section>
53
+ <section class="surface footprint-strip global-contribution" aria-labelledby="global-status-title"><div class="global-contribution-copy"><p class="eyebrow">${t('globalContribution')} · ${t('notAvailableYet')}</p><strong id="global-status-title">${t('globalNotJoined')}</strong><span id="global-status-copy">${t('globalNotJoinedCopy')}</span><div class="eligibility-meter" role="progressbar" aria-valuemin="0" aria-valuemax="${sharing_1.MINIMUM_GLOBAL_CONTRIBUTION_SECONDS}" aria-valuenow="0" id="global-progress"><i id="global-progress-fill"></i></div><div class="global-meta"><span id="global-valid-progress">${t('globalValidProgress', { current: '0s', required: '${Math.floor(MINIMUM_GLOBAL_CONTRIBUTION_SECONDS / 60)}m' })}</span><span id="global-last-sync">${t('globalNeverSynced')}</span><span>${t('globalDownloadsNote')}</span></div><span class="scan-status" id="compare-progress"></span><span class="scan-status" id="global-result"></span></div><div class="global-action">${rescan ? `<button class="button" id="global-permission-button" type="button">${t('reviewPermission')}</button><button class="button primary" id="review-payload" type="button">${t('openGlobal')}</button>` : ''}</div><div class="local-strip" hidden><span id="models-found">${snapshot.model_record_count}</span><span id="device-storage">${snapshot.model_storage_decimal_display}</span><span id="models-running">${snapshot.confirmed_running_model_count}</span><span id="hosted-insight">${t('localAiNone')}</span></div></section>
48
54
  <div class="monitor-line"><span>${t('monitorOverhead')} · <span id="overhead">${t('warmingUp')}</span> · <span id="overhead-note"></span></span><span>${t('validObservation')} <b id="metric-observed">0s</b> · ${t('longestActive')} <b id="metric-longest">0s</b></span></div>
49
55
  <details class="surface advanced"><summary>${t('technical')}</summary><div class="details-grid"><article class="detail-box"><h3>${t('engines')}</h3><ul class="detail-list" id="runtime-list"></ul></article><article class="detail-box"><h3>${t('modelsDevice')}</h3><ul class="detail-list" id="model-list"></ul></article><article class="detail-box"><h3>${t('appsTools')}</h3><ul class="detail-list" id="app-list"></ul></article><article class="detail-box"><h3>${t('measurementBoundary')}</h3><ul class="detail-list"><li><strong>${t('activityMeasurement')}</strong>${t('workingBoundary')}</li><li><strong>${t('loadedModel')}</strong>${t('loadedBoundary')}</li><li><strong>${t('storage')}</strong>${t('storageBoundary')}</li><li><strong>${t('resourcePulse')}</strong>${t('resourceLiveOnly')}</li><li><strong>${t('systems')}</strong><span id="raw-system-count">0</span></li></ul></article></div></details>
50
56
  <footer class="footer"><span>${t('localDevice')} · ${t('nothingUploaded')}</span><span>${t('noPrivateFooter')} · v${snapshot.product_version}</span></footer></main>
51
57
  <dialog id="share-dialog"><div class="share-layout"><div class="share-preview"><canvas id="share-canvas" width="1080" height="1080"></canvas></div><div class="share-side"><p class="eyebrow">${t('shareTitle')}</p><h2 id="share-heading">${t('shareHeading')}</h2><p>${t('shareFormat')}</p><div class="share-actions"><button class="button primary" id="native-share" type="button">${t('share')}</button><button class="button" id="download-share" type="button">${t('savePng')}</button><button class="button" id="copy-image" type="button">${t('copyImage')}</button><button class="button" id="copy-share" type="button">${t('copyCaption')}</button><button class="button" id="close-share" type="button">${t('close')}</button></div><p class="share-status" id="share-status">${t('sharePrivacy')}</p></div></div></dialog>
52
58
  <script>
53
- const copy=${safeScriptJson(localization_1.UI_COPY)},locale=${safeScriptJson(locale)},config=${safeScriptJson(config)},globalUrl=${safeScriptJson(globalUrl)},shareLogo=${safeScriptJson(shareLogo)};
59
+ const copy=${safeScriptJson(clientCopy)},locale=${safeScriptJson(locale)},config=${safeScriptJson(config)},globalUrl=${safeScriptJson(globalUrl)},shareLogo=${safeScriptJson(shareLogo)},globalContributionMinimumSeconds=${sharing_1.MINIMUM_GLOBAL_CONTRIBUTION_SECONDS};
54
60
  let footprint=${safeScriptJson(view ?? snapshot)},monitor={lifecycle:'stopped',observed_seconds:0,active_seconds:0,activity_ratio:null,products:[],timeline:[],resource_history:[],insights:[],presence:{state:'active',observation_eligible:true},overhead:{current_cpu_percent:null,current_memory_bytes:0,history_bytes:0},activity_view:{segments:[],contributions:[],insights:[],systems:[],extent:null,active_block_count:0,longest_active_seconds:0,longest_active_range:null,idle_seconds:0,excluded_seconds:0,simultaneous_systems_max:0,active_tool_count:0,observation_ids:[],continuity_ids:[],available_date_keys:[],current_resources:{cpu_percent:null,memory_bytes:null,process_count:0},resource_history:{buckets:[],systems:[]},resource_history_state:'not-retained'},last_observation:null};
55
61
  const $=id=>document.getElementById(id),format=new Intl.NumberFormat(locale),clock=new Intl.DateTimeFormat(locale,{day:'2-digit',month:'short',hour:'2-digit',minute:'2-digit',second:'2-digit'}),fullMoment=new Intl.DateTimeFormat(locale,{dateStyle:'medium',timeStyle:'medium'}),dateLabel=new Intl.DateTimeFormat(locale,{weekday:'short',day:'numeric',month:'short',year:'numeric'}),shareDateLabel=new Intl.DateTimeFormat(locale,{day:'numeric',month:'short',year:'numeric'}),systemPalette=['#00888d','#d98a25','#7758c7','#2e8f68','#c85578','#487bc4','#9b6b37','#637b2f'],colorsKey='forkit-footprints-colors-v1',lastSummaryKey='forkit-footprints-last-summary-v1';
56
62
  function text(key,values={}){let value=copy[locale][key]||key;for(const [name,replacement] of Object.entries(values))value=value.split('{'+name+'}').join(String(replacement));return value}
@@ -82,7 +88,10 @@ function renderMix(){const bar=$('mix-bar'),listNode=$('mix-list'),view=viewForD
82
88
  function renderHostedLocal(){const products=monitor.products||[],workingLocal=products.filter(product=>product.kind==='local-model-runtime'&&product.state==='working-now'),running=footprint.confirmed_running_model_count||0;$('models-found').textContent=format.format(footprint.model_record_count||0);$('models-running').textContent=format.format(running);$('device-storage').textContent=bytes(footprint.model_storage_bytes||0);$('metric-local').textContent=format.format(footprint.model_record_count||0)+' · '+bytes(footprint.model_storage_bytes||0);$('metric-local-running').textContent=format.format(running)+' '+text('runningShort');$('hosted-insight').textContent=running>0||workingLocal.length?text('hostedModelsRunningInsight'):text('localAiNone')}
83
89
  function renderRuntimes(){const container=$('rail-runtimes');container.replaceChildren();for(const runtime of footprint.local_details?.runtimes||[]){const row=document.createElement('button'),name=document.createElement('strong'),status=document.createElement('span');row.type='button';row.className='runtime-item';row.title=text('inspectRuntimeHint');name.textContent=runtime.name;status.textContent=runtime.loaded_model_count?text('modelsLoaded',{count:runtime.loaded_model_count,models:runtime.loaded_model_count===1?text('model'):text('models')}):text('verifiedAvailable');row.append(name,status);row.addEventListener('click',()=>setExplorerMode('runtime'));container.append(row)}if(!container.children.length){const note=document.createElement('div');note.className='early';note.textContent='—';container.append(note)}}function renderDetails(){if(!footprint.local_details)return;const details=footprint.local_details,runtimeList=$('runtime-list'),modelList=$('model-list'),appList=$('app-list');for(const node of [runtimeList,modelList,appList])node.replaceChildren();for(const runtime of details.runtimes)add(runtimeList,runtime.name,runtime.loaded_model_count?text('modelsLoaded',{count:runtime.loaded_model_count,models:runtime.loaded_model_count===1?text('model'):text('models')}):text('verifiedAvailable'));for(const model of details.models.filter(item=>item.loaded))add(modelList,model.name,text('loaded',{count:''}).trim());for(const product of monitor.products||[]){const note=[text(product.state==='working-now'?'working':'openIdleLegend'),format.format(product.process_count)+' '+text(product.process_count===1?'measuredProcess':'measuredProcesses'),product.memory_bytes?bytes(product.memory_bytes):''].filter(Boolean).join(' · ');add(appList,product.name,note)}if(!appList.children.length)for(const agent of details.agents)add(appList,agent.name,text('processActivity'));if($('raw-system-count'))$('raw-system-count').textContent=format.format((monitor.products||[]).length);renderRuntimes()}
84
90
  function saveLastSummary(){if(!monitor.observed_seconds)return;const dominant=dominantProduct();try{localStorage.setItem(lastSummaryKey,JSON.stringify({activity_ratio:monitor.activity_ratio,active_tool:dominant?.name||null,local_models_found:footprint.model_record_count||0,local_models_running:footprint.confirmed_running_model_count||0}))}catch{}}
85
- function renderMonitor(){const products=monitor.products||[],working=userFacingProducts().filter(product=>product.state==='working-now'),presence=monitor.presence||{state:'active',observation_eligible:true},status=$('monitor-status');status.className='status';let statusText;if(monitor.lifecycle==='stopped'){status.classList.add('stopped');statusText=text('stopped')}else if(!presence.observation_eligible){status.classList.add('paused');statusText=text('paused')}else if(working.length)statusText=text('menuWorking',{count:working.length});else statusText=text('menuMonitoring');$('monitor-status-text').textContent=statusText;if($('monitor-button'))$('monitor-button').textContent=monitor.lifecycle==='monitoring'?text('stopMonitoring'):monitor.observed_seconds?text('startMonitoringAgain'):text('startMonitoring');appendResourceSample();renderDates();const view=currentView()||{segments:[],contributions:[],systems:[],insights:[],longest_active_seconds:0},totals=selectedDateKey||focusedSystemSignature?viewTotals(view):{active:monitor.active_seconds,observed:monitor.observed_seconds,ratio:monitor.activity_ratio},resources=aggregateResources(products),historical=selectedDateKey&&selectedDateKey!==localDateKey(monitor.activity_view?.extent?.end_ms||Date.now()),live=!historical&&monitor.lifecycle==='monitoring',dominant=dominantProduct();if(dominant)document.documentElement.style.setProperty('--accent-system',systemColor(dominant));$('metric-ratio').textContent=pct(totals.ratio);$('ratio-label').textContent=totals.observed<60?text('earlyObservation'):totals.ratio>=.7?text('mostlyActive'):totals.ratio<=.3?text('mostlyIdle'):text('mixedActivity');$('metric-active').textContent=seconds(totals.active);$('metric-observed').textContent=seconds(totals.observed);$('metric-systems').textContent=format.format((view.systems||[]).filter(system=>system.active_seconds>0||system.state==='working-now').length);$('metric-longest').textContent=seconds(view.longest_active_seconds||0);$('observation-identity').textContent=text('observationIdentity',{number:monitor.observation_id??'—'});$('rail-observed').textContent=seconds(totals.observed);$('rail-active').textContent=seconds(totals.active);$('rail-ratio').textContent=pct(totals.ratio);$('rail-longest').textContent=seconds(view.longest_active_seconds||0);$('rail-cpu').textContent=live&&Number.isFinite(resources.cpu)?format.format(Number(resources.cpu.toFixed(1)))+'%':'—';$('rail-memory').textContent=live&&Number.isFinite(resources.memory)?bytes(resources.memory):'—';$('rail-space').textContent=bytes(footprint.model_storage_bytes||0);explorer.setData(view);renderInsights();renderMix();renderHostedLocal();renderDetails();renderInspector();renderFocusSummary();renderSystemTimeline();renderInspectorApps();renderBelowSummary();if($('overhead')){$('overhead').textContent=monitor.overhead?.current_cpu_percent===null?text('warmingUp'):format.format(Number(monitor.overhead.current_cpu_percent.toFixed(1)))+'% CPU';$('overhead-note').textContent=bytes(monitor.overhead?.current_memory_bytes||0)+' RAM'}if($('review-payload')){const eligible=monitor.observed_seconds>=3600;$('review-payload').disabled=!eligible;$('compare-progress').textContent=eligible?'':text('globalProgress',{remaining:seconds(Math.max(0,3600-monitor.observed_seconds))});const preview=monitor.global_preview;$('global-result').textContent=preview?.state==='contributed'?text('previewContributed'):preview?.state==='unavailable'?text('previewUnavailable'):monitor.global_permission==='granted'?text('previewWaiting'):text('permissionNeeded')}saveLastSummary()}
91
+ function renderMonitor(){const products=monitor.products||[],working=userFacingProducts().filter(product=>product.state==='working-now'),presence=monitor.presence||{state:'active',observation_eligible:true},status=$('monitor-status');status.className='status';let statusText;if(monitor.lifecycle==='stopped'){status.classList.add('stopped');statusText=text('stopped')}else if(!presence.observation_eligible){status.classList.add('paused');statusText=text('paused')}else if(working.length)statusText=text('menuWorking',{count:working.length});else statusText=text('menuMonitoring');$('monitor-status-text').textContent=statusText;if($('monitor-button'))$('monitor-button').textContent=monitor.lifecycle==='monitoring'?text('stopMonitoring'):monitor.observed_seconds?text('startMonitoringAgain'):text('startMonitoring');appendResourceSample();renderDates();const view=currentView()||{segments:[],contributions:[],systems:[],insights:[],longest_active_seconds:0},totals=selectedDateKey||focusedSystemSignature?viewTotals(view):{active:monitor.active_seconds,observed:monitor.observed_seconds,ratio:monitor.activity_ratio},resources=aggregateResources(products),historical=selectedDateKey&&selectedDateKey!==localDateKey(monitor.activity_view?.extent?.end_ms||Date.now()),live=!historical&&monitor.lifecycle==='monitoring',dominant=dominantProduct();if(dominant)document.documentElement.style.setProperty('--accent-system',systemColor(dominant));$('metric-ratio').textContent=pct(totals.ratio);$('ratio-label').textContent=totals.observed<60?text('earlyObservation'):totals.ratio>=.7?text('mostlyActive'):totals.ratio<=.3?text('mostlyIdle'):text('mixedActivity');$('metric-active').textContent=seconds(totals.active);$('metric-observed').textContent=seconds(totals.observed);$('metric-systems').textContent=format.format((view.systems||[]).filter(system=>system.active_seconds>0||system.state==='working-now').length);$('metric-longest').textContent=seconds(view.longest_active_seconds||0);$('observation-identity').textContent=text('observationIdentity',{number:monitor.observation_id??'—'});$('rail-observed').textContent=seconds(totals.observed);$('rail-active').textContent=seconds(totals.active);$('rail-ratio').textContent=pct(totals.ratio);$('rail-longest').textContent=seconds(view.longest_active_seconds||0);$('rail-cpu').textContent=live&&Number.isFinite(resources.cpu)?format.format(Number(resources.cpu.toFixed(1)))+'%':'—';$('rail-memory').textContent=live&&Number.isFinite(resources.memory)?bytes(resources.memory):'—';$('rail-space').textContent=bytes(footprint.model_storage_bytes||0);explorer.setData(view);renderInsights();renderMix();renderHostedLocal();renderDetails();renderInspector();renderFocusSummary();renderSystemTimeline();renderInspectorApps();renderBelowSummary();if($('overhead')){$('overhead').textContent=monitor.overhead?.current_cpu_percent===null?text('warmingUp'):format.format(Number(monitor.overhead.current_cpu_percent.toFixed(1)))+'% CPU';$('overhead-note').textContent=bytes(monitor.overhead?.current_memory_bytes||0)+' RAM'}if($('review-payload')){const eligible=monitor.observed_seconds>=globalContributionMinimumSeconds;$('review-payload').disabled=!eligible;$('compare-progress').textContent=eligible?'':text('globalProgress',{remaining:seconds(Math.max(0,globalContributionMinimumSeconds-monitor.observed_seconds))});const preview=monitor.global_preview;$('global-result').textContent=preview?.state==='contributed'?text('previewContributed'):preview?.state==='unavailable'?text('previewUnavailable'):monitor.global_permission==='granted'?text('previewWaiting'):text('permissionNeeded')}saveLastSummary()}
92
+ function renderGlobalContribution(){if(!$('global-status-title'))return;const observed=Math.max(0,Number(monitor.observed_seconds)||0),eligible=observed>=globalContributionMinimumSeconds,permission=monitor.global_permission==='granted',preview=monitor.global_preview||{},progress=Math.min(100,(observed/globalContributionMinimumSeconds)*100),lastSuccess=preview.last_success_at?new Date(preview.last_success_at):null;let title,copyText;if(!permission){title=text('globalNotJoined');copyText=text('globalNotJoinedCopy')}else if(preview.state==='contributed'){title=text('globalQualified');copyText=text('globalQualifiedCopy')}else if(preview.state==='unavailable'){title=eligible?text('globalRetry'):text('globalBuilding');copyText=eligible?text('globalRetryCopy'):text('globalBuildingCopy')}else if(eligible){title=text('globalSyncing');copyText=text('globalSyncingCopy')}else{title=text('globalBuilding');copyText=text('globalBuildingCopy')}$('global-status-title').textContent=title;$('global-status-copy').textContent=copyText;$('global-progress').setAttribute('aria-valuenow',String(Math.min(globalContributionMinimumSeconds,Math.round(observed))));$('global-progress-fill').style.width=progress+'%';$('global-valid-progress').textContent=text('globalValidProgress',{current:seconds(Math.min(observed,globalContributionMinimumSeconds)),required:seconds(globalContributionMinimumSeconds)});$('global-last-sync').textContent=lastSuccess&&!Number.isNaN(lastSuccess.getTime())?text('globalLastSync',{time:shareDateLabel.format(lastSuccess)+' '+clock.format(lastSuccess)}):text('globalNeverSynced');$('compare-progress').textContent=permission&&!eligible?text('globalProgress',{remaining:seconds(Math.max(0,globalContributionMinimumSeconds-observed))}):'';$('global-result').textContent=preview.state==='contributed'?text('previewContributed'):preview.state==='unavailable'?text('previewUnavailable'):permission?text('previewWaiting'):text('permissionNeeded')}
93
+ const renderMonitorContributionBase=renderMonitor;renderMonitor=function(){renderMonitorContributionBase();renderGlobalContribution()}
94
+ const renderGlobalContributionAccessBase=renderGlobalContribution;renderGlobalContribution=function(){renderGlobalContributionAccessBase();if($('review-payload'))$('review-payload').disabled=false;const last=monitor.global_preview?.last_success_at?new Date(monitor.global_preview.last_success_at):null;if(last&&!Number.isNaN(last.getTime()))$('global-last-sync').textContent=text('globalLastSync',{time:fullMoment.format(last)})}
86
95
  function shareObservationWindow(){const extent=monitor.activity_view?.extent;if(!extent)return text('shareMeasuredLocally');const start=new Date(extent.start_ms),end=new Date(Math.max(extent.start_ms,extent.end_ms-1));return shareDateLabel.format(start)+' '+clock.format(start)+' – '+shareDateLabel.format(end)+' '+clock.format(end)}
87
96
  function primaryInsight(){return monitor.insights?.[0]||text('activeShareFallback',{ratio:pct(monitor.activity_ratio)})}function shareInsightLine(){const tools=activeToolNames();if(tools.length===1)return text('shareFocusedLine');if(tools.length>1)return text('shareMultiLine',{count:format.format(tools.length)});if(footprint.model_record_count)return text('shareModelReserveLine');return primaryInsight()}function shareDurationLine(){return monitor.observed_seconds?text('shareMeasuredDurations',{observed:seconds(monitor.observed_seconds),active:seconds(monitor.active_seconds||0)}):''}function shareCaption(){const tools=activeToolNames(),running=footprint.confirmed_running_model_count||0,details=[text('shareActivity',{ratio:pct(monitor.activity_ratio)}),shareDurationLine(),tools.length?text('shareTool',{tools:list(tools)}):'',text('shareModels',{count:footprint.model_record_count,models:footprint.model_record_count===1?text('model'):text('models'),running:format.format(running)})].filter(Boolean).join(' ');return text('captionFirst',{ratio:pct(monitor.activity_ratio)})+'\\n\\n'+details+'\\n\\n'+text('captionQuestion')+'\\n\\nforkit.dev/ai-footprint'}
88
97
  function wrap(x,value,left,top,maxWidth,lineHeight,maxLines){const words=String(value||'').split(' '),lines=[];let line='';for(const word of words){const next=line?line+' '+word:word;if(x.measureText(next).width>maxWidth&&line){lines.push(line);line=word}else line=next}if(line)lines.push(line);for(let index=0;index<Math.min(lines.length,maxLines);index++)x.fillText(lines[index],left,top+index*lineHeight)}
@@ -91,6 +100,7 @@ const drawShareBase=drawShare;drawShare=async function(){await drawShareBase();c
91
100
  const dialog=$('share-dialog');async function openShare(){$('share-heading').textContent=shareInsightLine();await drawShare();dialog.showModal()}$('share-button').addEventListener('click',openShare);$('close-share').addEventListener('click',()=>dialog.close());async function blob(){await drawShare();return new Promise(resolve=>$('share-canvas').toBlob(resolve,'image/png'))}async function dataUrl(){const value=await blob();if(!value)return null;return new Promise(resolve=>{const reader=new FileReader();reader.onload=()=>resolve(reader.result);reader.onerror=()=>resolve(null);reader.readAsDataURL(value)})}function nativeAction(action,payload={}){const bridge=window.webkit?.messageHandlers?.forkitShare;if(!bridge)return false;bridge.postMessage({action,...payload});return true}window.forkitNativeShareResult=result=>{if(result.action==='open-global'){$('global-result').textContent=result.message||'';return}$('share-status').textContent=result.message||text('sharePrivacy');if(result.action==='copy-image')$('copy-image').textContent=text('copied');if(result.action==='copy-caption')$('copy-share').textContent=text('copied')};$('download-share').addEventListener('click',async()=>{const png=await dataUrl();if(!png)return;if(nativeAction('save',{png}))return;const value=await blob(),url=URL.createObjectURL(value),anchor=document.createElement('a');anchor.href=url;anchor.download='my-forkit-ai-footprint.png';anchor.click();setTimeout(()=>URL.revokeObjectURL(url),1000)});$('copy-share').addEventListener('click',async()=>{const caption=shareCaption();if(nativeAction('copy-caption',{caption}))return;await navigator.clipboard.writeText(caption);$('copy-share').textContent=text('copied')});$('copy-image').addEventListener('click',async()=>{const png=await dataUrl();if(!png)return;if(nativeAction('copy-image',{png}))return;const value=await blob();try{await navigator.clipboard.write([new ClipboardItem({'image/png':value})]);$('copy-image').textContent=text('copied')}catch{$('share-status').textContent=text('copyUnavailable')}});$('native-share').addEventListener('click',async()=>{const png=await dataUrl();if(!png)return;const caption=shareCaption();if(nativeAction('share',{png,caption}))return;const value=await blob(),file=new File([value],'my-forkit-ai-footprint.png',{type:'image/png'});if(navigator.share&&(!navigator.canShare||navigator.canShare({files:[file]})))await navigator.share({title:text('shareTitle'),text:caption,files:[file]});else await navigator.clipboard.writeText(caption)});
92
101
  for(const button of document.querySelectorAll('[data-resource-mode]'))button.addEventListener('click',()=>{explorerMode='resources';syncModeButtons();resourceMode=button.dataset.resourceMode;for(const item of document.querySelectorAll('[data-resource-mode]'))item.classList.toggle('active',item===button);explorer.setResourceMode(resourceMode);renderFocusSummary()});for(const button of document.querySelectorAll('[data-explorer-mode]'))button.addEventListener('click',()=>setExplorerMode(button.dataset.explorerMode));for(const section of document.querySelectorAll('.rail-section[data-view]'))section.addEventListener('click',event=>{if(event.target.closest('button,select'))return;setExplorerMode(section.dataset.view)});$('zoom-in').addEventListener('click',()=>{explorer.zoom(.65,explorer.visibleRange().reduce((a,b)=>a+b)/2);renderSystemTimeline()});$('zoom-out').addEventListener('click',()=>{explorer.zoom(1.4,explorer.visibleRange().reduce((a,b)=>a+b)/2);renderSystemTimeline()});$('reset-view').addEventListener('click',()=>{explorer.reset();setExplorerMode('pulse',false);renderMonitor()});$('system-timeline').addEventListener('wheel',event=>{event.preventDefault();explorer.zoom(event.deltaY<0?.78:1.22,explorer.visibleRange().reduce((a,b)=>a+b)/2);renderSystemTimeline()},{passive:false});let timelineResizeFrame=0;new ResizeObserver(()=>{cancelAnimationFrame(timelineResizeFrame);timelineResizeFrame=requestAnimationFrame(renderSystemTimeline)}).observe($('system-timeline'));for(const next of ['en','de'])$('locale-'+next).addEventListener('click',()=>{localStorage.setItem('forkit-footprints-locale',next);const url=new URL(location.href);url.searchParams.set('lang',next);location.href=url.href});const preferred=localStorage.getItem('forkit-footprints-locale');if(preferred&&preferred!==locale){const url=new URL(location.href);url.searchParams.set('lang',preferred);location.replace(url.href)}
93
102
  async function auth(endpoint,options={}){return fetch(endpoint,{method:'POST',headers:{'x-forkit-footprints-session':config.session_token,'content-type':'application/json'},...options})}
103
+ if(config&&$('global-permission-button'))$('global-permission-button').addEventListener('click',()=>{if(!nativeAction('global-permission'))$('global-result').textContent=text('permissionNativeOnly')})
94
104
  if(config){void auth(config.locale_endpoint,{body:JSON.stringify({locale})});$('monitor-button').addEventListener('click',async()=>{const endpoint=monitor.lifecycle==='monitoring'?config.monitor_stop_endpoint:config.monitor_start_endpoint,response=await auth(endpoint);if(response.ok){monitor=await response.json();renderMonitor()}});$('clear-button').addEventListener('click',async()=>{if(!confirm(text('clearConfirm')))return;const response=await auth(config.monitor_clear_endpoint);if(response.ok){monitor=await response.json();renderMonitor()}});$('scan-button').addEventListener('click',async()=>{const button=$('scan-button');button.disabled=true;$('scan-status').textContent=text('scanning');try{const response=await auth(config.endpoint);if(!response.ok)throw new Error();footprint=await response.json();renderHostedLocal();renderDetails();$('scan-status').textContent=text('refreshed')}catch{$('scan-status').textContent=text('scanFailed')}finally{button.disabled=false}});$('review-payload').addEventListener('click',()=>{if(!nativeAction('open-global'))location.assign(globalUrl)});(async()=>{try{const response=await auth(config.monitor_stream_endpoint);if(!response.ok||!response.body)return;const reader=response.body.getReader(),decoder=new TextDecoder();let buffer='';while(true){const part=await reader.read();if(part.done)break;buffer+=decoder.decode(part.value,{stream:true});const lines=buffer.split('\\n');buffer=lines.pop()||'';for(const line of lines)if(line){monitor=JSON.parse(line);renderMonitor()}}}catch{}})()}
95
105
  function appendSystemComparison(){const view=viewForDate(monitor.activity_view,selectedDateKey),selected=(view?.systems||[]).find(system=>system.signature===selectedSystemSignature),others=(view?.systems||[]).filter(system=>system.signature!==selectedSystemSignature&&system.active_seconds>0);if(!selected||!others.length)return;const container=document.createElement('div'),label=document.createElement('label'),select=document.createElement('select'),comparison=document.createElement('div');container.className='inspector';label.className='eyebrow';label.textContent=text('compareSystems');select.className='button';for(const system of others){const option=document.createElement('option');option.value=system.signature;option.textContent=system.name;select.append(option)}comparison.className='inspector-grid';const draw=()=>{comparison.replaceChildren();const other=(view.systems||[]).find(system=>system.signature===select.value);if(!other)return;appendInspectorStat(comparison,selected.name+' · '+text('activeDuration'),seconds(selected.active_seconds));appendInspectorStat(comparison,other.name+' · '+text('activeDuration'),seconds(other.active_seconds));appendInspectorStat(comparison,selected.name+' · '+text('contribution'),Math.round(selected.share*100)+'%');appendInspectorStat(comparison,other.name+' · '+text('contribution'),Math.round(other.share*100)+'%');appendInspectorStat(comparison,selected.name+' · '+text('longestActive'),seconds(selected.longest_active_seconds));appendInspectorStat(comparison,other.name+' · '+text('longestActive'),seconds(other.longest_active_seconds))};select.addEventListener('change',draw);container.append(label,select,comparison);$('app-inspector').append(container);draw()}const activeToolNamesBase=activeToolNames;activeToolNames=function(){return selectedDateKey?(currentView()?.systems||[]).filter(system=>system.active_seconds>0).map(system=>system.name):activeToolNamesBase()};const renderInsightsBase=renderInsights;renderInsights=function(){const source=monitor.activity_view?.insights;if(!source)return renderInsightsBase();monitor.activity_view.insights=source.filter(insight=>insight.kind!=='activity-share');try{return renderInsightsBase()}finally{monitor.activity_view.insights=source}};const renderInspectorBase=renderInspector;renderInspector=function(){renderInspectorBase();appendSystemComparison()};
96
106
  const renderInspectorLifecycleBase=renderInspector;renderInspector=function(){renderInspectorLifecycleBase();const stateNode=document.querySelector('.inspector-state'),evidence=$('selected-evidence'),previous=monitor.last_observation;if(stateNode&&monitor.lifecycle!=='monitoring')stateNode.textContent=text('previousObservationSaved');if(evidence&&monitor.lifecycle==='monitoring'&&!monitor.presence?.observation_eligible&&!monitor.observed_seconds&&previous)evidence.textContent=text('lastObservation')+' · '+pct(previous.activity_ratio)+' '+text('aiActive')+' · '+(previous.active_tool||text('noApp'))+' · '+format.format(previous.local_models_found||0)+' '+text('models')+' · '+format.format(previous.local_models_running||0)+' '+text('runningShort')};
package/dist/cli.js CHANGED
@@ -40,7 +40,7 @@ Options:
40
40
  --verbose Show detector and identity details
41
41
  --guess <count> Compare your estimate with discovered models
42
42
  --copy Copy the rendered local result to the clipboard
43
- --anonymous-payload Reserved for a completed one-hour monitor session
43
+ --anonymous-payload Reserved for a completed ten-minute monitor session
44
44
  --consent-share Prepare an aggregate payload; native-app consent controls Preview sync
45
45
  --output <file> Save the selected human or JSON report
46
46
  --model-dir <path> Inspect an explicit model directory; repeatable
@@ -58,7 +58,7 @@ Privacy:
58
58
  AI Footprints reads metadata only. It does not read model bytes or retain raw
59
59
  process commands or MCP configuration values. It does not write to any passport,
60
60
  registry, or Runtime_C2 service. The installed app may sync only the allowlisted anonymous
61
- Global AI Preview aggregate after explicit v2 consent and one valid hour.
61
+ Global AI Preview aggregate after explicit v3 consent and ten valid minutes.
62
62
  `;
63
63
  function parseArgs(args) {
64
64
  let command = 'install';
@@ -355,7 +355,7 @@ async function main(args = process.argv.slice(2)) {
355
355
  }
356
356
  if (options.command === 'install') {
357
357
  const destination = await installPersistentMacApp();
358
- process.stdout.write(`Forkit AI Footprint is installed at ${destination}\nOpen it later from Applications or Spotlight; Terminal is no longer required.\nOn first launch, Forkit asks whether an aggregate-only measurement may be contributed to the Global AI Preview after one valid observed hour. Nothing is sent unless the person explicitly agrees.\n`);
358
+ process.stdout.write(`Forkit AI Footprint is installed at ${destination}\nOpen it later from Applications or Spotlight; Terminal is no longer required.\nOn first launch, Forkit asks whether an aggregate-only measurement may be contributed to the Global AI Preview after ten valid observed minutes. Nothing is sent unless the person explicitly agrees.\n`);
359
359
  return 0;
360
360
  }
361
361
  if (options.command === 'serve') {
@@ -387,7 +387,7 @@ async function main(args = process.argv.slice(2)) {
387
387
  : `Observed ${summary.observed_seconds.toFixed(1)}s · AI active ${summary.active_seconds.toFixed(1)}s · ratio ${summary.activity_ratio === null ? 'unavailable' : `${Math.round(summary.activity_ratio * 100)}%`}\n`;
388
388
  await emit(rendered, options.output);
389
389
  if (options.anonymousPayload) {
390
- process.stderr.write('Anonymous preview requires a completed one-hour GUI session with the exact payload reviewed first. Nothing was uploaded.\n');
390
+ process.stderr.write('Anonymous preview requires a completed ten-minute GUI session with the exact payload reviewed first. Nothing was uploaded.\n');
391
391
  return 2;
392
392
  }
393
393
  return 0;
@@ -464,7 +464,7 @@ async function main(args = process.argv.slice(2)) {
464
464
  }
465
465
  if (options.anonymousPayload) {
466
466
  process.stderr.write(options.shareConsent
467
- ? 'Anonymous contribution was not prepared: a valid one-hour monitor session and exact payload review are required. Nothing was uploaded.\n'
467
+ ? 'Anonymous contribution was not prepared: a valid ten-minute monitor session and exact payload review are required. Nothing was uploaded.\n'
468
468
  : 'Anonymous contribution was not prepared: separate --consent-share is required. Nothing was uploaded.\n');
469
469
  return 2;
470
470
  }
@@ -1,5 +1,6 @@
1
1
  import type { AnonymousAiFootprintContribution } from './sharing';
2
2
  export declare const GLOBAL_PREVIEW_ENDPOINT = "https://www.forkit.dev/api/v1/ai-footprints";
3
+ /** The first eligible aggregate syncs immediately; later refreshes are hourly. */
3
4
  export declare const GLOBAL_PREVIEW_SYNC_INTERVAL_MS: number;
4
5
  export interface GlobalPreviewStatus {
5
6
  state: 'disabled' | 'waiting' | 'syncing' | 'contributed' | 'unavailable';
@@ -9,7 +9,8 @@ const promises_1 = __importDefault(require("node:fs/promises"));
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
10
  const local_device_1 = require("./local-device");
11
11
  exports.GLOBAL_PREVIEW_ENDPOINT = 'https://www.forkit.dev/api/v1/ai-footprints';
12
- exports.GLOBAL_PREVIEW_SYNC_INTERVAL_MS = 15 * 60 * 1000;
12
+ /** The first eligible aggregate syncs immediately; later refreshes are hourly. */
13
+ exports.GLOBAL_PREVIEW_SYNC_INTERVAL_MS = 60 * 60 * 1000;
13
14
  function base64url(value) {
14
15
  return value.toString('base64url');
15
16
  }
@@ -144,7 +145,9 @@ class GlobalPreviewContributor {
144
145
  const now = this.now();
145
146
  const lastAttempt = this.currentStatus.last_attempt_at ? Date.parse(this.currentStatus.last_attempt_at) : 0;
146
147
  const observedSeconds = contribution.observation.valid_seconds;
147
- if (lastAttempt && now.getTime() - lastAttempt < this.minimumSyncIntervalMs && observedSeconds <= this.currentStatus.last_observed_seconds + 60) {
148
+ // A new contributor is sent immediately. Once an attempt has been made,
149
+ // persist the hourly cadence across reconnects and browser restarts.
150
+ if (lastAttempt && now.getTime() - lastAttempt < this.minimumSyncIntervalMs) {
148
151
  return { ...this.currentStatus };
149
152
  }
150
153
  this.currentStatus = { ...this.currentStatus, state: 'syncing', last_attempt_at: now.toISOString(), last_error: null };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { detectAgentProducts, listSystemProcesses } from './agents';
2
2
  export { aggregateMacosFieldEvaluationDirectory, aggregateMacosFieldEvaluations } from './aggregate';
3
3
  export { runCensus } from './census';
4
- export { authorizeAnonymousAiFootprintContribution, buildAnonymousAiFootprintPreview, MINIMUM_COMPARISON_SECONDS } from './sharing';
4
+ export { authorizeAnonymousAiFootprintContribution, buildAnonymousAiFootprintPreview, MINIMUM_COMPARISON_SECONDS, MINIMUM_GLOBAL_CONTRIBUTION_SECONDS } from './sharing';
5
5
  export { GlobalPreviewContributor, GLOBAL_PREVIEW_ENDPOINT, GLOBAL_PREVIEW_SYNC_INTERVAL_MS } from './global-preview';
6
6
  export type { GlobalPreviewContributorOptions, GlobalPreviewStatus } from './global-preview';
7
7
  export { ActivityMonitor } from './monitor';
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.evaluateMacosFieldTruthFile = exports.evaluateMacosFieldTruth = exports.detectMacosMajorVersion = exports.formatDoctorReport = exports.formatCensusReport = exports.scanFilesystemModels = exports.parseLoopbackEndpointList = exports.parseLoopbackEndpoint = exports.runDoctor = exports.detectMcpConfigs = exports.detectAiTools = exports.startAiFootprintsServer = exports.buildLocalInsights = exports.renderCensusSharePage = exports.buildCensusShareSnapshot = exports.ActivityMonitor = exports.GLOBAL_PREVIEW_SYNC_INTERVAL_MS = exports.GLOBAL_PREVIEW_ENDPOINT = exports.GlobalPreviewContributor = exports.MINIMUM_COMPARISON_SECONDS = exports.buildAnonymousAiFootprintPreview = exports.authorizeAnonymousAiFootprintContribution = exports.runCensus = exports.aggregateMacosFieldEvaluations = exports.aggregateMacosFieldEvaluationDirectory = exports.listSystemProcesses = exports.detectAgentProducts = void 0;
17
+ exports.evaluateMacosFieldTruthFile = exports.evaluateMacosFieldTruth = exports.detectMacosMajorVersion = exports.formatDoctorReport = exports.formatCensusReport = exports.scanFilesystemModels = exports.parseLoopbackEndpointList = exports.parseLoopbackEndpoint = exports.runDoctor = exports.detectMcpConfigs = exports.detectAiTools = exports.startAiFootprintsServer = exports.buildLocalInsights = exports.renderCensusSharePage = exports.buildCensusShareSnapshot = exports.ActivityMonitor = exports.GLOBAL_PREVIEW_SYNC_INTERVAL_MS = exports.GLOBAL_PREVIEW_ENDPOINT = exports.GlobalPreviewContributor = exports.MINIMUM_GLOBAL_CONTRIBUTION_SECONDS = exports.MINIMUM_COMPARISON_SECONDS = exports.buildAnonymousAiFootprintPreview = exports.authorizeAnonymousAiFootprintContribution = exports.runCensus = exports.aggregateMacosFieldEvaluations = exports.aggregateMacosFieldEvaluationDirectory = exports.listSystemProcesses = exports.detectAgentProducts = void 0;
18
18
  var agents_1 = require("./agents");
19
19
  Object.defineProperty(exports, "detectAgentProducts", { enumerable: true, get: function () { return agents_1.detectAgentProducts; } });
20
20
  Object.defineProperty(exports, "listSystemProcesses", { enumerable: true, get: function () { return agents_1.listSystemProcesses; } });
@@ -27,6 +27,7 @@ var sharing_1 = require("./sharing");
27
27
  Object.defineProperty(exports, "authorizeAnonymousAiFootprintContribution", { enumerable: true, get: function () { return sharing_1.authorizeAnonymousAiFootprintContribution; } });
28
28
  Object.defineProperty(exports, "buildAnonymousAiFootprintPreview", { enumerable: true, get: function () { return sharing_1.buildAnonymousAiFootprintPreview; } });
29
29
  Object.defineProperty(exports, "MINIMUM_COMPARISON_SECONDS", { enumerable: true, get: function () { return sharing_1.MINIMUM_COMPARISON_SECONDS; } });
30
+ Object.defineProperty(exports, "MINIMUM_GLOBAL_CONTRIBUTION_SECONDS", { enumerable: true, get: function () { return sharing_1.MINIMUM_GLOBAL_CONTRIBUTION_SECONDS; } });
30
31
  var global_preview_1 = require("./global-preview");
31
32
  Object.defineProperty(exports, "GlobalPreviewContributor", { enumerable: true, get: function () { return global_preview_1.GlobalPreviewContributor; } });
32
33
  Object.defineProperty(exports, "GLOBAL_PREVIEW_ENDPOINT", { enumerable: true, get: function () { return global_preview_1.GLOBAL_PREVIEW_ENDPOINT; } });