nanoai-cli 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/bin/nanoai.js +4 -1
- package/package.json +1 -1
- package/scripts/download.js +14 -1
- package/scripts/postinstall.js +5 -1
- package/scripts/telemetry.js +115 -0
package/README.md
CHANGED
|
@@ -86,6 +86,7 @@ NANOAGENT_SKIP_UPDATE_CHECK=1 nanoai
|
|
|
86
86
|
| Variable | Purpose |
|
|
87
87
|
| --- | --- |
|
|
88
88
|
| `NANOAGENT_SKIP_DOWNLOAD` | Set to `1` to skip the install-time download. The binary will still be fetched on first run. |
|
|
89
|
+
| `NANOAGENT_TELEMETRY_DISABLED` | Set to `1` to opt out of the anonymous `nanoagent cli installed` analytics event. `DO_NOT_TRACK=1` is also honored. |
|
|
89
90
|
| `NANOAGENT_SKIP_UPDATE_CHECK` | Set to `1` to disable the runtime check for newer GitHub releases. |
|
|
90
91
|
| `NANOAGENT_CLI_TAG` | Override the release tag to download, such as `v1.2.3`. |
|
|
91
92
|
| `NANOAGENT_CLI_VERSION` | Override the version used to derive the default release tag. |
|
package/bin/nanoai.js
CHANGED
|
@@ -5,6 +5,7 @@ const { spawn } = require("child_process");
|
|
|
5
5
|
|
|
6
6
|
const { ensureBinary } = require("../scripts/download");
|
|
7
7
|
const { maybeUpdateBinary } = require("../scripts/update");
|
|
8
|
+
const { trackInstall } = require("../scripts/telemetry");
|
|
8
9
|
|
|
9
10
|
function log(message) {
|
|
10
11
|
console.error(`[nanoagent] ${message}`);
|
|
@@ -23,7 +24,9 @@ function forwardSignal(child, signal) {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
async function main() {
|
|
26
|
-
|
|
27
|
+
// On a clean `bun add` (postinstall is skipped) the binary is fetched here on
|
|
28
|
+
// first launch; record the install once at that point.
|
|
29
|
+
let binaryPath = await ensureBinary({ log, onDownloaded: () => trackInstall() });
|
|
27
30
|
binaryPath = await maybeUpdateBinary(binaryPath, { log });
|
|
28
31
|
|
|
29
32
|
const child = spawn(binaryPath, process.argv.slice(2), {
|
package/package.json
CHANGED
package/scripts/download.js
CHANGED
|
@@ -76,8 +76,10 @@ function extractExecutable(zipBuffer, destinationPath) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// Ensures the platform binary is present in vendor/. Returns the absolute path.
|
|
79
|
+
// `onDownloaded` is awaited only when a fresh (non-update) binary is fetched, so
|
|
80
|
+
// callers can record an anonymous install event exactly once per real install.
|
|
79
81
|
async function ensureBinary(options = {}) {
|
|
80
|
-
const { force = false, log = () => {}, tag } = options;
|
|
82
|
+
const { force = false, log = () => {}, tag, onDownloaded } = options;
|
|
81
83
|
|
|
82
84
|
const binaryPath = platform.installedBinaryPath();
|
|
83
85
|
if (!force && fs.existsSync(binaryPath)) {
|
|
@@ -121,6 +123,17 @@ async function ensureBinary(options = {}) {
|
|
|
121
123
|
fs.renameSync(tempPath, binaryPath);
|
|
122
124
|
|
|
123
125
|
log(`Installed NanoAgent CLI to ${binaryPath}.`);
|
|
126
|
+
|
|
127
|
+
// Fire once per genuine install. Updates pass force=true and are intentionally
|
|
128
|
+
// excluded so they are not counted as new installs.
|
|
129
|
+
if (!force && typeof onDownloaded === "function") {
|
|
130
|
+
try {
|
|
131
|
+
await onDownloaded();
|
|
132
|
+
} catch {
|
|
133
|
+
// Telemetry must never affect installation.
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
124
137
|
return binaryPath;
|
|
125
138
|
}
|
|
126
139
|
|
package/scripts/postinstall.js
CHANGED
|
@@ -12,8 +12,12 @@ if (process.env.NANOAGENT_SKIP_DOWNLOAD === "1") {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const { ensureBinary } = require("./download");
|
|
15
|
+
const { trackInstall } = require("./telemetry");
|
|
15
16
|
|
|
16
|
-
ensureBinary({
|
|
17
|
+
ensureBinary({
|
|
18
|
+
log: (m) => console.error(`[nanoagent] ${m}`),
|
|
19
|
+
onDownloaded: () => trackInstall(),
|
|
20
|
+
})
|
|
17
21
|
.then(() => {
|
|
18
22
|
console.error("[nanoagent] Ready. Run `nanoai` to start.");
|
|
19
23
|
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Best-effort, anonymous PostHog "installed" event shared by the postinstall step
|
|
4
|
+
// and the runtime first-run download (covers `bun add`, which skips postinstall).
|
|
5
|
+
// Telemetry must never affect installation: every failure is swallowed and the
|
|
6
|
+
// network call is bounded by a short timeout. Opt out with NANOAGENT_TELEMETRY_DISABLED
|
|
7
|
+
// or the cross-tool DO_NOT_TRACK convention.
|
|
8
|
+
|
|
9
|
+
const crypto = require("crypto");
|
|
10
|
+
|
|
11
|
+
const platform = require("./platform");
|
|
12
|
+
|
|
13
|
+
// Mirrors NanoAgent.Infrastructure.Configuration.TelemetryOptions so install and
|
|
14
|
+
// in-product analytics land in the same PostHog project.
|
|
15
|
+
const TELEMETRY_HOST = "https://us.i.posthog.com";
|
|
16
|
+
const TELEMETRY_PROJECT_TOKEN = "phc_AKZFSyU239kkQ5GQ2y4idb8MtFX96kVekgezgnsELHRk";
|
|
17
|
+
const TELEMETRY_EVENT = "nanoagent cli installed";
|
|
18
|
+
const TIMEOUT_MS = 5000;
|
|
19
|
+
|
|
20
|
+
function isTruthy(value) {
|
|
21
|
+
if (!value) return false;
|
|
22
|
+
return ["1", "true", "yes", "on"].includes(String(value).trim().toLowerCase());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function telemetryEnabled() {
|
|
26
|
+
if (isTruthy(process.env.NANOAGENT_TELEMETRY_DISABLED) || isTruthy(process.env.DO_NOT_TRACK)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return Boolean(TELEMETRY_PROJECT_TOKEN);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Identify the package manager that triggered the install. npm/pnpm/yarn set
|
|
33
|
+
// npm_config_user_agent during their lifecycle; bun is detected from the runtime
|
|
34
|
+
// when the binary is fetched lazily on first launch.
|
|
35
|
+
function detectInstallMethod() {
|
|
36
|
+
const ua = (process.env.npm_config_user_agent || "").toLowerCase();
|
|
37
|
+
if (ua.includes("bun")) return "bun";
|
|
38
|
+
if (ua.includes("pnpm")) return "pnpm";
|
|
39
|
+
if (ua.includes("yarn")) return "yarn";
|
|
40
|
+
if (ua.includes("npm")) return "npm";
|
|
41
|
+
if (process.versions && process.versions.bun) return "bun";
|
|
42
|
+
return "npm";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function osFamily() {
|
|
46
|
+
switch (process.platform) {
|
|
47
|
+
case "win32":
|
|
48
|
+
return "windows";
|
|
49
|
+
case "darwin":
|
|
50
|
+
return "macos";
|
|
51
|
+
case "linux":
|
|
52
|
+
return "linux";
|
|
53
|
+
default:
|
|
54
|
+
return "other";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isCi() {
|
|
59
|
+
return Boolean(
|
|
60
|
+
process.env.CI ||
|
|
61
|
+
process.env.GITHUB_ACTIONS ||
|
|
62
|
+
process.env.GITLAB_CI ||
|
|
63
|
+
process.env.BITBUCKET_BUILD_NUMBER
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveQuietly(resolver, fallback) {
|
|
68
|
+
try {
|
|
69
|
+
const value = resolver();
|
|
70
|
+
return value && String(value).trim() ? value : fallback;
|
|
71
|
+
} catch {
|
|
72
|
+
return fallback;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function trackInstall(options = {}) {
|
|
77
|
+
try {
|
|
78
|
+
if (!telemetryEnabled() || typeof fetch !== "function") {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const ci = isCi();
|
|
83
|
+
const payload = {
|
|
84
|
+
api_key: TELEMETRY_PROJECT_TOKEN,
|
|
85
|
+
event: TELEMETRY_EVENT,
|
|
86
|
+
distinct_id: crypto.randomUUID(),
|
|
87
|
+
properties: {
|
|
88
|
+
$lib: "nanoagent-installer",
|
|
89
|
+
install_method: options.method || detectInstallMethod(),
|
|
90
|
+
nanoagent_version: resolveQuietly(() => platform.resolveTag(), "unknown"),
|
|
91
|
+
os_family: osFamily(),
|
|
92
|
+
platform: resolveQuietly(() => platform.resolveRid(), "unknown"),
|
|
93
|
+
app_surface: "cli",
|
|
94
|
+
execution_environment: ci ? "ci" : "local",
|
|
95
|
+
is_ci: ci,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const signal =
|
|
100
|
+
typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function"
|
|
101
|
+
? AbortSignal.timeout(TIMEOUT_MS)
|
|
102
|
+
: undefined;
|
|
103
|
+
|
|
104
|
+
await fetch(`${TELEMETRY_HOST}/i/v0/e/`, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: { "Content-Type": "application/json" },
|
|
107
|
+
body: JSON.stringify(payload),
|
|
108
|
+
signal,
|
|
109
|
+
});
|
|
110
|
+
} catch {
|
|
111
|
+
// Telemetry must never affect installation.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = { trackInstall, detectInstallMethod };
|