forkit-ai-footprints 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/LICENSE +21 -0
  2. package/PRIVACY.md +150 -0
  3. package/README.md +313 -0
  4. package/STATUS.md +136 -0
  5. package/dist/agents.d.ts +22 -0
  6. package/dist/agents.js +281 -0
  7. package/dist/aggregate.d.ts +28 -0
  8. package/dist/aggregate.js +151 -0
  9. package/dist/assets/forkit-icon-dark.png +0 -0
  10. package/dist/assets/forkit-icon-light.png +0 -0
  11. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Info.plist +5 -0
  12. package/dist/bootstrap/Forkit AI Footprint.app/Contents/MacOS/Forkit AI Footprint +0 -0
  13. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/ForkitAIFootprint.icns +0 -0
  14. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/ForkitStatusTemplate.png +0 -0
  15. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/agents.d.ts +22 -0
  16. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/agents.js +281 -0
  17. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/aggregate.d.ts +28 -0
  18. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/aggregate.js +151 -0
  19. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/assets/forkit-icon-dark.png +0 -0
  20. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/assets/forkit-icon-light.png +0 -0
  21. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/census.d.ts +3 -0
  22. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/census.js +196 -0
  23. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/cli.d.ts +32 -0
  24. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/cli.js +404 -0
  25. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/doctor.d.ts +14 -0
  26. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/doctor.js +71 -0
  27. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/endpoints.d.ts +8 -0
  28. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/endpoints.js +45 -0
  29. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/evaluate.d.ts +44 -0
  30. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/evaluate.js +102 -0
  31. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/filesystem.d.ts +11 -0
  32. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/filesystem.js +226 -0
  33. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/format.d.ts +7 -0
  34. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/format.js +78 -0
  35. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/hash.d.ts +4 -0
  36. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/hash.js +16 -0
  37. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/index.d.ts +22 -0
  38. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/index.js +58 -0
  39. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/insights.d.ts +13 -0
  40. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/insights.js +76 -0
  41. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/local-device.d.ts +19 -0
  42. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/local-device.js +81 -0
  43. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/locales.json +272 -0
  44. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/localization.d.ts +277 -0
  45. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/localization.js +55 -0
  46. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/mcp.d.ts +9 -0
  47. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/mcp.js +93 -0
  48. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/monitor.d.ts +99 -0
  49. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/monitor.js +285 -0
  50. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/providers/base.d.ts +34 -0
  51. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/providers/base.js +88 -0
  52. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/providers/index.d.ts +6 -0
  53. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/providers/index.js +24 -0
  54. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/providers/lmstudio.d.ts +9 -0
  55. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/providers/lmstudio.js +71 -0
  56. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/providers/ollama.d.ts +12 -0
  57. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/providers/ollama.js +171 -0
  58. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/providers/openai-compatible.d.ts +10 -0
  59. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/providers/openai-compatible.js +68 -0
  60. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/resource-evidence.d.ts +17 -0
  61. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/resource-evidence.js +52 -0
  62. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/runtime-activity.d.ts +5 -0
  63. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/runtime-activity.js +66 -0
  64. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/runtime-identity.d.ts +7 -0
  65. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/runtime-identity.js +66 -0
  66. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/server.d.ts +21 -0
  67. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/server.js +308 -0
  68. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/share-page.d.ts +75 -0
  69. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/share-page.js +167 -0
  70. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/sharing.d.ts +35 -0
  71. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/sharing.js +67 -0
  72. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/tools.d.ts +9 -0
  73. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/tools.js +106 -0
  74. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/types.d.ts +166 -0
  75. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/types.js +3 -0
  76. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/version.d.ts +4 -0
  77. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/dist/version.js +13 -0
  78. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/node_modules/ps-list/index.d.ts +76 -0
  79. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/node_modules/ps-list/index.js +366 -0
  80. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/node_modules/ps-list/license +9 -0
  81. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/node_modules/ps-list/package.json +44 -0
  82. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/node_modules/ps-list/readme.md +56 -0
  83. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/node_modules/ps-list/vendor/fastlist-0.3.0-x64.exe +0 -0
  84. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/node_modules/ps-list/vendor/fastlist-0.3.0-x86.exe +0 -0
  85. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/app/package.json +6 -0
  86. package/dist/bootstrap/Forkit AI Footprint.app/Contents/Resources/runtime/node +0 -0
  87. package/dist/bootstrap/Forkit AI Footprint.app/Contents/_CodeSignature/CodeResources +931 -0
  88. package/dist/census.d.ts +3 -0
  89. package/dist/census.js +196 -0
  90. package/dist/cli.d.ts +32 -0
  91. package/dist/cli.js +404 -0
  92. package/dist/doctor.d.ts +14 -0
  93. package/dist/doctor.js +71 -0
  94. package/dist/endpoints.d.ts +8 -0
  95. package/dist/endpoints.js +45 -0
  96. package/dist/evaluate.d.ts +44 -0
  97. package/dist/evaluate.js +102 -0
  98. package/dist/filesystem.d.ts +11 -0
  99. package/dist/filesystem.js +226 -0
  100. package/dist/format.d.ts +7 -0
  101. package/dist/format.js +78 -0
  102. package/dist/hash.d.ts +4 -0
  103. package/dist/hash.js +16 -0
  104. package/dist/index.d.ts +22 -0
  105. package/dist/index.js +58 -0
  106. package/dist/insights.d.ts +13 -0
  107. package/dist/insights.js +76 -0
  108. package/dist/local-device.d.ts +19 -0
  109. package/dist/local-device.js +81 -0
  110. package/dist/locales.json +272 -0
  111. package/dist/localization.d.ts +277 -0
  112. package/dist/localization.js +55 -0
  113. package/dist/mcp.d.ts +9 -0
  114. package/dist/mcp.js +93 -0
  115. package/dist/monitor.d.ts +99 -0
  116. package/dist/monitor.js +285 -0
  117. package/dist/providers/base.d.ts +34 -0
  118. package/dist/providers/base.js +88 -0
  119. package/dist/providers/index.d.ts +6 -0
  120. package/dist/providers/index.js +24 -0
  121. package/dist/providers/lmstudio.d.ts +9 -0
  122. package/dist/providers/lmstudio.js +71 -0
  123. package/dist/providers/ollama.d.ts +12 -0
  124. package/dist/providers/ollama.js +171 -0
  125. package/dist/providers/openai-compatible.d.ts +10 -0
  126. package/dist/providers/openai-compatible.js +68 -0
  127. package/dist/resource-evidence.d.ts +17 -0
  128. package/dist/resource-evidence.js +52 -0
  129. package/dist/runtime-activity.d.ts +5 -0
  130. package/dist/runtime-activity.js +66 -0
  131. package/dist/runtime-identity.d.ts +7 -0
  132. package/dist/runtime-identity.js +66 -0
  133. package/dist/server.d.ts +21 -0
  134. package/dist/server.js +308 -0
  135. package/dist/share-page.d.ts +75 -0
  136. package/dist/share-page.js +167 -0
  137. package/dist/sharing.d.ts +35 -0
  138. package/dist/sharing.js +67 -0
  139. package/dist/tools.d.ts +9 -0
  140. package/dist/tools.js +106 -0
  141. package/dist/types.d.ts +166 -0
  142. package/dist/types.js +3 -0
  143. package/dist/version.d.ts +4 -0
  144. package/dist/version.js +13 -0
  145. package/docs/GLOBAL_DISTRIBUTION.md +79 -0
  146. package/docs/MACOS_ACCURACY_GATE.md +140 -0
  147. package/docs/MACOS_TESTER_GUIDE.md +88 -0
  148. package/examples/macos-truth.template.json +10 -0
  149. package/package.json +75 -0
  150. package/release/channels.json +41 -0
