idlewatch 0.1.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.
- package/.env.example +73 -0
- package/.github/workflows/ci.yml +99 -0
- package/.github/workflows/release-macos-trusted.yml +103 -0
- package/README.md +336 -0
- package/bin/idlewatch-agent.js +1053 -0
- package/docs/onboarding-external.md +58 -0
- package/docs/packaging/macos-dmg.md +199 -0
- package/docs/packaging/macos-launch-agent.md +70 -0
- package/docs/qa/archive/mac-qa-log-2026-02-17.md +5838 -0
- package/docs/qa/mac-qa-log.md +2864 -0
- package/docs/telemetry/idle-stale-policy.md +57 -0
- package/docs/telemetry/openclaw-mapping.md +80 -0
- package/package.json +76 -0
- package/scripts/build-dmg.sh +65 -0
- package/scripts/install-macos-launch-agent.sh +78 -0
- package/scripts/lib/telemetry-row-parser.mjs +100 -0
- package/scripts/package-macos.sh +228 -0
- package/scripts/uninstall-macos-launch-agent.sh +30 -0
- package/scripts/validate-all.sh +142 -0
- package/scripts/validate-bin.mjs +25 -0
- package/scripts/validate-dmg-checksum.sh +37 -0
- package/scripts/validate-dmg-install.sh +155 -0
- package/scripts/validate-dry-run-schema.mjs +257 -0
- package/scripts/validate-onboarding.mjs +63 -0
- package/scripts/validate-openclaw-cache-recovery-e2e.mjs +113 -0
- package/scripts/validate-openclaw-release-gates.mjs +51 -0
- package/scripts/validate-openclaw-stats-ingestion.mjs +372 -0
- package/scripts/validate-openclaw-usage-health.mjs +95 -0
- package/scripts/validate-packaged-artifact.mjs +233 -0
- package/scripts/validate-packaged-bundled-runtime.sh +191 -0
- package/scripts/validate-packaged-metadata.sh +43 -0
- package/scripts/validate-packaged-openclaw-cache-recovery-e2e.mjs +153 -0
- package/scripts/validate-packaged-openclaw-release-gates.mjs +72 -0
- package/scripts/validate-packaged-openclaw-stats-ingestion.mjs +402 -0
- package/scripts/validate-packaged-sourcemaps.mjs +82 -0
- package/scripts/validate-packaged-usage-alert-rate-e2e.mjs +98 -0
- package/scripts/validate-packaged-usage-probe-noise-e2e.mjs +87 -0
- package/scripts/validate-packaged-usage-recovery-e2e.mjs +90 -0
- package/scripts/validate-trusted-prereqs.sh +44 -0
- package/scripts/validate-usage-alert-rate-e2e.mjs +91 -0
- package/scripts/validate-usage-freshness-e2e.mjs +81 -0
- package/skill/SKILL.md +43 -0
- package/src/config.js +100 -0
- package/src/enrollment.js +176 -0
- package/src/gpu.js +115 -0
- package/src/memory.js +67 -0
- package/src/openclaw-cache.js +51 -0
- package/src/openclaw-usage.js +1020 -0
- package/src/telemetry-mapping.js +54 -0
- package/src/usage-alert.js +41 -0
- package/src/usage-freshness.js +31 -0
- package/test/config.test.mjs +112 -0
- package/test/fixtures/gpu-agx.txt +2 -0
- package/test/fixtures/gpu-iogpu.txt +2 -0
- package/test/fixtures/gpu-top-grep.txt +2 -0
- package/test/fixtures/openclaw-fleet-sample-v1.json +68 -0
- package/test/fixtures/openclaw-mixed-equal-score-status-vs-generic-iso-ts.txt +2 -0
- package/test/fixtures/openclaw-mixed-equal-score-status-vs-generic-newest.txt +2 -0
- package/test/fixtures/openclaw-mixed-equal-score-status-vs-generic-string-ts.txt +2 -0
- package/test/fixtures/openclaw-mixed-status-then-generic-output.txt +2 -0
- package/test/fixtures/openclaw-stats-current-wrapper.json +12 -0
- package/test/fixtures/openclaw-stats-current-wrapper2.json +15 -0
- package/test/fixtures/openclaw-stats-data-wrapper.json +21 -0
- package/test/fixtures/openclaw-stats-nested-session-wrapper.json +23 -0
- package/test/fixtures/openclaw-stats-payload-wrapper.json +1 -0
- package/test/fixtures/openclaw-stats-status-current-wrapper.json +19 -0
- package/test/fixtures/openclaw-stats.json +17 -0
- package/test/fixtures/openclaw-status-ansi-complex-noise.txt +3 -0
- package/test/fixtures/openclaw-status-ansi-noise.txt +2 -0
- package/test/fixtures/openclaw-status-control-noise.txt +1 -0
- package/test/fixtures/openclaw-status-data-wrapper.json +20 -0
- package/test/fixtures/openclaw-status-dcs-noise.txt +1 -0
- package/test/fixtures/openclaw-status-epoch-seconds.json +15 -0
- package/test/fixtures/openclaw-status-mixed-noise.txt +1 -0
- package/test/fixtures/openclaw-status-multi-json.txt +3 -0
- package/test/fixtures/openclaw-status-nested-recent.json +19 -0
- package/test/fixtures/openclaw-status-noisy-default-then-usage.txt +2 -0
- package/test/fixtures/openclaw-status-noisy.txt +3 -0
- package/test/fixtures/openclaw-status-osc-noise.txt +1 -0
- package/test/fixtures/openclaw-status-result-session.json +15 -0
- package/test/fixtures/openclaw-status-session-map-with-defaults.json +23 -0
- package/test/fixtures/openclaw-status-session-map.json +28 -0
- package/test/fixtures/openclaw-status-session-model-name.json +18 -0
- package/test/fixtures/openclaw-status-snake-session-wrapper.json +13 -0
- package/test/fixtures/openclaw-status-stats-current-sessions-snake-tokens.json +25 -0
- package/test/fixtures/openclaw-status-stats-current-sessions.json +28 -0
- package/test/fixtures/openclaw-status-stats-current-usage-time-camelcase.json +19 -0
- package/test/fixtures/openclaw-status-stats-session-default-model.json +27 -0
- package/test/fixtures/openclaw-status-status-wrapper.json +13 -0
- package/test/fixtures/openclaw-status-strings.json +38 -0
- package/test/fixtures/openclaw-status-ts-ms-alias.json +14 -0
- package/test/fixtures/openclaw-status-updated-at-ms-alias.json +14 -0
- package/test/fixtures/openclaw-status-usage-timestamp-ms-alias.json +14 -0
- package/test/fixtures/openclaw-status-usage-ts-alias.json +14 -0
- package/test/fixtures/openclaw-status-wrap-session-object.json +24 -0
- package/test/fixtures/openclaw-status.json +41 -0
- package/test/fixtures/openclaw-usage-model-name-generic.json +9 -0
- package/test/gpu.test.mjs +58 -0
- package/test/memory.test.mjs +35 -0
- package/test/openclaw-cache.test.mjs +48 -0
- package/test/openclaw-env.test.mjs +365 -0
- package/test/openclaw-usage.test.mjs +555 -0
- package/test/telemetry-mapping.test.mjs +69 -0
- package/test/telemetry-row-parser.test.mjs +44 -0
- package/test/usage-alert.test.mjs +73 -0
- package/test/usage-freshness.test.mjs +63 -0
- package/test/validate-dry-run-schema.test.mjs +146 -0
- package/tui/Cargo.lock +801 -0
- package/tui/Cargo.toml +11 -0
- package/tui/src/main.rs +368 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# External onboarding + distribution
|
|
2
|
+
|
|
3
|
+
## Paths for external users
|
|
4
|
+
|
|
5
|
+
### 1) npx quickstart (fastest)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx idlewatch-skill quickstart
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Wizard output:
|
|
12
|
+
- Generates an env file (`idlewatch.env`) under user config directory.
|
|
13
|
+
- In production mode, copies the provided service-account key to `credentials/` with mode `0600`.
|
|
14
|
+
- Emits next-step command to load env and run a one-shot validation.
|
|
15
|
+
|
|
16
|
+
### 2) Signed DMG install (managed rollout)
|
|
17
|
+
|
|
18
|
+
1. Build trusted artifacts:
|
|
19
|
+
```bash
|
|
20
|
+
npm run package:trusted
|
|
21
|
+
```
|
|
22
|
+
2. Distribute `dist/IdleWatch-<version>-signed.dmg`.
|
|
23
|
+
3. User drags app into Applications and launches.
|
|
24
|
+
4. On first run, user executes quickstart from packaged app terminal context to enroll credentials.
|
|
25
|
+
|
|
26
|
+
### Optional: background startup on macOS
|
|
27
|
+
|
|
28
|
+
Users can register the packaged app for background startup with LaunchAgent:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm run install:macos-launch-agent
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
To remove the LaunchAgent later:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm run uninstall:macos-launch-agent
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
See `docs/packaging/macos-dmg.md` for signing/notarization setup.
|
|
41
|
+
|
|
42
|
+
## Credential strategy (least privilege)
|
|
43
|
+
|
|
44
|
+
- Prefer `FIREBASE_SERVICE_ACCOUNT_FILE` over raw JSON env values.
|
|
45
|
+
- Use a dedicated service account per deployment environment.
|
|
46
|
+
- Grant minimal Firestore write permissions needed for telemetry ingestion.
|
|
47
|
+
- Avoid reusing broad admin credentials across developer laptops.
|
|
48
|
+
- Rotate keys periodically and replace the local credential file path atomically.
|
|
49
|
+
|
|
50
|
+
## Automation validation
|
|
51
|
+
|
|
52
|
+
Run:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm run validate:onboarding
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This validates non-interactive enrollment output and secure credential file staging.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# macOS DMG packaging pipeline
|
|
2
|
+
|
|
3
|
+
IdleWatch ships an `.app` scaffold in `dist/` and a DMG installer pipeline.
|
|
4
|
+
Signing/notarization are optional but available when Apple credentials are configured.
|
|
5
|
+
The `scripts/` folder now includes packaging, runtime, and launch lifecycle helpers for
|
|
6
|
+
local testing and release preparation.
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
- Xcode Command Line Tools (`xcode-select --install`)
|
|
11
|
+
- Node.js 20+ on target host (current launcher executes payload with local `node` or bundled runtime)
|
|
12
|
+
- Apple Developer signing identity (for production)
|
|
13
|
+
- Notary API key/profile configured in keychain (for production)
|
|
14
|
+
- Optional tools for polished DMG UX:
|
|
15
|
+
- `create-dmg`
|
|
16
|
+
- `dmgbuild`
|
|
17
|
+
|
|
18
|
+
## Pipeline stages
|
|
19
|
+
|
|
20
|
+
1. **Build artifact**
|
|
21
|
+
- `npm ci`
|
|
22
|
+
- `npm test`
|
|
23
|
+
- `npm pack` to generate tarball
|
|
24
|
+
2. **Create app wrapper skeleton**
|
|
25
|
+
- Stage Node package + app metadata into `dist/IdleWatch.app/Contents/Resources/`
|
|
26
|
+
- Add launcher script in `Contents/MacOS/IdleWatch`
|
|
27
|
+
3. **Launch lifecycle (optional)**
|
|
28
|
+
- Install LaunchAgent to keep IdleWatch running in the background:
|
|
29
|
+
- `scripts/install-macos-launch-agent.sh`
|
|
30
|
+
- Uninstall: `scripts/uninstall-macos-launch-agent.sh`
|
|
31
|
+
4. **Codesign (optional)**
|
|
32
|
+
- Sign app with `codesign --deep --force --options runtime ...`
|
|
33
|
+
5. **Notarize (optional)**
|
|
34
|
+
- `xcrun notarytool submit ... --wait`
|
|
35
|
+
- `xcrun stapler staple IdleWatch-<ver>-*.dmg`
|
|
36
|
+
6. **Build DMG (baseline)**
|
|
37
|
+
- `hdiutil create ...` output: `dist/IdleWatch-<version>-unsigned.dmg` or `-signed.dmg`
|
|
38
|
+
|
|
39
|
+
## Current scaffold commands
|
|
40
|
+
|
|
41
|
+
### OpenClaw discoverability in packaged launches
|
|
42
|
+
|
|
43
|
+
IdleWatch writes packaging metadata (`Contents/Resources/packaging-metadata.json`) during `package-macos` with the `openclawBinHint` used at build time.
|
|
44
|
+
|
|
45
|
+
When the packaged launcher starts, it resolves the OpenClaw binary in this order:
|
|
46
|
+
|
|
47
|
+
1. `IDLEWATCH_OPENCLAW_BIN` (explicit runtime override, preferred)
|
|
48
|
+
2. `IDLEWATCH_OPENCLAW_BIN_HINT` (legacy launcher hint, supported)
|
|
49
|
+
3. `openclawBinHint` from `packaging-metadata.json`
|
|
50
|
+
(packaging writes this value from the same `IDLEWATCH_OPENCLAW_BIN` / `IDLEWATCH_OPENCLAW_BIN_HINT` inputs used during build)
|
|
51
|
+
4. `openclaw` via normal `PATH`
|
|
52
|
+
|
|
53
|
+
When bundling a Node runtime, `package-macos.sh` copies a portable subset (`bin`, `lib`, `include`) with symlink dereference, so packaged layouts remain portable even when the host runtime is a symlink and avoids copying non-essential symlinked shell-completion trees.
|
|
54
|
+
|
|
55
|
+
OpenClaw command probing in the packaged runtime uses the same command preference list as local runs:
|
|
56
|
+
`status --json`, `usage --json`, `session status --json`, `session_status --json`, `stats --json`.
|
|
57
|
+
|
|
58
|
+
This makes packaged installs more reliable in environments where `openclaw` is not on the default launcher `PATH`.
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
Each packaged app includes `Contents/Resources/packaging-metadata.json` with build provenance (version, signing/runtime hints, source git commit/clean-state, payload filename, and launcher settings) to support supportability checks and deterministic QA.
|
|
62
|
+
|
|
63
|
+
Optional environment variables:
|
|
64
|
+
- `IDLEWATCH_OPENCLAW_BIN="/opt/homebrew/bin/openclaw"` — pins OpenClaw binary path for packaged/non-interactive runtime usage collection.
|
|
65
|
+
- `IDLEWATCH_OPENCLAW_BIN_HINT="/opt/homebrew/bin/openclaw"` — legacy launcher hint (supported for compatibility). When `IDLEWATCH_OPENCLAW_BIN` is unset, this is used as the strict-mode explicit fallback as well.
|
|
66
|
+
- `IDLEWATCH_NODE_BIN="/opt/homebrew/bin/node"` — pins Node binary path used by packaged app launcher.
|
|
67
|
+
- `IDLEWATCH_SKIP_SOURCEMAP_VALIDATION="1"` — temporarily disables packaged sourcemap integrity validation during `package:macos` (use only for emergency/debug scenarios).
|
|
68
|
+
- `IDLEWATCH_NODE_RUNTIME_DIR="/path/to/node-runtime"` — optionally bundles portable Node runtime into app resources (`<runtime>/bin/node` required).
|
|
69
|
+
- `IDLEWATCH_APP_PATH="/Applications/IdleWatch.app"` — app path used by LaunchAgent scripts.
|
|
70
|
+
- `IDLEWATCH_LAUNCH_AGENT_LABEL="com.idlewatch.agent"` — override LaunchAgent label.
|
|
71
|
+
- `IDLEWATCH_LAUNCH_AGENT_PLIST_ROOT="$HOME/Library/LaunchAgents"` — override plist root for install/uninstall scripts.
|
|
72
|
+
- `IDLEWATCH_LAUNCH_AGENT_LOG_DIR="$HOME/Library/Logs/IdleWatch"` — set log destination for LaunchAgent output.
|
|
73
|
+
- `IDLEWATCH_OPENCLAW_PROBE_TIMEOUT_MS=2500` — baseline per-probe timeout for OpenClaw usage commands (packaging validation wrappers default to `4000`).
|
|
74
|
+
- `IDLEWATCH_OPENCLAW_MAX_OUTPUT_BYTES=2097152` — maximum bytes captured per OpenClaw probe call (increase for noisy/progress-heavy OpenClaw outputs).
|
|
75
|
+
- `IDLEWATCH_OPENCLAW_MAX_OUTPUT_BYTES_HARD_CAP=16777216` — hard ceiling for automatic capture growth on noisy outputs before giving up.
|
|
76
|
+
- OpenClaw probe output is merged from stdout+stderr before JSON extraction, so mixed-output CLIs remain parseable during packaged validation and local collection.
|
|
77
|
+
- `IDLEWATCH_DRY_RUN_TIMEOUT_MS=15000` — baseline timeout in milliseconds for `--dry-run` validation helpers (prevents launchers that emit continuous output from hanging validation).
|
|
78
|
+
- Packaged runtime and DMG install validators default this to `90000` during execution to reduce false timeout failures on slow hosts.
|
|
79
|
+
- OpenClaw release-gate helpers now default this to `60000` (kept aligned with host release gates unless package-specific override is needed):
|
|
80
|
+
- `validate:openclaw-release-gates`
|
|
81
|
+
- `validate:packaged-openclaw-release-gates` (and `:reuse-artifact`)
|
|
82
|
+
- This avoids false negatives on slower machines where `openclaw --json` probes may take longer than `15000ms`.
|
|
83
|
+
- `IDLEWATCH_DRY_RUN_TIMEOUT_RETRY_BONUS_MS=10000` — extra timeout budget (in ms) applied when an `--dry-run` attempt does not emit telemetry.
|
|
84
|
+
- Example: `90000` -> fallback retry tries `100000` (then `110000`, etc., if `IDLEWATCH_DRY_RUN_TIMEOUT_MAX_ATTEMPTS` is raised).
|
|
85
|
+
- `IDLEWATCH_DRY_RUN_TIMEOUT_MAX_ATTEMPTS=3` — number of timeout/retry attempts for packaged validator dry-runs. Set to `1` to keep strict single-pass behavior.
|
|
86
|
+
- `IDLEWATCH_DRY_RUN_TIMEOUT_BACKOFF_MS=2000` — optional backoff delay (ms) between retries in packaged validators. Helps avoid flapping when disk or mount pressure temporarily stalls output.
|
|
87
|
+
- Set to `0` for tight loops when deterministic timing is already stable.
|
|
88
|
+
- `IDLEWATCH_REQUIRE_SOURCE_COMMIT_MATCH=1` — when validating reusable artifacts (including DMG-installed artifacts), require current checkout commit to match `packaging-metadata.json.sourceGitCommit`.
|
|
89
|
+
- If the artifact lacks `sourceGitCommit`, validation fails fast and requests a fresh package by default.
|
|
90
|
+
- Set `IDLEWATCH_ALLOW_LEGACY_SOURCE_GIT_COMMIT=1` to temporarily continue with older artifacts while planning a rebuild.
|
|
91
|
+
- `IDLEWATCH_REQUIRE_SOURCE_DIRTY_MATCH=1` — when set, require current working-tree cleanliness to match `packaging-metadata.json.sourceGitDirty` for artifacts with reliable provenance (`sourceGitDirtyKnown: true`).
|
|
92
|
+
- For strict reuse-only workflows, this is fail-closed when provenance is incomplete.
|
|
93
|
+
- Set `IDLEWATCH_ALLOW_LEGACY_SOURCE_GIT_DIRTY=1` to temporarily keep compatibility with legacy artifacts that only expose `sourceGitDirty` or omit dirty provenance while planning a rebuild.
|
|
94
|
+
- Rebuild with `npm run package:macos` to regenerate canonical provenance (`sourceGitDirtyKnown: true`).
|
|
95
|
+
- `IDLEWATCH_PACKAGED_ARTIFACT_MAX_AGE_MS=<ms>` — if set, fail reusable artifact validation when metadata `builtAt` exceeds this age.
|
|
96
|
+
- `IDLEWATCH_ARTIFACT_DIR=<path>` — override artifact root when invoking `validate-packaged-artifact.mjs` against non-dist app bundles (used by `validate:dmg-install`).
|
|
97
|
+
- `IDLEWATCH_DMG_ATTACH_TIMEOUT_MS=30000` — maximum wall time for `hdiutil attach` in `validate:dmg-install`.
|
|
98
|
+
- `IDLEWATCH_DMG_DETACH_TIMEOUT_MS=8000` — timeout for ejecting the DMG in cleanup.
|
|
99
|
+
- `MACOS_CODESIGN_IDENTITY="Developer ID Application: ..."` — signs `IdleWatch.app` during `package-macos.sh`.
|
|
100
|
+
- `MACOS_NOTARY_PROFILE="<keychain-profile>"` — notarizes/staples DMG during `build-dmg.sh`.
|
|
101
|
+
- `IDLEWATCH_REQUIRE_TRUSTED_DISTRIBUTION=1` — strict mode; fails packaging unless signing/notarization prerequisites are present.
|
|
102
|
+
- `IDLEWATCH_ALLOW_UNSIGNED_TAG_RELEASE=1` — explicit CI-only bypass for tag builds; disables auto-strict guard that otherwise blocks unsigned `refs/tags/*` packaging.
|
|
103
|
+
- `IDLEWATCH_BUNDLED_RUNTIME_REQUIRED=1` — when set to `1`, `validate:packaged-bundled-runtime`/`:reuse-artifact` requires `nodeRuntimeBundled=1` in metadata and enforces strict PATH-scrubbed validation. In reuse mode, this defaults to `0` so prebuilt non-bundled artifacts can still validate launchability with host PATH fallback; set `1` to force strict node-free checks.
|
|
104
|
+
- `IDLEWATCH_USE_ORIGINAL_PATH_FOR_NON_BUNDLED=1` — allows `validate:packaged-bundled-runtime:reuse-artifact` to validate non-bundled artifacts by falling back to current host PATH when `nodeRuntimeBundled!=1`.
|
|
105
|
+
|
|
106
|
+
- `scripts/package-macos.sh`
|
|
107
|
+
- Creates `dist/IdleWatch.app`
|
|
108
|
+
- Generates the package tarball via `npm pack --json` and selects the reported filename deterministically (instead of relying on glob/mtime heuristics), then expands it into `Contents/Resources/payload/package`
|
|
109
|
+
- Installs production runtime dependencies into packaged payload (`npm ci --omit=dev` when a lockfile is available, otherwise `npm install --omit=dev`) so mounted-DMG launches do not rely on workspace/global `node_modules`
|
|
110
|
+
- Validates packaged JavaScript sourcemap integrity immediately after dependency install (`node scripts/validate-packaged-sourcemaps.mjs`). If any `sourceMappingURL` points to a missing external file, packaging fails early with a concrete path list to force a clean rebuild and avoid non-deterministic DMG noise.
|
|
111
|
+
- Generates a working launcher (`Contents/MacOS/IdleWatch`) that runs:
|
|
112
|
+
- `<node> Contents/Resources/payload/package/bin/idlewatch-agent.js ...`
|
|
113
|
+
- Node binary resolution order: `IDLEWATCH_NODE_BIN` → bundled runtime (`Contents/Resources/runtime/node/bin/node`, when `IDLEWATCH_NODE_RUNTIME_DIR` is supplied at package time) → `PATH` (`node`)
|
|
114
|
+
- Launcher enforces Node.js major version `>=20` and fails with actionable runtime-path/version diagnostics otherwise
|
|
115
|
+
- Stages `dist/dmg-root` and adds `/Applications` symlink
|
|
116
|
+
- `scripts/build-dmg.sh`
|
|
117
|
+
- Creates `dist/IdleWatch-<version>-unsigned.dmg` (or `-signed.dmg` when `MACOS_CODESIGN_IDENTITY` is set) from `dist/dmg-root`
|
|
118
|
+
- Writes SHA-256 checksum to `dist/IdleWatch-<version>-<signed|unsigned>.dmg.sha256`
|
|
119
|
+
- If `MACOS_NOTARY_PROFILE` is set, submits DMG via `notarytool` and staples on success
|
|
120
|
+
- `scripts/validate-packaged-metadata.sh`
|
|
121
|
+
- Verifies generated `Contents/Resources/packaging-metadata.json` and packaged entrypoint integrity
|
|
122
|
+
- `scripts/validate-dmg-checksum.sh`
|
|
123
|
+
- Verifies checksum integrity for latest DMG (or explicit path)
|
|
124
|
+
- `npm run validate:dmg-checksum`
|
|
125
|
+
- Runs `scripts/validate-dmg-checksum.sh` for artifact integrity checks in local/release workflows
|
|
126
|
+
- `npm run validate:packaged-metadata`
|
|
127
|
+
- Runs `scripts/validate-packaged-metadata.sh` and validates bundle metadata + platform consistency
|
|
128
|
+
- `npm run package:release`
|
|
129
|
+
- Runs `package:trusted` and checksum validation in one command (safe for production-ready artifact preparation)
|
|
130
|
+
- `scripts/install-macos-launch-agent.sh`
|
|
131
|
+
- Writes `~/Library/LaunchAgents/<label>.plist`
|
|
132
|
+
- Loads `LaunchAgent` under current user sandbox, with `StartInterval` aligned to `IDLEWATCH_INTERVAL_MS` (min 60s), background mode, stdout/stderr logs
|
|
133
|
+
- `scripts/uninstall-macos-launch-agent.sh`
|
|
134
|
+
- Unloads and removes `~/Library/LaunchAgents/<label>.plist`
|
|
135
|
+
- `npm run install:macos-launch-agent`
|
|
136
|
+
- Wrapper for `scripts/install-macos-launch-agent.sh`
|
|
137
|
+
- `npm run uninstall:macos-launch-agent`
|
|
138
|
+
- Wrapper for `scripts/uninstall-macos-launch-agent.sh`
|
|
139
|
+
- `scripts/validate-trusted-prereqs.sh`
|
|
140
|
+
- Validates local signing identity + notary keychain profile before trusted packaging
|
|
141
|
+
- `npm run package:trusted`
|
|
142
|
+
- Strict signed + notarized local path (`IDLEWATCH_REQUIRE_TRUSTED_DISTRIBUTION=1`)
|
|
143
|
+
- `npm run validate:dmg-install`
|
|
144
|
+
- Mounts latest DMG (or a provided path), copies `IdleWatch.app` into a temp Applications-like folder, then validates launcher dry-run schema from the copied app.
|
|
145
|
+
- Before launch dry-run, runs `scripts/validate-packaged-artifact.mjs` against mounted `IdleWatch.app` (`IDLEWATCH_ARTIFACT_DIR` override) so commit/dirty-state staleness is caught before expensive runtime checks.
|
|
146
|
+
- Runs OpenClaw-enabled dry-run first and retries up to `IDLEWATCH_DRY_RUN_TIMEOUT_MAX_ATTEMPTS` with increasing timeout (`+IDLEWATCH_DRY_RUN_TIMEOUT_RETRY_BONUS_MS`) plus optional backoff (`IDLEWATCH_DRY_RUN_TIMEOUT_BACKOFF_MS`) before a disabled-usage launchability pass (`IDLEWATCH_OPENCLAW_USAGE=off`).
|
|
147
|
+
- Uses bounded attach/detach timeouts and emits the last ~60 lines of failed attempt output to make hangs diagnosable.
|
|
148
|
+
- Uses shared noise-tolerant JSON extraction from `scripts/lib/telemetry-row-parser.mjs`, so ANSI/control frames in launcher output do not mask valid telemetry rows.
|
|
149
|
+
- `npm run validate:packaged-bundled-runtime`
|
|
150
|
+
- By default, rebuilds the app with `IDLEWATCH_NODE_RUNTIME_DIR` pointed at the current Node runtime, validates the generated package metadata, then executes `IdleWatch.app/Contents/MacOS/IdleWatch --dry-run` in a PATH-scrubbed environment to confirm bundled runtime/path-resolution still works when the host PATH does not provide a Node binary.
|
|
151
|
+
- If `IDLEWATCH_SKIP_PACKAGE_MACOS=1`, the validator reuses an existing `dist/IdleWatch.app` artifact instead of repackaging (useful for repeated CI/test runs).
|
|
152
|
+
- Reuse mode now verifies the reused artifact is compatible with the current workspace (and, when strict mode is enabled, requires bundled runtime metadata); when strict mode is off it can still run launchability checks for non-bundled artifacts via fallback PATH logic.
|
|
153
|
+
- In fallback mode, the script checks Node presence with an absolute-path lookup (`which node`) to avoid shell hash-table false-positives in temporary PATH checks.
|
|
154
|
+
- If checks fail, rerun with a fresh package (`npm run package:macos` or remove `IDLEWATCH_SKIP_PACKAGE_MACOS`).
|
|
155
|
+
- Non-bundled artifacts can still be validated in host-PATH launchability mode using `IDLEWATCH_USE_ORIGINAL_PATH_FOR_NON_BUNDLED=1` (this remains the default fallback in reuse mode).
|
|
156
|
+
- The validation is timeout-bound via `IDLEWATCH_DRY_RUN_TIMEOUT_MS`, retry count (`IDLEWATCH_DRY_RUN_TIMEOUT_MAX_ATTEMPTS`), and incremental timeout/backoff (`IDLEWATCH_DRY_RUN_TIMEOUT_RETRY_BONUS_MS`, `IDLEWATCH_DRY_RUN_TIMEOUT_BACKOFF_MS`).
|
|
157
|
+
- It validates required sample fields (`host`, `ts`, and `fleet`/`source` contract) while preventing false positives from log banners.
|
|
158
|
+
- If the OpenClaw-enabled dry-run path does not emit telemetry within the timeout window, the script retries with extended timeout and then falls back to `IDLEWATCH_OPENCLAW_USAGE=off` for deterministic launchability checks.
|
|
159
|
+
- In `IDLEWATCH_OPENCLAW_USAGE=off`, schema validation expects `source.usage=disabled` and `usageFreshnessState=disabled`, so launchability checks remain deterministic even without local OpenClaw CLI availability.
|
|
160
|
+
- Failed attempt output is preserved in temp attempt logs and the last 60 lines are echoed to stderr, so intermittent startup hangs are easier to diagnose.
|
|
161
|
+
- Applies the same shared noise-tolerant JSON extractor used by other validators, keeping schema checks robust when runtime output includes ANSI/control frames.
|
|
162
|
+
- `npm run validate:packaged-bundled-runtime:reuse-artifact`
|
|
163
|
+
- Fast path for validation pipelines that already ran `package:macos` (or a previous packaging step): reuses the same artifact and skips repackaging while keeping all launchability assertions.
|
|
164
|
+
- Clean-machine verification note:
|
|
165
|
+
- For external QA, treat `validate:packaged-bundled-runtime` output plus a fresh `validate:dmg-install` smoke run from a separate macOS account/environment as your clean-machine gate for end-user install friction.
|
|
166
|
+
- This script is self-contained (Node-only) and does not depend on host Python tooling.
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
## CI integration
|
|
170
|
+
|
|
171
|
+
- Baseline packaging smoke: `.github/workflows/ci.yml` (`macos-packaging-smoke` job; includes bundled-runtime launcher validation via `npm run validate:packaged-bundled-runtime` (or `...:reuse-artifact` when `dist/` is prebuilt), packaged app telemetry schema check via `npm run validate:packaged-dry-run-schema:reuse-artifact`, packaged usage-health validation via `npm run validate:packaged-usage-health:reuse-artifact`, and OpenClaw robustness coverage via `npm run validate:packaged-openclaw-robustness:reuse-artifact` (age-SLO, alert-rate transitions, probe-noise resilience, and OpenClaw release checks), plus checksum validation via `npm run validate:dmg-checksum` and DMG install validation via `npm run validate:dmg-install`).
|
|
172
|
+
- All packaged `:reuse-artifact` wrappers run `npm run validate:packaged-artifact` first, which checks `dist/` launcher presence, metadata integrity, source-commit freshness, and (by default) clean/dirty working-tree parity so mismatched build contexts fail fast with a rebuild hint.
|
|
173
|
+
- Core validation coverage also runs `npm run validate:openclaw-release-gates` (host health/stats/cache guardrail) and `npm run validate:packaged-openclaw-release-gates:reuse-artifact` (which bundles `validate:packaged-usage-health:reuse-artifact`, `validate:packaged-openclaw-stats-ingestion:reuse-artifact`, and `validate:packaged-openclaw-cache-recovery-e2e:reuse-artifact`) to guard both host and packaged OpenClaw-health behavior, `stats --json` fallback path, and packaged recovery behavior when probe output is noisy or stale. `validate:openclaw-stats-ingestion` and packaged stats ingestion now explicitly cover `status.result`, `status.current`, and timestamp-alias variants (`usage_ts_ms`, `usage_timestamp_ms`, `updated_at_ms`, `ts_ms`) used in the wild.
|
|
174
|
+
- For local scripted workflows, `npm run validate:openclaw-release-gates:all` (host-only on non-macOS, adds packaged reuse on macOS) plus `npm run validate:packaged-openclaw-release-gates:all` provide staged host+packaged coverage in one command.
|
|
175
|
+
|
|
176
|
+
- Trusted signed/notarized release path: `.github/workflows/release-macos-trusted.yml`
|
|
177
|
+
|
|
178
|
+
Trusted release workflow enforces stronger OpenClaw verification and expects these repository secrets:
|
|
179
|
+
|
|
180
|
+
- `MACOS_CODESIGN_IDENTITY`
|
|
181
|
+
- `APPLE_DEVELOPER_ID_APP_P12_BASE64`
|
|
182
|
+
- `APPLE_DEVELOPER_ID_APP_P12_PASSWORD`
|
|
183
|
+
- `APPLE_BUILD_KEYCHAIN_PASSWORD`
|
|
184
|
+
- `APPLE_NOTARY_KEY_ID`
|
|
185
|
+
- `APPLE_NOTARY_ISSUER_ID`
|
|
186
|
+
- `APPLE_NOTARY_API_KEY_P8`
|
|
187
|
+
|
|
188
|
+
When present, the workflow imports a temporary build keychain, signs `IdleWatch.app`, notarizes/staples the DMG, and uploads `IdleWatch-*-signed.dmg`.
|
|
189
|
+
|
|
190
|
+
Release policy gate:
|
|
191
|
+
- Trusted release workflow enforces OpenClaw resilience in two stages before artifact upload:
|
|
192
|
+
- `npm run validate:openclaw-release-gates` (host validation)
|
|
193
|
+
- `npm run validate:packaged-openclaw-release-gates:reuse-artifact` (runs `validate:packaged-usage-health:reuse-artifact`, `validate:packaged-openclaw-stats-ingestion:reuse-artifact`, and `validate:packaged-openclaw-cache-recovery-e2e:reuse-artifact` against the signed artifact).
|
|
194
|
+
In release mode the packaged checks run via the wrapper’s `IDLEWATCH_SKIP_PACKAGE_MACOS=1`, and enforce OpenClaw usage presence by default (`IDLEWATCH_REQUIRE_OPENCLAW_USAGE=1` unless explicitly disabled). Set `0|false|off|no` to disable and `1|true|on|yes` to force on.
|
|
195
|
+
- Trusted release workflow additionally enforces `IDLEWATCH_MAX_OPENCLAW_USAGE_AGE_MS=300000` so packaged rows fail if usage age is excessively stale at validation time.
|
|
196
|
+
- Packaging scripts now auto-enable strict trusted requirements on CI tag refs (`refs/tags/*`) to prevent accidental unsigned release artifacts; set `IDLEWATCH_ALLOW_UNSIGNED_TAG_RELEASE=1` only for deliberate break-glass exceptions.
|
|
197
|
+
- Packaged validators default to packaging a fresh app unless reuse mode is requested. `validate:packaged-dry-run-schema`, `validate:packaged-usage-age-slo`, and `validate:packaged-usage-health` still auto-run `package:macos` first; their `:reuse-artifact` counterparts assume a prebuilt artifact and set `IDLEWATCH_SKIP_PACKAGE_MACOS=1` to validate the already-built binary directly.
|
|
198
|
+
- Reuse-mode validation uses the artifact preflight in `scripts/validate-packaged-artifact.mjs`, so stale/foreign artifacts now fail fast before expensive dry-run execution.
|
|
199
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# macOS LaunchAgent Setup
|
|
2
|
+
|
|
3
|
+
Run IdleWatch as a background service that starts automatically at login.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- IdleWatch.app installed (typically `/Applications/IdleWatch.app`)
|
|
8
|
+
- macOS 12+ (Monterey or later)
|
|
9
|
+
- Optional: Firebase credentials configured via `quickstart`
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm run install:macos-launch-agent
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This creates a `launchd` plist at `~/Library/LaunchAgents/com.idlewatch.agent.plist` and loads it immediately.
|
|
18
|
+
|
|
19
|
+
### Configuration
|
|
20
|
+
|
|
21
|
+
All settings are optional environment variables set **before** running the install script:
|
|
22
|
+
|
|
23
|
+
| Variable | Default | Description |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `IDLEWATCH_APP_PATH` | `/Applications/IdleWatch.app` | Path to installed app |
|
|
26
|
+
| `IDLEWATCH_APP_BIN` | `$APP_PATH/Contents/MacOS/IdleWatch` | Launcher binary |
|
|
27
|
+
| `IDLEWATCH_LAUNCH_AGENT_LABEL` | `com.idlewatch.agent` | Plist label |
|
|
28
|
+
| `IDLEWATCH_LAUNCH_AGENT_LOG_DIR` | `~/Library/Logs/IdleWatch` | Log output directory |
|
|
29
|
+
| `IDLEWATCH_INTERVAL_MS` | `10000` | Collection interval (min 60s for StartInterval) |
|
|
30
|
+
|
|
31
|
+
### Custom app path
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
IDLEWATCH_APP_PATH="$HOME/Applications/IdleWatch.app" npm run install:macos-launch-agent
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Uninstall
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm run uninstall:macos-launch-agent
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Removes the plist and unloads the agent from launchd.
|
|
44
|
+
|
|
45
|
+
## Logs
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# stdout
|
|
49
|
+
tail -f ~/Library/Logs/IdleWatch/idlewatch.out.log
|
|
50
|
+
|
|
51
|
+
# stderr
|
|
52
|
+
tail -f ~/Library/Logs/IdleWatch/idlewatch.err.log
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Verify
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Check if running
|
|
59
|
+
launchctl print gui/$(id -u)/com.idlewatch.agent
|
|
60
|
+
|
|
61
|
+
# Quick health check
|
|
62
|
+
/Applications/IdleWatch.app/Contents/MacOS/IdleWatch --dry-run --json | python3 -m json.tool
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Troubleshooting
|
|
66
|
+
|
|
67
|
+
- **Agent not starting:** Check `~/Library/Logs/IdleWatch/idlewatch.err.log` for errors.
|
|
68
|
+
- **Permission denied:** Ensure the binary is executable: `chmod +x /Applications/IdleWatch.app/Contents/MacOS/IdleWatch`
|
|
69
|
+
- **Stale telemetry:** If `openclawUsageAgeMs` stays high, OpenClaw may be idle — this is expected behavior (see `docs/telemetry/idle-stale-policy.md`).
|
|
70
|
+
- **Reinstall after update:** After installing a new IdleWatch.app, re-run the install script to reload the agent.
|