idlewatch 0.1.5 → 0.1.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/README.md CHANGED
@@ -57,6 +57,8 @@ Use `gpuSource` + `gpuConfidence` in dashboards to decide whether to trust value
57
57
  npx idlewatch quickstart
58
58
  ```
59
59
 
60
+ `idlewatch` is the primary package/command name. `idlewatch-skill` still works as a compatibility alias, but treat it as legacy in user-facing docs.
61
+
60
62
  The wizard keeps setup small:
61
63
  - asks for a **device name**
62
64
  - asks for your **API key** from `idlewatch.com/api`
@@ -374,6 +374,10 @@ if (quickstartRequested) {
374
374
  try {
375
375
  const result = await runEnrollmentWizard({ noTui: args.has('--no-tui') })
376
376
 
377
+ if (!result?.outputEnvFile || !fs.existsSync(result.outputEnvFile)) {
378
+ throw new Error(`setup_did_not_write_env_file:${result?.outputEnvFile || 'unknown'}`)
379
+ }
380
+
377
381
  const enrolledEnv = parseEnvFileToObject(result.outputEnvFile)
378
382
  const onceRun = spawnSync(process.execPath, [process.argv[1], '--once'], {
379
383
  stdio: 'inherit',
@@ -395,7 +399,13 @@ if (quickstartRequested) {
395
399
  console.error('Or rerun: idlewatch quickstart')
396
400
  process.exit(onceRun.status ?? 1)
397
401
  } catch (err) {
398
- console.error(`Enrollment failed: ${err.message}`)
402
+ if (String(err?.message || '') === 'setup_cancelled') {
403
+ console.error('Enrollment cancelled before saving config.')
404
+ } else if (String(err?.message || '').startsWith('setup_did_not_write_env_file:')) {
405
+ console.error(`Enrollment failed: setup did not save idlewatch.env (${String(err.message).split(':').slice(1).join(':')}).`)
406
+ } else {
407
+ console.error(`Enrollment failed: ${err.message}`)
408
+ }
399
409
  process.exit(1)
400
410
  }
401
411
  }
@@ -624,9 +634,13 @@ if (firebaseConfigError) {
624
634
  process.exit(1)
625
635
  }
626
636
 
627
- if (!appReady && !(CLOUD_INGEST_URL && CLOUD_API_KEY)) {
637
+ const hasAnyFirebaseConfig = Boolean(PROJECT_ID || CREDS_FILE || CREDS_JSON || CREDS_B64 || FIRESTORE_EMULATOR_HOST)
638
+ const hasCloudConfig = Boolean(CLOUD_INGEST_URL && CLOUD_API_KEY)
639
+ const shouldWarnAboutMissingPublishConfig = !appReady && !hasCloudConfig && !DRY_RUN && !hasAnyFirebaseConfig
640
+
641
+ if (shouldWarnAboutMissingPublishConfig) {
628
642
  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 run idlewatch quickstart to link cloud ingest.'
643
+ 'No publish target is configured yet. Running in local-only mode. Run idlewatch quickstart to link cloud ingest, or configure Firebase/emulator mode if you need that path.'
630
644
  )
631
645
  }
632
646
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "idlewatch",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Host telemetry collector for IdleWatch",
5
5
  "type": "module",
6
6
  "files": [
package/src/enrollment.js CHANGED
@@ -10,6 +10,24 @@ function defaultConfigDir() {
10
10
  return path.join(os.homedir(), '.idlewatch')
11
11
  }
12
12
 
13
+ function machineName() {
14
+ if (process.platform === 'darwin') {
15
+ const macName = spawnSync('scutil', ['--get', 'ComputerName'], { encoding: 'utf8' })
16
+ if (macName.status === 0) {
17
+ const value = String(macName.stdout || '').trim()
18
+ if (value) return value
19
+ }
20
+ }
21
+
22
+ const hostName = String(os.hostname() || '').trim()
23
+ if (hostName) return hostName
24
+
25
+ const envHost = String(process.env.HOSTNAME || '').trim()
26
+ if (envHost) return envHost
27
+
28
+ return 'IdleWatch Device'
29
+ }
30
+
13
31
  function ensureDir(dirPath) {
14
32
  fs.mkdirSync(dirPath, { recursive: true })
15
33
  }
@@ -80,12 +98,12 @@ function looksLikeCloudApiKey(value) {
80
98
  return /^iwk_[A-Za-z0-9_-]{20,}$/.test(String(value || '').trim())
81
99
  }
82
100
 
83
- function normalizeDeviceName(raw, fallback = os.hostname()) {
101
+ function normalizeDeviceName(raw, fallback = machineName()) {
84
102
  const value = String(raw || '').trim().replace(/\s+/g, ' ')
85
103
  return value || fallback
86
104
  }
87
105
 
88
- function sanitizeDeviceId(raw, fallback = os.hostname()) {
106
+ function sanitizeDeviceId(raw, fallback = machineName()) {
89
107
  const base = normalizeDeviceName(raw, fallback).toLowerCase()
90
108
  const sanitized = base
91
109
  .replace(/[^a-z0-9._-]+/g, '-')
@@ -174,7 +192,7 @@ export async function runEnrollmentWizard(options = {}) {
174
192
  let mode = options.mode || process.env.IDLEWATCH_ENROLL_MODE || null
175
193
  let cloudApiKey = normalizeCloudApiKey(options.cloudApiKey || process.env.IDLEWATCH_CLOUD_API_KEY || null)
176
194
  let cloudIngestUrl = options.cloudIngestUrl || process.env.IDLEWATCH_CLOUD_INGEST_URL || 'https://api.idlewatch.com/api/ingest'
177
- let deviceName = normalizeDeviceName(options.deviceName || process.env.IDLEWATCH_DEVICE_NAME || os.hostname())
195
+ let deviceName = normalizeDeviceName(options.deviceName || process.env.IDLEWATCH_DEVICE_NAME || machineName())
178
196
 
179
197
  const availableMonitorTargets = detectAvailableMonitorTargets()
180
198
  let monitorTargets = normalizeMonitorTargets(
@@ -229,7 +247,7 @@ export async function runEnrollmentWizard(options = {}) {
229
247
  monitorTargets = normalizeMonitorTargets(monitorInput || suggested, availableMonitorTargets)
230
248
  }
231
249
 
232
- const safeDeviceId = sanitizeDeviceId(options.deviceId || process.env.IDLEWATCH_DEVICE_ID || deviceName, os.hostname())
250
+ const safeDeviceId = sanitizeDeviceId(options.deviceId || process.env.IDLEWATCH_DEVICE_ID || deviceName, machineName())
233
251
  const localLogPath = path.join(configDir, 'logs', `${safeDeviceId}-metrics.ndjson`)
234
252
  const localCachePath = path.join(configDir, 'cache', `${safeDeviceId}-openclaw-last-good.json`)
235
253
 
package/tui/src/main.rs CHANGED
@@ -70,6 +70,34 @@ fn command_exists(cmd: &str, args: &[&str]) -> bool {
70
70
  .unwrap_or(false)
71
71
  }
72
72
 
73
+ fn machine_name() -> String {
74
+ if cfg!(target_os = "macos") {
75
+ if let Ok(output) = Command::new("scutil").args(["--get", "ComputerName"]).output() {
76
+ if output.status.success() {
77
+ let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
78
+ if !name.is_empty() {
79
+ return name;
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ if let Ok(output) = Command::new("hostname").output() {
86
+ if output.status.success() {
87
+ let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
88
+ if !name.is_empty() {
89
+ return name;
90
+ }
91
+ }
92
+ }
93
+
94
+ std::env::var("HOSTNAME")
95
+ .ok()
96
+ .map(|value| value.trim().to_string())
97
+ .filter(|value| !value.is_empty())
98
+ .unwrap_or_else(|| "IdleWatch Device".to_string())
99
+ }
100
+
73
101
  fn parse_env_file(path: &Path) -> ExistingConfig {
74
102
  let mut config = ExistingConfig::default();
75
103
  let Ok(raw) = fs::read_to_string(path) else {
@@ -443,7 +471,7 @@ fn main() -> Result<()> {
443
471
  let env_file = std::env::var("IDLEWATCH_ENROLL_OUTPUT_ENV_FILE")
444
472
  .map(PathBuf::from)
445
473
  .unwrap_or_else(|_| config_dir.join("idlewatch.env"));
446
- let host = sanitize_host(&std::env::var("HOSTNAME").unwrap_or_else(|_| "host".to_string()));
474
+ let host = sanitize_host(&machine_name());
447
475
  let existing = parse_env_file(&env_file);
448
476
 
449
477
  enable_raw_mode()?;
@@ -464,7 +492,7 @@ fn main() -> Result<()> {
464
492
  KeyCode::Char('q') | KeyCode::Esc => {
465
493
  disable_raw_mode()?;
466
494
  execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
467
- return Ok(());
495
+ return Err(anyhow!("setup_cancelled"));
468
496
  }
469
497
  _ => {}
470
498
  }
@@ -503,7 +531,7 @@ fn main() -> Result<()> {
503
531
  KeyCode::Char('q') | KeyCode::Esc => {
504
532
  disable_raw_mode()?;
505
533
  execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
506
- return Ok(());
534
+ return Err(anyhow!("setup_cancelled"));
507
535
  }
508
536
  _ => {}
509
537
  }
@@ -545,7 +573,7 @@ fn main() -> Result<()> {
545
573
  KeyCode::Char('q') | KeyCode::Esc => {
546
574
  disable_raw_mode()?;
547
575
  execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
548
- return Ok(());
576
+ return Err(anyhow!("setup_cancelled"));
549
577
  }
550
578
  KeyCode::Char(c) => {
551
579
  device_name_input.push(c);
@@ -592,7 +620,7 @@ fn main() -> Result<()> {
592
620
  KeyCode::Char('q') | KeyCode::Esc => {
593
621
  disable_raw_mode()?;
594
622
  execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
595
- return Ok(());
623
+ return Err(anyhow!("setup_cancelled"));
596
624
  }
597
625
  KeyCode::Char(c) => {
598
626
  cloud_api_key_input.push(c);