@@ -0,0 +1,102 @@
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.detectMacosMajorVersion = detectMacosMajorVersion;
7
+ exports.evaluateMacosFieldTruth = evaluateMacosFieldTruth;
8
+ exports.evaluateMacosFieldTruthFile = evaluateMacosFieldTruthFile;
9
+ const promises_1 = __importDefault(require("node:fs/promises"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const node_child_process_1 = require("node:child_process");
12
+ const census_1 = require("./census");
13
+ function detectMacosMajorVersion() {
14
+ if (process.platform !== 'darwin')
15
+ return null;
16
+ const result = (0, node_child_process_1.spawnSync)('/usr/bin/sw_vers', ['-productVersion'], {
17
+ encoding: 'utf8',
18
+ shell: false,
19
+ timeout: 2_000,
20
+ });
21
+ if (result.status !== 0)
22
+ return null;
23
+ const major = Number.parseInt(result.stdout.trim().split('.')[0] ?? '', 10);
24
+ return Number.isInteger(major) && major > 0 ? major : null;
25
+ }
26
+ function stringSet(value, label) {
27
+ if (!Array.isArray(value) || !value.every((entry) => typeof entry === 'string' && entry.length > 0)) {
28
+ throw new Error(`${label} must contain non-empty strings.`);
29
+ }
30
+ return new Set(value);
31
+ }
32
+ function score(actualInput, expectedInput) {
33
+ const actual = new Set(actualInput);
34
+ const expected = new Set(expectedInput);
35
+ const truePositive = [...actual].filter((value) => expected.has(value)).length;
36
+ const falsePositive = [...actual].filter((value) => !expected.has(value)).length;
37
+ const falseNegative = [...expected].filter((value) => !actual.has(value)).length;
38
+ return {
39
+ actual_count: actual.size,
40
+ expected_count: expected.size,
41
+ true_positive: truePositive,
42
+ false_positive: falsePositive,
43
+ false_negative: falseNegative,
44
+ precision: actual.size === 0 ? null : truePositive / actual.size,
45
+ recall: expected.size === 0 ? null : truePositive / expected.size,
46
+ };
47
+ }
48
+ function parseTruth(value) {
49
+ if (!value || typeof value !== 'object' || Array.isArray(value))
50
+ throw new Error('Truth file must contain an object.');
51
+ const record = value;
52
+ if (record.schema_version !== '1.0')
53
+ throw new Error('Unsupported truth schema version.');
54
+ const expectedValue = record.expected;
55
+ if (!expectedValue || typeof expectedValue !== 'object' || Array.isArray(expectedValue)) {
56
+ throw new Error('Truth file must contain an expected object.');
57
+ }
58
+ const expected = expectedValue;
59
+ return {
60
+ schema_version: '1.0',
61
+ expected: {
62
+ agent_signatures: [...stringSet(expected.agent_signatures, 'expected.agent_signatures')],
63
+ tool_names: [...stringSet(expected.tool_names, 'expected.tool_names')],
64
+ online_runtime_names: [...stringSet(expected.online_runtime_names, 'expected.online_runtime_names')],
65
+ model_keys: [...stringSet(expected.model_keys, 'expected.model_keys')],
66
+ mcp_clients: [...stringSet(expected.mcp_clients, 'expected.mcp_clients')],
67
+ },
68
+ };
69
+ }
70
+ function evaluateMacosFieldTruth(truthInput, report, macosMajor = detectMacosMajorVersion()) {
71
+ if (report.system.platform !== 'darwin')
72
+ throw new Error('macOS field evaluation requires a macOS Census Report.');
73
+ const truth = parseTruth(truthInput);
74
+ return {
75
+ evaluation: 'macos-local-labelled-device',
76
+ schema_version: truth.schema_version,
77
+ product_version: report.product_version,
78
+ environment: {
79
+ platform: 'darwin',
80
+ architecture: report.system.architecture,
81
+ macos_major: macosMajor,
82
+ node_major: report.system.node_major,
83
+ },
84
+ metrics: {
85
+ agents: score(report.agents.map((item) => item.signature), truth.expected.agent_signatures),
86
+ tools: score(report.tools.map((item) => item.name), truth.expected.tool_names),
87
+ runtimes: score(report.runtimes.filter((item) => item.evidence_status === 'online').map((item) => item.name), truth.expected.online_runtime_names),
88
+ models: score(report.models.map((item) => `${item.runtime}:${item.name}`), truth.expected.model_keys),
89
+ mcp: score(report.mcp_configs.map((item) => item.client), truth.expected.mcp_clients),
90
+ },
91
+ uploaded: false,
92
+ field_accuracy_claim_allowed: false,
93
+ };
94
+ }
95
+ async function evaluateMacosFieldTruthFile(filePath) {
96
+ if (process.platform !== 'darwin')
97
+ throw new Error('macOS field evaluation must run on macOS.');
98
+ const content = await promises_1.default.readFile(node_path_1.default.resolve(filePath), 'utf8');
99
+ const truth = JSON.parse(content);
100
+ return evaluateMacosFieldTruth(truth, await (0, census_1.runCensus)());
101
+ }
102
+ //# sourceMappingURL=evaluate.js.map
@@ -0,0 +1,11 @@
1
+ import type { RuntimeScanResult } from './types';
2
+ export interface FilesystemScanOptions {
3
+ roots?: string[];
4
+ minBytes?: number;
5
+ maxDepth?: number;
6
+ maxFiles?: number;
7
+ }
8
+ export declare function getDefaultModelRoots(): string[];
9
+ export declare function scanFilesystemModels(observedAt: string, input?: FilesystemScanOptions): Promise<RuntimeScanResult>;
10
+ export declare function filesystemRootFingerprint(root: string): string;
11
+ //# sourceMappingURL=filesystem.d.ts.map
@@ -0,0 +1,226 @@
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.getDefaultModelRoots = getDefaultModelRoots;
7
+ exports.scanFilesystemModels = scanFilesystemModels;
8
+ exports.filesystemRootFingerprint = filesystemRootFingerprint;
9
+ const node_crypto_1 = require("node:crypto");
10
+ const promises_1 = __importDefault(require("node:fs/promises"));
11
+ const node_os_1 = __importDefault(require("node:os"));
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const hash_1 = require("./hash");
14
+ const base_1 = require("./providers/base");
15
+ const MODEL_EXTENSIONS = new Set([
16
+ '.bin',
17
+ '.ggml',
18
+ '.gguf',
19
+ '.onnx',
20
+ '.pt',
21
+ '.pth',
22
+ '.safetensors',
23
+ ]);
24
+ const DEFAULT_MIN_BYTES = 1;
25
+ const DEFAULT_MAX_DEPTH = 32;
26
+ const DEFAULT_MAX_FILES = 100_000;
27
+ function configuredRootsFromEnvironment() {
28
+ return String(process.env.FORKIT_CENSUS_MODEL_DIRS ?? '')
29
+ .split(/[\n,]/)
30
+ .map((entry) => entry.trim())
31
+ .filter(Boolean);
32
+ }
33
+ function getDefaultModelRoots() {
34
+ const home = node_os_1.default.homedir();
35
+ const janData = node_path_1.default.join(home, 'Library', 'Application Support', 'Jan', 'data');
36
+ const xdgHuggingFaceHub = process.env.XDG_CACHE_HOME
37
+ ? node_path_1.default.join(process.env.XDG_CACHE_HOME, 'huggingface', 'hub')
38
+ : '';
39
+ const candidates = [
40
+ ...configuredRootsFromEnvironment(),
41
+ node_path_1.default.join(home, '.cache', 'huggingface', 'hub'),
42
+ node_path_1.default.join(home, '.ollama', 'models'),
43
+ process.env.HF_HOME ? node_path_1.default.join(process.env.HF_HOME, 'hub') : '',
44
+ process.env.HF_HUB_CACHE ?? '',
45
+ xdgHuggingFaceHub,
46
+ node_path_1.default.join(home, '.cache', 'lm-studio', 'models'),
47
+ node_path_1.default.join(home, '.lmstudio', 'models'),
48
+ node_path_1.default.join(janData, 'llamacpp', 'models'),
49
+ node_path_1.default.join(janData, 'mlx', 'models'),
50
+ node_path_1.default.join(home, 'models'),
51
+ node_path_1.default.join(home, 'model'),
52
+ node_path_1.default.join(home, 'ai', 'models'),
53
+ node_path_1.default.join(home, '.cache', 'mlx-models'),
54
+ ];
55
+ // HF_XET_CACHE is intentionally excluded: it is a transport chunk cache,
56
+ // not a model inventory, and counting it would duplicate model storage.
57
+ return [...new Set(candidates.filter(Boolean).map((entry) => node_path_1.default.resolve(entry)))];
58
+ }
59
+ async function directoryExists(directory) {
60
+ try {
61
+ return (await promises_1.default.stat(directory)).isDirectory();
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ function huggingFaceName(relativePath) {
68
+ const marker = relativePath
69
+ .split(/[\\/]/)
70
+ .find((part) => part.startsWith('models--'));
71
+ return marker?.replace(/^models--/, '').replaceAll('--', '/') || null;
72
+ }
73
+ function genericModelName(relativePath) {
74
+ const parsed = node_path_1.default.parse(relativePath);
75
+ const base = parsed.name;
76
+ if (!['model', 'pytorch_model', 'consolidated', 'weights', 'adapter_model'].includes(base.toLowerCase())) {
77
+ return base.replace(/[-_.]?\d{1,5}-of-\d{1,5}$/i, '') || base;
78
+ }
79
+ return node_path_1.default.basename(parsed.dir) || base;
80
+ }
81
+ function ollamaStorageFile(root, relativePath) {
82
+ if (!root.replaceAll('\\', '/').endsWith('/.ollama/models'))
83
+ return false;
84
+ const normalized = relativePath.replaceAll('\\', '/');
85
+ return normalized.startsWith('blobs/sha256-') || normalized.startsWith('manifests/');
86
+ }
87
+ async function collectFiles(root, directory, depth, options, state) {
88
+ if (depth > options.maxDepth || state.files.length >= options.maxFiles) {
89
+ state.limitReached = true;
90
+ return;
91
+ }
92
+ let entries;
93
+ try {
94
+ entries = await promises_1.default.readdir(directory, { withFileTypes: true });
95
+ }
96
+ catch {
97
+ return;
98
+ }
99
+ for (const entry of entries) {
100
+ if (state.files.length >= options.maxFiles) {
101
+ state.limitReached = true;
102
+ return;
103
+ }
104
+ const absolutePath = node_path_1.default.join(directory, entry.name);
105
+ if (entry.isDirectory()) {
106
+ await collectFiles(root, absolutePath, depth + 1, options, state);
107
+ continue;
108
+ }
109
+ if (!entry.isFile() && !entry.isSymbolicLink())
110
+ continue;
111
+ try {
112
+ const stat = await promises_1.default.stat(absolutePath);
113
+ if (!stat.isFile())
114
+ continue;
115
+ const relativePath = node_path_1.default.relative(root, absolutePath);
116
+ const isOllamaStorage = ollamaStorageFile(root, relativePath);
117
+ const extension = node_path_1.default.extname(entry.name).toLowerCase();
118
+ if (!isOllamaStorage && !MODEL_EXTENSIONS.has(extension))
119
+ continue;
120
+ if (!isOllamaStorage && stat.size < options.minBytes)
121
+ continue;
122
+ const fileIdentity = `${stat.dev}:${stat.ino}`;
123
+ if (state.seenFileIdentities.has(fileIdentity))
124
+ continue;
125
+ state.seenFileIdentities.add(fileIdentity);
126
+ const hfName = huggingFaceName(relativePath);
127
+ state.files.push({
128
+ root,
129
+ relativePath,
130
+ size: stat.size,
131
+ modifiedAt: stat.mtime.toISOString(),
132
+ extension,
133
+ modelName: isOllamaStorage ? 'ollama-storage' : hfName ?? genericModelName(relativePath),
134
+ sourceKind: isOllamaStorage ? 'ollama-store' : hfName ? 'huggingface-cache' : 'configured-model-root',
135
+ fileIdentity,
136
+ inventoryModel: !isOllamaStorage,
137
+ });
138
+ }
139
+ catch {
140
+ // A file can disappear between directory enumeration and stat.
141
+ }
142
+ }
143
+ }
144
+ function collapseFiles(files) {
145
+ const groups = new Map();
146
+ for (const file of files.filter((entry) => entry.inventoryModel)) {
147
+ const key = `${file.root}:${file.sourceKind}:${file.modelName}`;
148
+ groups.set(key, [...(groups.get(key) ?? []), file]);
149
+ }
150
+ return [...groups.values()].map((group) => {
151
+ const first = group[0];
152
+ const evidence = group
153
+ .map((file) => `${file.relativePath}:${file.size}:${file.modifiedAt}`)
154
+ .sort()
155
+ .join('|');
156
+ const identity = (0, node_crypto_1.createHash)('sha256').update(evidence, 'utf8').digest('hex');
157
+ const modifiedAt = group.map((file) => file.modifiedAt).sort().at(-1) ?? null;
158
+ return (0, base_1.createModel)({
159
+ name: first.modelName,
160
+ runtime: 'filesystem',
161
+ identityKind: 'metadata-sha256',
162
+ identity,
163
+ source: 'filesystem',
164
+ confidence: 'low',
165
+ sizeBytes: group.reduce((total, file) => total + file.size, 0),
166
+ modifiedAt,
167
+ locationHint: first.sourceKind,
168
+ });
169
+ }).sort((left, right) => left.name.localeCompare(right.name));
170
+ }
171
+ async function scanFilesystemModels(observedAt, input = {}) {
172
+ const roots = [...new Set((input.roots ?? getDefaultModelRoots()).map((entry) => node_path_1.default.resolve(entry)))];
173
+ const existingRoots = (await Promise.all(roots.map(async (root) => ({
174
+ root,
175
+ exists: await directoryExists(root),
176
+ })))).filter((entry) => entry.exists).map((entry) => entry.root);
177
+ const state = { files: [], seenFileIdentities: new Set(), limitReached: false };
178
+ const options = {
179
+ minBytes: input.minBytes ?? DEFAULT_MIN_BYTES,
180
+ maxDepth: input.maxDepth ?? DEFAULT_MAX_DEPTH,
181
+ maxFiles: input.maxFiles ?? DEFAULT_MAX_FILES,
182
+ };
183
+ for (const root of existingRoots) {
184
+ await collectFiles(root, root, 0, options, state);
185
+ }
186
+ const models = collapseFiles(state.files);
187
+ const logicalBytes = state.files.reduce((total, file) => total + file.size, 0);
188
+ return {
189
+ runtime: (0, base_1.createRuntime)({
190
+ name: 'filesystem',
191
+ endpoint: null,
192
+ status: existingRoots.length > 0 ? 'inventory-only' : 'unavailable',
193
+ modelCount: models.length,
194
+ observedAt,
195
+ errorCode: existingRoots.length > 0 ? null : 'no_model_directories_found',
196
+ }),
197
+ models,
198
+ storage: {
199
+ logical_bytes: logicalBytes,
200
+ recognized_file_count: state.files.length,
201
+ complete: !state.limitReached,
202
+ measurement: 'recognized-logical-file-bytes',
203
+ },
204
+ warnings: models.length > 0 ? [
205
+ {
206
+ code: 'filesystem_identity_is_metadata_only',
207
+ message: 'Filesystem identities use names, sizes, and modification times; model bytes were not read.',
208
+ scope: 'model',
209
+ },
210
+ {
211
+ code: 'filesystem_detection_is_best_effort',
212
+ message: 'Filesystem findings are best-effort suggestions and may include unrelated weight-like files or miss unsupported locations.',
213
+ scope: 'model',
214
+ },
215
+ ...(state.limitReached ? [{
216
+ code: 'filesystem_scan_limit_reached',
217
+ message: 'The recognized model-file total is incomplete because the local safety limit was reached.',
218
+ scope: 'model',
219
+ }] : []),
220
+ ] : [],
221
+ };
222
+ }
223
+ function filesystemRootFingerprint(root) {
224
+ return (0, hash_1.stableId)('root', node_path_1.default.resolve(root));
225
+ }
226
+ //# sourceMappingURL=filesystem.js.map
@@ -0,0 +1,7 @@
1
+ import type { CensusReport } from './types';
2
+ import type { DoctorReport } from './doctor';
3
+ export declare function formatCensusReport(report: CensusReport, options?: {
4
+ verbose?: boolean;
5
+ }): string;
6
+ export declare function formatDoctorReport(report: DoctorReport): string;
7
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatCensusReport = formatCensusReport;
4
+ exports.formatDoctorReport = formatDoctorReport;
5
+ function line(label, value) {
6
+ return ` ${label.padEnd(20)} ${value}`;
7
+ }
8
+ function formatCensusReport(report, options = {}) {
9
+ const output = [
10
+ 'Forkit AI Footprints',
11
+ 'Metadata-only local AI inventory',
12
+ '-'.repeat(72),
13
+ line('census id', report.census_id),
14
+ line('generated', report.generated_at),
15
+ line('system', `${report.system.platform}/${report.system.architecture} · Node ${report.system.node_major}`),
16
+ line('runtimes', `${report.summary.available_runtime_count} available / ${report.summary.runtime_count} observed`),
17
+ line('models', `${report.summary.model_count} discovered · ${report.summary.confirmed_running_model_count} confirmed running`),
18
+ line('model storage', `${(report.summary.storage_bytes / (1000 ** 3)).toFixed(2)} GB · ${(report.summary.storage_bytes / (1024 ** 3)).toFixed(2)} GiB · ${report.summary.storage_bytes.toLocaleString('en-US')} recognized logical bytes · ${report.summary.storage_complete ? 'complete supported-root scan' : 'incomplete scan'}`),
19
+ line('agent products', report.summary.agent_product_count),
20
+ line('agent processes', report.summary.agent_process_count),
21
+ line('agent CPU snapshot', report.summary.agent_cpu_percent === null ? 'unavailable' : `${report.summary.agent_cpu_percent.toFixed(1)}%`),
22
+ line('agent memory snap.', report.summary.agent_memory_percent === null ? 'unavailable' : `${report.summary.agent_memory_percent.toFixed(1)}%`),
23
+ line('AI tools', report.summary.tool_count),
24
+ line('MCP configs', report.summary.mcp_config_count),
25
+ line('warnings', report.summary.warning_count),
26
+ line('privacy', 'metadata only · no model bytes · no retained commands/config values'),
27
+ ];
28
+ if (report.models.length > 0) {
29
+ output.push('', 'Models');
30
+ for (const model of report.models) {
31
+ output.push(` - ${model.name} · ${model.runtime} · ${model.evidence_status} · ${model.confidence} confidence${options.verbose ? ` · ${model.identity_kind}` : ''}`);
32
+ }
33
+ }
34
+ if (report.runtimes.length > 0) {
35
+ output.push('', 'Runtimes');
36
+ for (const runtime of report.runtimes) {
37
+ output.push(` - ${runtime.name} · ${runtime.evidence_status} · ${runtime.model_count} model(s)`);
38
+ }
39
+ }
40
+ if (report.agents.length > 0) {
41
+ output.push('', 'Agent products');
42
+ for (const agent of report.agents) {
43
+ output.push(` - ${agent.name} · ${agent.kind} · ${agent.confidence} confidence · ${agent.instance_count} process(es)`);
44
+ }
45
+ }
46
+ if (report.tools.length > 0) {
47
+ output.push('', 'AI tools and environments');
48
+ for (const tool of report.tools) {
49
+ output.push(` - ${tool.name} · ${tool.evidence_status} · ${tool.confidence} confidence${options.verbose ? ` · ${tool.detector_types.join('+')}` : ''}`);
50
+ }
51
+ }
52
+ if (report.mcp_configs.length > 0) {
53
+ output.push('', 'MCP configurations');
54
+ for (const config of report.mcp_configs) {
55
+ output.push(` - ${config.client} · configured · ${config.server_count} server config(s)`);
56
+ }
57
+ }
58
+ if (report.guess.provided !== null) {
59
+ const difference = report.guess.difference ?? 0;
60
+ output.push('', `Your guess: ${report.guess.provided} · discovered: ${report.guess.discovered} · difference: ${difference >= 0 ? '+' : ''}${difference}`);
61
+ }
62
+ if (report.warnings.length > 0) {
63
+ output.push('', 'Review notes');
64
+ for (const warning of report.warnings)
65
+ output.push(` - ${warning.message}`);
66
+ }
67
+ output.push('', 'Optional next step: use Forkit Connect if you later choose Passport review and an explicit final Mint. AI Footprints has not created or registered anything.');
68
+ return `${output.join('\n')}\n`;
69
+ }
70
+ function formatDoctorReport(report) {
71
+ const output = [
72
+ 'Forkit AI Footprints Doctor',
73
+ '-'.repeat(72),
74
+ ...report.checks.map((check) => line(check.name, `${check.ok ? 'ok' : 'failed'} · ${check.detail}`)),
75
+ ];
76
+ return `${output.join('\n')}\n`;
77
+ }
78
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1,4 @@
1
+ export declare function sha256(input: string): string;
2
+ export declare function stableId(prefix: string, input: string): string;
3
+ export declare function normalizeLabel(value: string): string;
4
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sha256 = sha256;
4
+ exports.stableId = stableId;
5
+ exports.normalizeLabel = normalizeLabel;
6
+ const node_crypto_1 = require("node:crypto");
7
+ function sha256(input) {
8
+ return (0, node_crypto_1.createHash)('sha256').update(input, 'utf8').digest('hex');
9
+ }
10
+ function stableId(prefix, input) {
11
+ return `${prefix}_${sha256(input).slice(0, 24)}`;
12
+ }
13
+ function normalizeLabel(value) {
14
+ return String(value || '').trim().replace(/\s+/g, ' ');
15
+ }
16
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1,22 @@
1
+ export { detectAgentProducts, listSystemProcesses } from './agents';
2
+ export { aggregateMacosFieldEvaluationDirectory, aggregateMacosFieldEvaluations } from './aggregate';
3
+ export { runCensus } from './census';
4
+ export { authorizeAnonymousAiFootprintContribution, buildAnonymousAiFootprintPreview, MINIMUM_COMPARISON_SECONDS } from './sharing';
5
+ export { ActivityMonitor } from './monitor';
6
+ export type * from './monitor';
7
+ export { buildCensusShareSnapshot, renderCensusSharePage } from './share-page';
8
+ export type { AiFootprintPageOptions } from './share-page';
9
+ export { buildLocalInsights } from './insights';
10
+ export type { LocalInsight, LocalInsightFootprint, LocalInsightKind } from './insights';
11
+ export { startAiFootprintsServer } from './server';
12
+ export type { AiFootprintsServer, AiFootprintsServerOptions } from './server';
13
+ export { detectAiTools } from './tools';
14
+ export { detectMcpConfigs } from './mcp';
15
+ export { runDoctor } from './doctor';
16
+ export { parseLoopbackEndpoint, parseLoopbackEndpointList } from './endpoints';
17
+ export { scanFilesystemModels } from './filesystem';
18
+ export { formatCensusReport, formatDoctorReport } from './format';
19
+ export { detectMacosMajorVersion, evaluateMacosFieldTruth, evaluateMacosFieldTruthFile } from './evaluate';
20
+ export * from './providers';
21
+ export type * from './types';
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
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.MINIMUM_COMPARISON_SECONDS = exports.buildAnonymousAiFootprintPreview = exports.authorizeAnonymousAiFootprintContribution = exports.runCensus = exports.aggregateMacosFieldEvaluations = exports.aggregateMacosFieldEvaluationDirectory = exports.listSystemProcesses = exports.detectAgentProducts = void 0;
18
+ var agents_1 = require("./agents");
19
+ Object.defineProperty(exports, "detectAgentProducts", { enumerable: true, get: function () { return agents_1.detectAgentProducts; } });
20
+ Object.defineProperty(exports, "listSystemProcesses", { enumerable: true, get: function () { return agents_1.listSystemProcesses; } });
21
+ var aggregate_1 = require("./aggregate");
22
+ Object.defineProperty(exports, "aggregateMacosFieldEvaluationDirectory", { enumerable: true, get: function () { return aggregate_1.aggregateMacosFieldEvaluationDirectory; } });
23
+ Object.defineProperty(exports, "aggregateMacosFieldEvaluations", { enumerable: true, get: function () { return aggregate_1.aggregateMacosFieldEvaluations; } });
24
+ var census_1 = require("./census");
25
+ Object.defineProperty(exports, "runCensus", { enumerable: true, get: function () { return census_1.runCensus; } });
26
+ var sharing_1 = require("./sharing");
27
+ Object.defineProperty(exports, "authorizeAnonymousAiFootprintContribution", { enumerable: true, get: function () { return sharing_1.authorizeAnonymousAiFootprintContribution; } });
28
+ Object.defineProperty(exports, "buildAnonymousAiFootprintPreview", { enumerable: true, get: function () { return sharing_1.buildAnonymousAiFootprintPreview; } });
29
+ Object.defineProperty(exports, "MINIMUM_COMPARISON_SECONDS", { enumerable: true, get: function () { return sharing_1.MINIMUM_COMPARISON_SECONDS; } });
30
+ var monitor_1 = require("./monitor");
31
+ Object.defineProperty(exports, "ActivityMonitor", { enumerable: true, get: function () { return monitor_1.ActivityMonitor; } });
32
+ var share_page_1 = require("./share-page");
33
+ Object.defineProperty(exports, "buildCensusShareSnapshot", { enumerable: true, get: function () { return share_page_1.buildCensusShareSnapshot; } });
34
+ Object.defineProperty(exports, "renderCensusSharePage", { enumerable: true, get: function () { return share_page_1.renderCensusSharePage; } });
35
+ var insights_1 = require("./insights");
36
+ Object.defineProperty(exports, "buildLocalInsights", { enumerable: true, get: function () { return insights_1.buildLocalInsights; } });
37
+ var server_1 = require("./server");
38
+ Object.defineProperty(exports, "startAiFootprintsServer", { enumerable: true, get: function () { return server_1.startAiFootprintsServer; } });
39
+ var tools_1 = require("./tools");
40
+ Object.defineProperty(exports, "detectAiTools", { enumerable: true, get: function () { return tools_1.detectAiTools; } });
41
+ var mcp_1 = require("./mcp");
42
+ Object.defineProperty(exports, "detectMcpConfigs", { enumerable: true, get: function () { return mcp_1.detectMcpConfigs; } });
43
+ var doctor_1 = require("./doctor");
44
+ Object.defineProperty(exports, "runDoctor", { enumerable: true, get: function () { return doctor_1.runDoctor; } });
45
+ var endpoints_1 = require("./endpoints");
46
+ Object.defineProperty(exports, "parseLoopbackEndpoint", { enumerable: true, get: function () { return endpoints_1.parseLoopbackEndpoint; } });
47
+ Object.defineProperty(exports, "parseLoopbackEndpointList", { enumerable: true, get: function () { return endpoints_1.parseLoopbackEndpointList; } });
48
+ var filesystem_1 = require("./filesystem");
49
+ Object.defineProperty(exports, "scanFilesystemModels", { enumerable: true, get: function () { return filesystem_1.scanFilesystemModels; } });
50
+ var format_1 = require("./format");
51
+ Object.defineProperty(exports, "formatCensusReport", { enumerable: true, get: function () { return format_1.formatCensusReport; } });
52
+ Object.defineProperty(exports, "formatDoctorReport", { enumerable: true, get: function () { return format_1.formatDoctorReport; } });
53
+ var evaluate_1 = require("./evaluate");
54
+ Object.defineProperty(exports, "detectMacosMajorVersion", { enumerable: true, get: function () { return evaluate_1.detectMacosMajorVersion; } });
55
+ Object.defineProperty(exports, "evaluateMacosFieldTruth", { enumerable: true, get: function () { return evaluate_1.evaluateMacosFieldTruth; } });
56
+ Object.defineProperty(exports, "evaluateMacosFieldTruthFile", { enumerable: true, get: function () { return evaluate_1.evaluateMacosFieldTruthFile; } });
57
+ __exportStar(require("./providers"), exports);
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ import type { MonitorSnapshot } from './monitor';
2
+ import { type UiLocale } from './localization';
3
+ export type LocalInsightKind = 'activity-share' | 'longest-block' | 'workflow' | 'local-models-unused' | 'mostly-idle';
4
+ export interface LocalInsight {
5
+ kind: LocalInsightKind;
6
+ text: string;
7
+ }
8
+ export interface LocalInsightFootprint {
9
+ model_record_count: number;
10
+ confirmed_running_model_count: number;
11
+ }
12
+ export declare function buildLocalInsights(monitor: MonitorSnapshot, footprint: LocalInsightFootprint, minimumSeconds?: number, locale?: UiLocale): LocalInsight[];
13
+ //# sourceMappingURL=insights.d.ts.map
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildLocalInsights = buildLocalInsights;
4
+ const localization_1 = require("./localization");
5
+ const MINIMUM_INSIGHT_SECONDS = 60;
6
+ function durationLabel(value, locale) {
7
+ const seconds = Math.max(0, Math.round(value));
8
+ if (seconds < 60)
9
+ return locale === 'de' ? `${seconds} Sek` : `${seconds}s`;
10
+ if (seconds < 3600)
11
+ return locale === 'de' ? `${Math.floor(seconds / 60)} Min ${seconds % 60} Sek` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
12
+ return locale === 'de' ? `${Math.floor(seconds / 3600)} Std ${Math.floor((seconds % 3600) / 60)} Min` : `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
13
+ }
14
+ function longestActiveSeconds(timeline, gapToleranceMs) {
15
+ let longest = 0;
16
+ let blockStart = null;
17
+ let blockEnd = null;
18
+ let observationId;
19
+ for (const segment of timeline) {
20
+ if (segment.state !== 'working-now') {
21
+ if (blockStart !== null && blockEnd !== null)
22
+ longest = Math.max(longest, blockEnd - blockStart);
23
+ blockStart = null;
24
+ blockEnd = null;
25
+ observationId = undefined;
26
+ continue;
27
+ }
28
+ const started = Date.parse(segment.started_at);
29
+ const ended = Date.parse(segment.ended_at);
30
+ if (!Number.isFinite(started) || !Number.isFinite(ended) || ended < started)
31
+ continue;
32
+ const sessionChanged = observationId !== undefined && segment.observation_id !== undefined && segment.observation_id !== observationId;
33
+ if (blockStart === null || blockEnd === null || sessionChanged || started - blockEnd > gapToleranceMs) {
34
+ if (blockStart !== null && blockEnd !== null)
35
+ longest = Math.max(longest, blockEnd - blockStart);
36
+ blockStart = started;
37
+ blockEnd = ended;
38
+ observationId = segment.observation_id;
39
+ }
40
+ else
41
+ blockEnd = Math.max(blockEnd, ended);
42
+ }
43
+ if (blockStart !== null && blockEnd !== null)
44
+ longest = Math.max(longest, blockEnd - blockStart);
45
+ return longest / 1000;
46
+ }
47
+ function buildLocalInsights(monitor, footprint, minimumSeconds = MINIMUM_INSIGHT_SECONDS, locale = 'en') {
48
+ if (monitor.observed_seconds < minimumSeconds || monitor.activity_ratio === null)
49
+ return [];
50
+ const insights = [];
51
+ const activeProducts = monitor.products.filter((product) => product.active_seconds > 0);
52
+ const activityPercent = Math.round(monitor.activity_ratio * 100);
53
+ if (monitor.activity_ratio <= 0.2) {
54
+ insights.push({ kind: 'mostly-idle', text: (0, localization_1.uiText)(locale, 'insightMostlyIdle') });
55
+ }
56
+ else if (activeProducts.length === 1) {
57
+ insights.push({ kind: 'activity-share', text: (0, localization_1.uiText)(locale, 'insightActivityProduct', { product: activeProducts[0].name, percent: activityPercent }) });
58
+ }
59
+ else {
60
+ insights.push({ kind: 'activity-share', text: (0, localization_1.uiText)(locale, 'insightActivityGeneric', { percent: activityPercent }) });
61
+ }
62
+ const longest = longestActiveSeconds(monitor.timeline, monitor.sample_interval_ms * 3);
63
+ if (longest >= 10)
64
+ insights.push({ kind: 'longest-block', text: (0, localization_1.uiText)(locale, 'insightLongest', { duration: durationLabel(longest, locale) }) });
65
+ if (activeProducts.length > 1) {
66
+ insights.push({ kind: 'workflow', text: (0, localization_1.uiText)(locale, 'insightMulti', { count: activeProducts.length }) });
67
+ }
68
+ else if (footprint.model_record_count > 0 && footprint.confirmed_running_model_count === 0) {
69
+ insights.push({ kind: 'local-models-unused', text: (0, localization_1.uiText)(locale, 'insightModelsUnused', { count: footprint.model_record_count }) });
70
+ }
71
+ else if (activeProducts.length === 1 && insights[0]?.kind !== 'activity-share') {
72
+ insights.push({ kind: 'workflow', text: (0, localization_1.uiText)(locale, 'insightFocused', { product: activeProducts[0].name }) });
73
+ }
74
+ return insights.slice(0, 3);
75
+ }
76
+ //# sourceMappingURL=insights.js.map
@@ -0,0 +1,19 @@
1
+ export interface LocalDeviceJournal {
2
+ schema_version: '1.0';
3
+ device_label: string;
4
+ device_label_source: 'macos-computer-name' | 'local-hostname' | 'generic-mac';
5
+ first_scan_at: string;
6
+ last_scan_at: string;
7
+ scan_count: number;
8
+ }
9
+ interface RecordLocalScanOptions {
10
+ stateDirectory?: string;
11
+ deviceLabel?: string;
12
+ now?: () => Date;
13
+ platform?: NodeJS.Platform;
14
+ }
15
+ export declare function defaultLocalStateDirectory(): string;
16
+ export declare function detectLocalDeviceLabel(platform?: NodeJS.Platform): Pick<LocalDeviceJournal, 'device_label' | 'device_label_source'>;
17
+ export declare function recordLocalScan(options?: RecordLocalScanOptions): Promise<LocalDeviceJournal>;
18
+ export {};
19
+ //# sourceMappingURL=local-device.d.ts.map