snow-flow 10.0.67 → 10.0.68
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/package.json +1 -1
- package/src/cli/cmd/tui/app.tsx +21 -0
- package/src/config/config.ts +4 -0
- package/src/flag/flag.ts +1 -0
- package/src/session/prompt.ts +2 -1
- package/src/usage/anonymous-telemetry.ts +118 -0
- package/src/usage/index.ts +1 -0
package/package.json
CHANGED
package/src/cli/cmd/tui/app.tsx
CHANGED
|
@@ -38,6 +38,7 @@ import { ArgsProvider, useArgs, type Args } from "./context/args"
|
|
|
38
38
|
import open from "open"
|
|
39
39
|
import { writeHeapSnapshot } from "v8"
|
|
40
40
|
import { PromptRefProvider, usePromptRef } from "./context/prompt"
|
|
41
|
+
import { Config } from "@/config/config"
|
|
41
42
|
|
|
42
43
|
async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
|
|
43
44
|
// can't set raw mode if not a TTY
|
|
@@ -634,6 +635,26 @@ function App() {
|
|
|
634
635
|
dialog.clear()
|
|
635
636
|
},
|
|
636
637
|
},
|
|
638
|
+
{
|
|
639
|
+
title: kv.get("telemetry_enabled", true) ? "Disable anonymous telemetry" : "Enable anonymous telemetry",
|
|
640
|
+
value: "app.toggle.telemetry",
|
|
641
|
+
category: "System",
|
|
642
|
+
slash: {
|
|
643
|
+
name: "telemetry",
|
|
644
|
+
},
|
|
645
|
+
onSelect: async (dialog) => {
|
|
646
|
+
const enabled = kv.get("telemetry_enabled", true)
|
|
647
|
+
kv.set("telemetry_enabled", !enabled)
|
|
648
|
+
await Config.updateGlobal({ telemetry: !enabled }).catch(() => {})
|
|
649
|
+
toast.show({
|
|
650
|
+
message: enabled
|
|
651
|
+
? "Telemetry disabled. Takes effect next session."
|
|
652
|
+
: "Telemetry enabled. Thank you!",
|
|
653
|
+
variant: "info",
|
|
654
|
+
})
|
|
655
|
+
dialog.clear()
|
|
656
|
+
},
|
|
657
|
+
},
|
|
637
658
|
])
|
|
638
659
|
|
|
639
660
|
createEffect(() => {
|
package/src/config/config.ts
CHANGED
|
@@ -1301,6 +1301,10 @@ export namespace Config {
|
|
|
1301
1301
|
url: z.string().optional().describe("Enterprise URL"),
|
|
1302
1302
|
})
|
|
1303
1303
|
.optional(),
|
|
1304
|
+
telemetry: z
|
|
1305
|
+
.boolean()
|
|
1306
|
+
.optional()
|
|
1307
|
+
.describe("Enable anonymous usage telemetry (default: true). Set to false to disable."),
|
|
1304
1308
|
compaction: z
|
|
1305
1309
|
.object({
|
|
1306
1310
|
auto: z.boolean().optional().describe("Enable automatic compaction when context is full (default: true)"),
|
package/src/flag/flag.ts
CHANGED
|
@@ -27,6 +27,7 @@ export namespace Flag {
|
|
|
27
27
|
export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthyBoth("DISABLE_DEFAULT_PLUGINS")
|
|
28
28
|
export const OPENCODE_DISABLE_LSP_DOWNLOAD = truthyBoth("DISABLE_LSP_DOWNLOAD")
|
|
29
29
|
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthyBoth("ENABLE_EXPERIMENTAL_MODELS")
|
|
30
|
+
export const OPENCODE_TELEMETRY_DISABLED = truthyBoth("TELEMETRY_DISABLED")
|
|
30
31
|
export const OPENCODE_DISABLE_AUTOCOMPACT = truthyBoth("DISABLE_AUTOCOMPACT")
|
|
31
32
|
export const OPENCODE_DISABLE_MODELS_FETCH = truthyBoth("DISABLE_MODELS_FETCH")
|
|
32
33
|
export const OPENCODE_DISABLE_CLAUDE_CODE = truthyBoth("DISABLE_CLAUDE_CODE")
|
package/src/session/prompt.ts
CHANGED
|
@@ -45,7 +45,7 @@ import { LLM } from "./llm"
|
|
|
45
45
|
import { iife } from "@/util/iife"
|
|
46
46
|
import { Shell } from "@/shell/shell"
|
|
47
47
|
import { Truncate } from "@/tool/truncation"
|
|
48
|
-
import { UsageReporter, ActivityReporter } from "@/usage"
|
|
48
|
+
import { UsageReporter, ActivityReporter, AnonymousTelemetry } from "@/usage"
|
|
49
49
|
|
|
50
50
|
// @ts-ignore
|
|
51
51
|
globalThis.AI_SDK_LOG_WARNINGS = false
|
|
@@ -156,6 +156,7 @@ export namespace SessionPrompt {
|
|
|
156
156
|
// Initialize TUI usage reporting (lazy, no-op if no enterprise auth)
|
|
157
157
|
UsageReporter.init()
|
|
158
158
|
ActivityReporter.init()
|
|
159
|
+
AnonymousTelemetry.init()
|
|
159
160
|
|
|
160
161
|
const message = await createUserMessage(input)
|
|
161
162
|
await Session.touch(input.sessionID)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Instance } from "@/project/instance"
|
|
2
|
+
import { Bus } from "@/bus"
|
|
3
|
+
import { MessageV2 } from "@/session/message-v2"
|
|
4
|
+
import { Log } from "@/util/log"
|
|
5
|
+
import { Installation } from "@/installation"
|
|
6
|
+
import { Flag } from "@/flag/flag"
|
|
7
|
+
import { Config } from "@/config/config"
|
|
8
|
+
import { machineIdSync } from "node-machine-id"
|
|
9
|
+
|
|
10
|
+
const log = Log.create({ service: "usage.anonymous-telemetry" })
|
|
11
|
+
|
|
12
|
+
const PORTAL_URL = process.env.SNOW_FLOW_PORTAL_URL || "https://portal.snow-flow.dev"
|
|
13
|
+
|
|
14
|
+
function isDisabled(): boolean {
|
|
15
|
+
// 1. Standard DO_NOT_TRACK convention (consoledonottrack.com)
|
|
16
|
+
const dnt = process.env.DO_NOT_TRACK?.toLowerCase()
|
|
17
|
+
if (dnt === "1" || dnt === "true") return true
|
|
18
|
+
|
|
19
|
+
// 2. Snow-Flow specific env vars
|
|
20
|
+
if (Flag.OPENCODE_TELEMETRY_DISABLED) return true
|
|
21
|
+
|
|
22
|
+
// 3. CI environments
|
|
23
|
+
const ci = process.env.CI?.toLowerCase()
|
|
24
|
+
if (ci === "true" || ci === "1") return true
|
|
25
|
+
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getMachineId(): string | undefined {
|
|
30
|
+
try {
|
|
31
|
+
return machineIdSync()
|
|
32
|
+
} catch {
|
|
33
|
+
return undefined
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export namespace AnonymousTelemetry {
|
|
38
|
+
const state = Instance.state(
|
|
39
|
+
() => {
|
|
40
|
+
// Check env-based opt-out first (sync, before any subscriptions)
|
|
41
|
+
if (isDisabled()) {
|
|
42
|
+
log.info("anonymous telemetry disabled (env)")
|
|
43
|
+
return { disabled: true as const, unsubs: [] as (() => void)[], startTime: 0, messageCount: 0 }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const machineId = getMachineId()
|
|
47
|
+
if (!machineId) {
|
|
48
|
+
log.info("anonymous telemetry disabled (no machine id)")
|
|
49
|
+
return { disabled: true as const, unsubs: [] as (() => void)[], startTime: 0, messageCount: 0 }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let messageCount = 0
|
|
53
|
+
const startTime = Date.now()
|
|
54
|
+
|
|
55
|
+
const unsubs = [
|
|
56
|
+
Bus.subscribe(MessageV2.Event.Updated, (event) => {
|
|
57
|
+
if (event.properties.info.role === "user") {
|
|
58
|
+
messageCount++
|
|
59
|
+
}
|
|
60
|
+
}),
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
// Check config-based opt-out (async)
|
|
64
|
+
Config.get().then((config) => {
|
|
65
|
+
if (config.telemetry === false) {
|
|
66
|
+
log.info("anonymous telemetry disabled (config)")
|
|
67
|
+
for (const unsub of unsubs) unsub()
|
|
68
|
+
unsubs.length = 0
|
|
69
|
+
} else {
|
|
70
|
+
log.info("anonymous telemetry active")
|
|
71
|
+
}
|
|
72
|
+
}).catch(() => {
|
|
73
|
+
// Config not available yet — keep telemetry active
|
|
74
|
+
log.info("anonymous telemetry active (config unavailable)")
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
return { disabled: false as const, unsubs, startTime, machineId, get messageCount() { return messageCount } }
|
|
78
|
+
},
|
|
79
|
+
async (current) => {
|
|
80
|
+
for (const unsub of current.unsubs) unsub()
|
|
81
|
+
|
|
82
|
+
if (current.disabled) return
|
|
83
|
+
|
|
84
|
+
const sessionDurationSec = Math.round((Date.now() - current.startTime) / 1000)
|
|
85
|
+
|
|
86
|
+
// Check config opt-out before sending
|
|
87
|
+
const config = await Config.get().catch(() => undefined)
|
|
88
|
+
if (config?.telemetry === false) return
|
|
89
|
+
|
|
90
|
+
const payload = {
|
|
91
|
+
machineId: current.machineId,
|
|
92
|
+
version: Installation.VERSION,
|
|
93
|
+
channel: Installation.CHANNEL,
|
|
94
|
+
os: process.platform,
|
|
95
|
+
arch: process.arch,
|
|
96
|
+
sessionDurationSec,
|
|
97
|
+
messageCount: current.messageCount,
|
|
98
|
+
timestamp: Date.now(),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await fetch(`${PORTAL_URL}/api/telemetry/ping`, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: { "Content-Type": "application/json" },
|
|
105
|
+
body: JSON.stringify(payload),
|
|
106
|
+
signal: AbortSignal.timeout(5_000),
|
|
107
|
+
})
|
|
108
|
+
} catch {
|
|
109
|
+
// Fire-and-forget: network errors expected when offline
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
/** Initialize the anonymous telemetry reporter. Call once per Instance lifecycle. */
|
|
115
|
+
export function init() {
|
|
116
|
+
state()
|
|
117
|
+
}
|
|
118
|
+
}
|
package/src/usage/index.ts
CHANGED