idlewatch 0.1.3 → 0.1.5
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 +13 -9
- package/bin/idlewatch-agent.js +3 -3
- package/package.json +2 -1
- package/src/enrollment.js +48 -4
- package/tui/bin/darwin-arm64/idlewatch-setup +0 -0
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ npx idlewatch quickstart
|
|
|
17
17
|
npx idlewatch --dry-run
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
`quickstart`
|
|
20
|
+
`quickstart` is the happy path: create an API key on idlewatch.com/api, run the wizard, pick a device name + metrics, and IdleWatch saves local config before sending a first sample. It prefers a bundled TUI binary when available, otherwise falls back to a local Cargo build only on developer machines that already have Cargo installed. Use `idlewatch quickstart --no-tui` to skip the TUI and stay on the plain text setup path.
|
|
21
21
|
|
|
22
22
|
## CLI options
|
|
23
23
|
|
|
@@ -49,26 +49,30 @@ Use `gpuSource` + `gpuConfidence` in dashboards to decide whether to trust value
|
|
|
49
49
|
- `low`: constrained probe path
|
|
50
50
|
- `none`: no usable sample for that sample window
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## Quickstart
|
|
53
53
|
|
|
54
|
-
### Recommended: guided enrollment
|
|
54
|
+
### Recommended: guided enrollment
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
57
|
npx idlewatch quickstart
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
The wizard
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
60
|
+
The wizard keeps setup small:
|
|
61
|
+
- asks for a **device name**
|
|
62
|
+
- asks for your **API key** from `idlewatch.com/api`
|
|
63
|
+
- lets you choose which **metrics** to collect
|
|
64
|
+
- saves local config to `~/.idlewatch/idlewatch.env`
|
|
65
|
+
- sends a first sample so the device can link right away
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
The saved config is auto-loaded on later runs, so you should not need to manually source the env file in normal use.
|
|
68
|
+
|
|
69
|
+
Then run a one-shot publish check any time with:
|
|
66
70
|
|
|
67
71
|
```bash
|
|
68
72
|
idlewatch --once
|
|
69
73
|
```
|
|
70
74
|
|
|
71
|
-
|
|
75
|
+
## Advanced Firebase wiring
|
|
72
76
|
|
|
73
77
|
### Manual wiring
|
|
74
78
|
|
package/bin/idlewatch-agent.js
CHANGED
|
@@ -18,7 +18,7 @@ import { enrichWithOpenClawFleetTelemetry } from '../src/telemetry-mapping.js'
|
|
|
18
18
|
import pkg from '../package.json' with { type: 'json' }
|
|
19
19
|
|
|
20
20
|
function printHelp() {
|
|
21
|
-
console.log(`idlewatch\n\nUsage:\n idlewatch [quickstart|configure|dashboard|run] [--dry-run] [--once] [--help]\n\nOptions:\n quickstart Run first-run setup
|
|
21
|
+
console.log(`idlewatch\n\nUsage:\n idlewatch [quickstart|configure|dashboard|run] [--dry-run] [--once] [--help]\n\nOptions:\n quickstart Run first-run setup and save local IdleWatch config\n configure Alias for quickstart; reopen setup to change device name, API key, or metrics\n dashboard Launch local dashboard from local IdleWatch logs\n run Start the background collector using saved local config\n --no-tui Skip the Rust TUI and use plain text setup without installing Cargo\n --dry-run Collect and print one telemetry sample, then exit without remote writes\n --once Collect and publish one telemetry sample, then exit\n --help Show this help message\n\nQuickstart:\n 1. Create an API key on idlewatch.com/api\n 2. Run: idlewatch quickstart\n 3. Pick a device name and metrics\n 4. IdleWatch saves your local config and sends a first sample\n\nEnvironment:\n IDLEWATCH_HOST Optional custom host label (default: hostname)\n IDLEWATCH_INTERVAL_MS Sampling interval in ms (default: 10000)\n IDLEWATCH_LOCAL_LOG_PATH Optional NDJSON file path for local sample durability\n IDLEWATCH_DASHBOARD_PORT Local dashboard HTTP port (default: 4373)\n IDLEWATCH_OPENCLAW_USAGE OpenClaw usage lookup mode: auto|off (default: auto)\n IDLEWATCH_OPENCLAW_PROBE_TIMEOUT_MS OpenClaw command timeout per probe in ms (default: 2500)\n IDLEWATCH_OPENCLAW_PROBE_RETRIES Extra OpenClaw probe sweep retries after first pass (default: 1)\n IDLEWATCH_OPENCLAW_MAX_OUTPUT_BYTES Max per-command OpenClaw probe output capture in bytes before truncation (default: 2097152 / 2MB)\n IDLEWATCH_OPENCLAW_MAX_OUTPUT_BYTES_HARD_CAP Hard cap for auto-retry output capture escalation (default: 16777216 / 16MB)\n IDLEWATCH_USAGE_STALE_MS Mark OpenClaw usage stale beyond this age in ms (default: max(interval*3,60000))\n IDLEWATCH_USAGE_NEAR_STALE_MS Mark OpenClaw usage as aging beyond this age in ms (default: floor((stale+grace)*0.85))\n IDLEWATCH_USAGE_STALE_GRACE_MS Extra grace window before status becomes stale (default: min(interval,10000))\n IDLEWATCH_USAGE_REFRESH_REPROBES Forced uncached reprobes when usage crosses stale threshold (default: 1)\n IDLEWATCH_USAGE_REFRESH_DELAY_MS Delay between forced stale-threshold reprobes in ms (default: 250)\n IDLEWATCH_USAGE_REFRESH_ON_NEAR_STALE Trigger refresh when usage is near-stale: 1|0 (default: 1)\n IDLEWATCH_USAGE_IDLE_AFTER_MS Downgrade stale usage alerts to idle notice beyond this age in ms (default: 21600000)\n IDLEWATCH_OPENCLAW_LAST_GOOD_MAX_AGE_MS Reuse last successful usage snapshot after probe failures up to this age in ms\n IDLEWATCH_OPENCLAW_LAST_GOOD_CACHE_PATH Persist/reuse last successful usage snapshot across restarts (default: ~/.idlewatch/cache/<host>-openclaw-last-good.json)\n IDLEWATCH_CLOUD_INGEST_URL Optional cloud ingest endpoint (e.g. https://idlewatch.com/api/ingest)\n IDLEWATCH_CLOUD_API_KEY Cloud API key from idlewatch.com/api for device linking\n IDLEWATCH_REQUIRE_CLOUD_WRITES Require cloud publish path in --once mode: 1|0 (default: 0)\n\nAdvanced Firebase / emulator mode:\n IDLEWATCH_REQUIRE_FIREBASE_WRITES Require Firebase publish path in --once mode: 1|0 (default: 0)\n FIREBASE_PROJECT_ID Firebase project id\n FIREBASE_SERVICE_ACCOUNT_FILE Path to service account JSON file (preferred for production)\n FIREBASE_SERVICE_ACCOUNT_JSON Raw JSON service account (supported, less secure than file path)\n FIREBASE_SERVICE_ACCOUNT_B64 Base64-encoded JSON service account (legacy)\n FIRESTORE_EMULATOR_HOST Optional Firestore emulator host; allows local writes without service-account creds\n`)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const require = createRequire(import.meta.url)
|
|
@@ -372,7 +372,7 @@ if (dashboardRequested) {
|
|
|
372
372
|
|
|
373
373
|
if (quickstartRequested) {
|
|
374
374
|
try {
|
|
375
|
-
const result = await runEnrollmentWizard()
|
|
375
|
+
const result = await runEnrollmentWizard({ noTui: args.has('--no-tui') })
|
|
376
376
|
|
|
377
377
|
const enrolledEnv = parseEnvFileToObject(result.outputEnvFile)
|
|
378
378
|
const onceRun = spawnSync(process.execPath, [process.argv[1], '--once'], {
|
|
@@ -626,7 +626,7 @@ if (firebaseConfigError) {
|
|
|
626
626
|
|
|
627
627
|
if (!appReady && !(CLOUD_INGEST_URL && CLOUD_API_KEY)) {
|
|
628
628
|
console.error(
|
|
629
|
-
'Firebase is not configured. Running without Firebase writes. Set FIREBASE_PROJECT_ID + FIREBASE_SERVICE_ACCOUNT_FILE (preferred, or FIREBASE_SERVICE_ACCOUNT_JSON / FIREBASE_SERVICE_ACCOUNT_B64), use FIREBASE_PROJECT_ID + FIRESTORE_EMULATOR_HOST for emulator writes, or
|
|
629
|
+
'Firebase is not configured. Running without Firebase writes. Set FIREBASE_PROJECT_ID + FIREBASE_SERVICE_ACCOUNT_FILE (preferred, or FIREBASE_SERVICE_ACCOUNT_JSON / FIREBASE_SERVICE_ACCOUNT_B64), use FIREBASE_PROJECT_ID + FIRESTORE_EMULATOR_HOST for emulator writes, or run idlewatch quickstart to link cloud ingest.'
|
|
630
630
|
)
|
|
631
631
|
}
|
|
632
632
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "idlewatch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Host telemetry collector for IdleWatch",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"scripts",
|
|
10
10
|
"skill",
|
|
11
11
|
"tui/src",
|
|
12
|
+
"tui/bin",
|
|
12
13
|
"tui/Cargo.toml",
|
|
13
14
|
"tui/Cargo.lock",
|
|
14
15
|
"README.md",
|
package/src/enrollment.js
CHANGED
|
@@ -94,12 +94,53 @@ function sanitizeDeviceId(raw, fallback = os.hostname()) {
|
|
|
94
94
|
return sanitized || normalizeDeviceName(fallback).replace(/[^a-zA-Z0-9_.-]/g, '_')
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
function cargoAvailable() {
|
|
98
|
+
const cargoProbe = spawnSync('cargo', ['--version'], { stdio: 'ignore' })
|
|
99
|
+
return cargoProbe.status === 0
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function bundledTuiBinaryPath() {
|
|
103
|
+
const platform = process.platform
|
|
104
|
+
const arch = process.arch
|
|
105
|
+
const ext = platform === 'win32' ? '.exe' : ''
|
|
106
|
+
return path.join(PACKAGE_ROOT, 'tui', 'bin', `${platform}-${arch}`, `idlewatch-setup${ext}`)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function tryBundledRustTui({ configDir, outputEnvFile }) {
|
|
110
|
+
const binPath = bundledTuiBinaryPath()
|
|
111
|
+
if (!fs.existsSync(binPath)) return { ok: false, reason: 'bundled-binary-missing', binPath }
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
fs.chmodSync(binPath, 0o755)
|
|
115
|
+
} catch {
|
|
116
|
+
// best effort
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const run = spawnSync(binPath, [], {
|
|
120
|
+
stdio: 'inherit',
|
|
121
|
+
env: {
|
|
122
|
+
...process.env,
|
|
123
|
+
IDLEWATCH_ENROLL_CONFIG_DIR: configDir,
|
|
124
|
+
IDLEWATCH_ENROLL_OUTPUT_ENV_FILE: outputEnvFile
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
if (run.status === 0) return { ok: true, binPath }
|
|
129
|
+
return { ok: false, reason: `bundled-binary-failed:${run.status ?? 'unknown'}`, binPath }
|
|
130
|
+
}
|
|
131
|
+
|
|
97
132
|
function tryRustTui({ configDir, outputEnvFile }) {
|
|
98
133
|
const disabled = process.env.IDLEWATCH_DISABLE_RUST_TUI === '1'
|
|
99
134
|
if (disabled) return { ok: false, reason: 'disabled' }
|
|
100
135
|
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
136
|
+
const bundled = tryBundledRustTui({ configDir, outputEnvFile })
|
|
137
|
+
if (bundled.ok) return bundled
|
|
138
|
+
|
|
139
|
+
if (!cargoAvailable()) {
|
|
140
|
+
return bundled.reason === 'bundled-binary-missing'
|
|
141
|
+
? { ok: false, reason: 'bundled-binary-missing-and-cargo-missing', binPath: bundled.binPath }
|
|
142
|
+
: { ok: false, reason: 'cargo-missing' }
|
|
143
|
+
}
|
|
103
144
|
|
|
104
145
|
const manifestPath = path.join(PACKAGE_ROOT, 'tui', 'Cargo.toml')
|
|
105
146
|
if (!fs.existsSync(manifestPath)) return { ok: false, reason: 'manifest-missing', manifestPath }
|
|
@@ -126,6 +167,7 @@ function promptModeText() {
|
|
|
126
167
|
|
|
127
168
|
export async function runEnrollmentWizard(options = {}) {
|
|
128
169
|
const nonInteractive = options.nonInteractive || process.env.IDLEWATCH_ENROLL_NON_INTERACTIVE === '1'
|
|
170
|
+
const noTui = options.noTui || process.env.IDLEWATCH_NO_TUI === '1'
|
|
129
171
|
const configDir = path.resolve(options.configDir || process.env.IDLEWATCH_ENROLL_CONFIG_DIR || defaultConfigDir())
|
|
130
172
|
const outputEnvFile = path.resolve(options.outputEnvFile || process.env.IDLEWATCH_ENROLL_OUTPUT_ENV_FILE || path.join(configDir, 'idlewatch.env'))
|
|
131
173
|
|
|
@@ -140,7 +182,7 @@ export async function runEnrollmentWizard(options = {}) {
|
|
|
140
182
|
availableMonitorTargets
|
|
141
183
|
)
|
|
142
184
|
|
|
143
|
-
if (!nonInteractive) {
|
|
185
|
+
if (!nonInteractive && !noTui) {
|
|
144
186
|
const tuiResult = tryRustTui({ configDir, outputEnvFile })
|
|
145
187
|
if (tuiResult.ok) {
|
|
146
188
|
return {
|
|
@@ -150,7 +192,9 @@ export async function runEnrollmentWizard(options = {}) {
|
|
|
150
192
|
}
|
|
151
193
|
}
|
|
152
194
|
|
|
153
|
-
if (
|
|
195
|
+
if (tuiResult.reason === 'bundled-binary-missing-and-cargo-missing') {
|
|
196
|
+
console.warn('IdleWatch TUI is not bundled for this platform and Cargo is not installed. Falling back to text setup. Use --no-tui to skip this check.')
|
|
197
|
+
} else if (!['disabled', 'cargo-missing', 'bundled-binary-missing'].includes(tuiResult.reason || '')) {
|
|
154
198
|
console.warn(`IdleWatch TUI unavailable (${tuiResult.reason || 'unknown'}). Falling back to text setup.`)
|
|
155
199
|
}
|
|
156
200
|
}
|
|
Binary file
|