@josharsh/demon-cli 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.
Files changed (172) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +130 -0
  3. package/dist/actions/base.d.ts +4 -0
  4. package/dist/actions/base.d.ts.map +1 -0
  5. package/dist/actions/base.js +32 -0
  6. package/dist/actions/base.js.map +1 -0
  7. package/dist/actions/file.d.ts +7 -0
  8. package/dist/actions/file.d.ts.map +1 -0
  9. package/dist/actions/file.js +35 -0
  10. package/dist/actions/file.js.map +1 -0
  11. package/dist/actions/github.d.ts +7 -0
  12. package/dist/actions/github.d.ts.map +1 -0
  13. package/dist/actions/github.js +125 -0
  14. package/dist/actions/github.js.map +1 -0
  15. package/dist/actions/index.d.ts +5 -0
  16. package/dist/actions/index.d.ts.map +1 -0
  17. package/dist/actions/index.js +22 -0
  18. package/dist/actions/index.js.map +1 -0
  19. package/dist/actions/log.d.ts +7 -0
  20. package/dist/actions/log.d.ts.map +1 -0
  21. package/dist/actions/log.js +30 -0
  22. package/dist/actions/log.js.map +1 -0
  23. package/dist/actions/notify.d.ts +11 -0
  24. package/dist/actions/notify.d.ts.map +1 -0
  25. package/dist/actions/notify.js +34 -0
  26. package/dist/actions/notify.js.map +1 -0
  27. package/dist/actions/script.d.ts +7 -0
  28. package/dist/actions/script.d.ts.map +1 -0
  29. package/dist/actions/script.js +46 -0
  30. package/dist/actions/script.js.map +1 -0
  31. package/dist/actions/slack.d.ts +7 -0
  32. package/dist/actions/slack.d.ts.map +1 -0
  33. package/dist/actions/slack.js +47 -0
  34. package/dist/actions/slack.js.map +1 -0
  35. package/dist/actions/webhook.d.ts +7 -0
  36. package/dist/actions/webhook.d.ts.map +1 -0
  37. package/dist/actions/webhook.js +41 -0
  38. package/dist/actions/webhook.js.map +1 -0
  39. package/dist/adapters/clock.d.ts +3 -0
  40. package/dist/adapters/clock.d.ts.map +1 -0
  41. package/dist/adapters/clock.js +8 -0
  42. package/dist/adapters/clock.js.map +1 -0
  43. package/dist/adapters/fs-kv.d.ts +3 -0
  44. package/dist/adapters/fs-kv.d.ts.map +1 -0
  45. package/dist/adapters/fs-kv.js +34 -0
  46. package/dist/adapters/fs-kv.js.map +1 -0
  47. package/dist/adapters/keys.d.ts +3 -0
  48. package/dist/adapters/keys.d.ts.map +1 -0
  49. package/dist/adapters/keys.js +17 -0
  50. package/dist/adapters/keys.js.map +1 -0
  51. package/dist/commands/ensure-provider.d.ts +7 -0
  52. package/dist/commands/ensure-provider.d.ts.map +1 -0
  53. package/dist/commands/ensure-provider.js +121 -0
  54. package/dist/commands/ensure-provider.js.map +1 -0
  55. package/dist/commands/init.d.ts +2 -0
  56. package/dist/commands/init.d.ts.map +1 -0
  57. package/dist/commands/init.js +196 -0
  58. package/dist/commands/init.js.map +1 -0
  59. package/dist/commands/inspect.d.ts +7 -0
  60. package/dist/commands/inspect.d.ts.map +1 -0
  61. package/dist/commands/inspect.js +97 -0
  62. package/dist/commands/inspect.js.map +1 -0
  63. package/dist/commands/install.d.ts +3 -0
  64. package/dist/commands/install.d.ts.map +1 -0
  65. package/dist/commands/install.js +192 -0
  66. package/dist/commands/install.js.map +1 -0
  67. package/dist/commands/logs.d.ts +8 -0
  68. package/dist/commands/logs.d.ts.map +1 -0
  69. package/dist/commands/logs.js +103 -0
  70. package/dist/commands/logs.js.map +1 -0
  71. package/dist/commands/providers.d.ts +5 -0
  72. package/dist/commands/providers.d.ts.map +1 -0
  73. package/dist/commands/providers.js +251 -0
  74. package/dist/commands/providers.js.map +1 -0
  75. package/dist/commands/ps.d.ts +2 -0
  76. package/dist/commands/ps.d.ts.map +1 -0
  77. package/dist/commands/ps.js +87 -0
  78. package/dist/commands/ps.js.map +1 -0
  79. package/dist/commands/secrets.d.ts +9 -0
  80. package/dist/commands/secrets.d.ts.map +1 -0
  81. package/dist/commands/secrets.js +97 -0
  82. package/dist/commands/secrets.js.map +1 -0
  83. package/dist/commands/start.d.ts +10 -0
  84. package/dist/commands/start.d.ts.map +1 -0
  85. package/dist/commands/start.js +238 -0
  86. package/dist/commands/start.js.map +1 -0
  87. package/dist/commands/status.d.ts +2 -0
  88. package/dist/commands/status.d.ts.map +1 -0
  89. package/dist/commands/status.js +69 -0
  90. package/dist/commands/status.js.map +1 -0
  91. package/dist/commands/stop.d.ts +6 -0
  92. package/dist/commands/stop.d.ts.map +1 -0
  93. package/dist/commands/stop.js +43 -0
  94. package/dist/commands/stop.js.map +1 -0
  95. package/dist/index.d.ts +3 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +253 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/providers/config.d.ts +8 -0
  100. package/dist/providers/config.d.ts.map +1 -0
  101. package/dist/providers/config.js +31 -0
  102. package/dist/providers/config.js.map +1 -0
  103. package/dist/providers/credentials.d.ts +5 -0
  104. package/dist/providers/credentials.d.ts.map +1 -0
  105. package/dist/providers/credentials.js +69 -0
  106. package/dist/providers/credentials.js.map +1 -0
  107. package/dist/providers/registry.d.ts +12 -0
  108. package/dist/providers/registry.d.ts.map +1 -0
  109. package/dist/providers/registry.js +58 -0
  110. package/dist/providers/registry.js.map +1 -0
  111. package/dist/providers/secrets.d.ts +9 -0
  112. package/dist/providers/secrets.d.ts.map +1 -0
  113. package/dist/providers/secrets.js +44 -0
  114. package/dist/providers/secrets.js.map +1 -0
  115. package/dist/providers/system-auth.d.ts +8 -0
  116. package/dist/providers/system-auth.d.ts.map +1 -0
  117. package/dist/providers/system-auth.js +168 -0
  118. package/dist/providers/system-auth.js.map +1 -0
  119. package/dist/runtime/daemon.d.ts +10 -0
  120. package/dist/runtime/daemon.d.ts.map +1 -0
  121. package/dist/runtime/daemon.js +264 -0
  122. package/dist/runtime/daemon.js.map +1 -0
  123. package/dist/runtime/supervisor.d.ts +6 -0
  124. package/dist/runtime/supervisor.d.ts.map +1 -0
  125. package/dist/runtime/supervisor.js +45 -0
  126. package/dist/runtime/supervisor.js.map +1 -0
  127. package/dist/types.d.ts +27 -0
  128. package/dist/types.d.ts.map +1 -0
  129. package/dist/types.js +7 -0
  130. package/dist/types.js.map +1 -0
  131. package/dist/utils/logger.d.ts +4 -0
  132. package/dist/utils/logger.d.ts.map +1 -0
  133. package/dist/utils/logger.js +44 -0
  134. package/dist/utils/logger.js.map +1 -0
  135. package/dist/utils/manifest.d.ts +5 -0
  136. package/dist/utils/manifest.d.ts.map +1 -0
  137. package/dist/utils/manifest.js +276 -0
  138. package/dist/utils/manifest.js.map +1 -0
  139. package/dist/utils/paths.d.ts +13 -0
  140. package/dist/utils/paths.d.ts.map +1 -0
  141. package/dist/utils/paths.js +52 -0
  142. package/dist/utils/paths.js.map +1 -0
  143. package/dist/utils/time.d.ts +11 -0
  144. package/dist/utils/time.d.ts.map +1 -0
  145. package/dist/utils/time.js +40 -0
  146. package/dist/utils/time.js.map +1 -0
  147. package/dist/watchers/base.d.ts +3 -0
  148. package/dist/watchers/base.d.ts.map +1 -0
  149. package/dist/watchers/base.js +20 -0
  150. package/dist/watchers/base.js.map +1 -0
  151. package/dist/watchers/filesystem.d.ts +12 -0
  152. package/dist/watchers/filesystem.d.ts.map +1 -0
  153. package/dist/watchers/filesystem.js +211 -0
  154. package/dist/watchers/filesystem.js.map +1 -0
  155. package/dist/watchers/github.d.ts +3 -0
  156. package/dist/watchers/github.d.ts.map +1 -0
  157. package/dist/watchers/github.js +322 -0
  158. package/dist/watchers/github.js.map +1 -0
  159. package/dist/watchers/http.d.ts +13 -0
  160. package/dist/watchers/http.d.ts.map +1 -0
  161. package/dist/watchers/http.js +149 -0
  162. package/dist/watchers/http.js.map +1 -0
  163. package/dist/watchers/index.d.ts +8 -0
  164. package/dist/watchers/index.d.ts.map +1 -0
  165. package/dist/watchers/index.js +25 -0
  166. package/dist/watchers/index.js.map +1 -0
  167. package/examples/README.md +9 -0
  168. package/examples/codebase-guardian.yaml +22 -0
  169. package/examples/competitor-watch.yaml +18 -0
  170. package/examples/founder-sentinel.md +126 -0
  171. package/examples/founder-sentinel.yaml +27 -0
  172. package/package.json +62 -0
@@ -0,0 +1,149 @@
1
+ import axios from 'axios';
2
+ import { createHash } from 'crypto';
3
+ import fs from 'fs-extra';
4
+ import { join } from 'path';
5
+ import { homedir } from 'os';
6
+ function stateFilePath(url) {
7
+ const safe = url.replace(/[^a-zA-Z0-9]/g, '_').slice(0, 80);
8
+ return join(homedir(), '.demon', 'watcher-state', `http_${safe}.json`);
9
+ }
10
+ function hashBody(data) {
11
+ const str = typeof data === 'string' ? data : JSON.stringify(data);
12
+ return createHash('sha256').update(str).digest('hex');
13
+ }
14
+ function diffSummary(prev, next) {
15
+ if (!prev)
16
+ return 'Response content changed (first baseline set)';
17
+ return `Response hash changed: ${prev.slice(0, 8)} → ${next.slice(0, 8)}`;
18
+ }
19
+ export class HttpWatcher {
20
+ type = 'http';
21
+ config;
22
+ stateFile;
23
+ state = {
24
+ lastHash: null,
25
+ lastStatusCode: null,
26
+ lastCheckedAt: null,
27
+ };
28
+ stateLoaded = false;
29
+ constructor(config) {
30
+ this.config = config;
31
+ this.stateFile = stateFilePath(config.url ?? 'unknown');
32
+ }
33
+ async loadState() {
34
+ if (this.stateLoaded)
35
+ return;
36
+ try {
37
+ await fs.ensureDir(join(homedir(), '.demon', 'watcher-state'));
38
+ if (await fs.pathExists(this.stateFile)) {
39
+ this.state = await fs.readJson(this.stateFile);
40
+ }
41
+ }
42
+ catch {
43
+ // Start fresh if state is corrupt
44
+ }
45
+ this.stateLoaded = true;
46
+ }
47
+ async saveState() {
48
+ try {
49
+ await fs.writeJson(this.stateFile, this.state, { spaces: 2 });
50
+ }
51
+ catch {
52
+ // Non-fatal — worst case we re-baseline next cycle
53
+ }
54
+ }
55
+ async collect() {
56
+ const { url, headers } = this.config;
57
+ if (!url)
58
+ throw new Error('http watcher requires a "url" field');
59
+ await this.loadState();
60
+ const startTime = Date.now();
61
+ let responseData;
62
+ let statusCode;
63
+ let error;
64
+ try {
65
+ const response = await axios.get(url, {
66
+ headers: headers ?? {},
67
+ timeout: 30_000,
68
+ validateStatus: () => true,
69
+ });
70
+ statusCode = response.status;
71
+ responseData = response.data;
72
+ }
73
+ catch (err) {
74
+ statusCode = 0;
75
+ error = err.message;
76
+ }
77
+ const latencyMs = Date.now() - startTime;
78
+ const isError = statusCode >= 400 || statusCode === 0;
79
+ const currentHash = error ? null : hashBody(responseData);
80
+ const contentChanged = currentHash !== null && currentHash !== this.state.lastHash;
81
+ const statusChanged = this.state.lastStatusCode !== null && statusCode !== this.state.lastStatusCode;
82
+ // Update persisted state
83
+ const prevHash = this.state.lastHash;
84
+ this.state = {
85
+ lastHash: currentHash ?? this.state.lastHash,
86
+ lastStatusCode: statusCode,
87
+ lastCheckedAt: new Date().toISOString(),
88
+ };
89
+ await this.saveState();
90
+ // Only emit an observation when something is notable
91
+ const shouldReport = isError || contentChanged || statusChanged || prevHash === null;
92
+ if (!shouldReport) {
93
+ return []; // No change, no error — skip this cycle
94
+ }
95
+ let summary;
96
+ if (error) {
97
+ summary = `HTTP GET ${url} failed: ${error}`;
98
+ }
99
+ else if (isError) {
100
+ summary = `HTTP GET ${url} → ${statusCode} ERROR (${latencyMs}ms)`;
101
+ }
102
+ else if (prevHash === null) {
103
+ summary = `HTTP GET ${url} → ${statusCode} OK (${latencyMs}ms) — baseline recorded`;
104
+ }
105
+ else if (statusChanged) {
106
+ summary = `HTTP GET ${url} status changed: ${this.state.lastStatusCode} → ${statusCode} (${latencyMs}ms)`;
107
+ }
108
+ else {
109
+ summary =
110
+ `HTTP GET ${url} → ${statusCode} OK (${latencyMs}ms) — ` +
111
+ diffSummary(prevHash, currentHash);
112
+ }
113
+ return [
114
+ {
115
+ watcherType: this.type,
116
+ data: {
117
+ url,
118
+ statusCode,
119
+ latencyMs,
120
+ isOk: !isError,
121
+ isError,
122
+ contentChanged,
123
+ statusChanged,
124
+ currentHash: currentHash?.slice(0, 16) ?? null,
125
+ previousHash: prevHash?.slice(0, 16) ?? null,
126
+ error,
127
+ body: error ? undefined : truncateBody(responseData),
128
+ },
129
+ summary,
130
+ timestamp: new Date().toISOString(),
131
+ },
132
+ ];
133
+ }
134
+ }
135
+ function truncateBody(data) {
136
+ if (typeof data === 'string') {
137
+ return data.length > 2000 ? data.slice(0, 2000) + '... [truncated]' : data;
138
+ }
139
+ if (typeof data === 'object' && data !== null) {
140
+ const json = JSON.stringify(data);
141
+ if (json.length > 2000) {
142
+ // Return a truncated string representation rather than broken JSON
143
+ return json.slice(0, 2000) + '... [truncated]';
144
+ }
145
+ return data;
146
+ }
147
+ return data;
148
+ }
149
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/watchers/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { Watcher, WatcherConfig } from '../types.js';
2
+ export { createWatcher } from './base.js';
3
+ /**
4
+ * buildWatcher — convenience factory used by the daemon loop.
5
+ * Credentials are resolved inside each watcher from env/secrets/providers.
6
+ */
7
+ export declare function buildWatcher(config: WatcherConfig): Watcher;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/watchers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAKpD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAEzC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAe3D"}
@@ -0,0 +1,25 @@
1
+ import { buildGitHubWatcher } from './github.js';
2
+ import { HttpWatcher } from './http.js';
3
+ import { FilesystemWatcher } from './filesystem.js';
4
+ export { createWatcher } from './base.js';
5
+ /**
6
+ * buildWatcher — convenience factory used by the daemon loop.
7
+ * Credentials are resolved inside each watcher from env/secrets/providers.
8
+ */
9
+ export function buildWatcher(config) {
10
+ switch (config.type) {
11
+ case 'github_prs':
12
+ case 'github_ci':
13
+ case 'github_commits':
14
+ return buildGitHubWatcher(config, {});
15
+ case 'http':
16
+ return new HttpWatcher(config);
17
+ case 'filesystem':
18
+ return new FilesystemWatcher(config);
19
+ default: {
20
+ const _exhaustive = config.type;
21
+ throw new Error(`Unknown watcher type: ${String(_exhaustive)}`);
22
+ }
23
+ }
24
+ }
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/watchers/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAEzC;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,YAAY,CAAC;QAClB,KAAK,WAAW,CAAC;QACjB,KAAK,gBAAgB;YACnB,OAAO,kBAAkB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;QACvC,KAAK,MAAM;YACT,OAAO,IAAI,WAAW,CAAC,MAAM,CAAC,CAAA;QAChC,KAAK,YAAY;YACf,OAAO,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAA;QACtC,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,WAAW,GAAU,MAAM,CAAC,IAAI,CAAA;YACtC,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;QACjE,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ # Examples
2
+
3
+ Reference demons you can copy, fill in, and run.
4
+
5
+ | Example | Description |
6
+ |---|---|
7
+ | [codebase-guardian](./codebase-guardian.yaml) | Watches a repo's PRs and CI, flags coverage drops, large diffs, and obvious bugs before they merge |
8
+ | [competitor-watch](./competitor-watch.yaml) | Monitors competitor pages for pricing changes, new features, and announcements |
9
+ | [founder-sentinel](./founder-sentinel.yaml) | Watches all your active projects and surfaces stale PRs, broken builds, and degraded health — only when something actually warrants it ([guide](./founder-sentinel.md)) |
@@ -0,0 +1,22 @@
1
+ name: codebase-guardian
2
+ purpose: |
3
+ Keep this codebase healthy. Watch for PRs that drop test coverage,
4
+ introduce obvious bugs, or are too large to review properly. Flag
5
+ issues proactively before they merge. Track patterns over time.
6
+ watch:
7
+ - type: github_prs
8
+ repo: OWNER/REPO
9
+ - type: github_ci
10
+ repo: OWNER/REPO
11
+ interval: 15m
12
+ action_mode: dry_run
13
+ actions:
14
+ - provider: github
15
+ action: post_comment
16
+ on: pr
17
+ - provider: slack
18
+ action: send_message
19
+ channel: "#engineering"
20
+ - provider: log
21
+ action: info
22
+ model: claude-sonnet-4-6
@@ -0,0 +1,18 @@
1
+ name: competitor-watch
2
+ purpose: |
3
+ Monitor competitor websites for meaningful changes — new features,
4
+ pricing changes, product announcements. Surface only what matters.
5
+ watch:
6
+ - type: http
7
+ url: https://competitor.com/pricing
8
+ - type: http
9
+ url: https://competitor.com/changelog
10
+ interval: 1h
11
+ action_mode: dry_run
12
+ actions:
13
+ - provider: slack
14
+ action: send_message
15
+ channel: "#competitive-intel"
16
+ - provider: log
17
+ action: info
18
+ model: claude-sonnet-4-6
@@ -0,0 +1,126 @@
1
+ # founder-sentinel
2
+
3
+ A demon that watches all your active projects simultaneously and surfaces only what actually needs your attention — stale PRs, broken builds, degraded health endpoints, blocked work. It stays quiet when things are fine.
4
+
5
+ ---
6
+
7
+ ## What it does
8
+
9
+ You run multiple projects. Checking each one manually means either constant context switching or things slipping through. founder-sentinel runs on a 30-minute cycle, looks across all of them, and decides what matters right now.
10
+
11
+ It does not alert on every CI run or every commit. It flags things that have crossed a threshold: a PR open for 48+ hours with no review, a health endpoint that just started returning errors, a build that's been red across three consecutive runs. The judgment about what matters lives in the model, not in a ruleset you had to write upfront.
12
+
13
+ Over time, as it accumulates memory across runs, it gets better at knowing what's noise for your specific projects and what actually needs you.
14
+
15
+ ---
16
+
17
+ ## Setup
18
+
19
+ **1. Copy the YAML and fill in your repos and URLs**
20
+
21
+ ```yaml
22
+ watch:
23
+ - type: github_prs
24
+ repo: yourname/frontend-app
25
+ - type: github_ci
26
+ repo: yourname/frontend-app
27
+ - type: http
28
+ url: https://yourapp.com/health
29
+ - type: github_prs
30
+ repo: yourname/api-service
31
+ ```
32
+
33
+ Add as many `watch` entries as you have active projects. Each `type: http` entry should point to a health or status endpoint that returns a non-200 when something is wrong.
34
+
35
+ **2. Configure notifications**
36
+
37
+ The `notify` provider sends a system notification (macOS/Linux). If you want Slack instead, swap it:
38
+
39
+ ```yaml
40
+ actions:
41
+ - provider: slack
42
+ action: send_message
43
+ channel: "#founder-alerts"
44
+ - provider: log
45
+ action: info
46
+ ```
47
+
48
+ Keep `provider: log` as a secondary action — it gives you a local audit trail.
49
+
50
+ **3. Start in dry_run**
51
+
52
+ Leave `action_mode: dry_run` for the first week. In this mode the demon runs its full analysis every 30 minutes but only logs what it would have done. Nothing gets sent. You can review the log to see whether its judgment matches yours.
53
+
54
+ **4. When you're confident, switch to auto**
55
+
56
+ ```yaml
57
+ action_mode: auto
58
+ ```
59
+
60
+ That's it. From here it acts on its own judgments.
61
+
62
+ ---
63
+
64
+ ## The first week
65
+
66
+ Run it in `dry_run` and check the log once a day. Look for two failure modes:
67
+
68
+ **Too noisy** — it's flagging things you don't care about. Tighten your `watch` list, or add context to the `purpose` field to tell it what *not* to flag. Example:
69
+
70
+ ```yaml
71
+ purpose: |
72
+ Watch all active projects for things that genuinely need attention.
73
+ Flag stale PRs, blocked work, degraded health, and important changes —
74
+ but only when something actually warrants it. Learn what matters over time.
75
+ Surface the right thing at the right moment, not noise.
76
+
77
+ Do not flag PRs that are marked as draft. Do not flag CI failures
78
+ on feature branches — only flag main/master.
79
+ ```
80
+
81
+ **Missing things** — it let something slip that you wish it had caught. Add that class of watch to your `watch` list, or describe the pattern in `purpose` so the model knows to look for it.
82
+
83
+ After a week of the log matching your judgment, switch to `auto`.
84
+
85
+ ---
86
+
87
+ ## Example notifications
88
+
89
+ What you will actually receive once it's running:
90
+
91
+ > **[frontend-app]** PR #47 "Refactor auth flow" has been open 3 days with no review activity. Last CI run passed. Tagging for your attention.
92
+
93
+ > **[api-service]** Build has failed on main for the past 4 runs (2h 15m). Last green commit: a3f92c1. Three commits since then, all from the same author.
94
+
95
+ > **[yourapp.com/health]** Health endpoint returned 503 twice in the last two cycles. Previous 48 hours were clean. May be worth checking.
96
+
97
+ > **[frontend-app]** PR #51 merged while CI was red. Build is now fixed but this pattern has happened twice this week.
98
+
99
+ Notifications are batched per cycle. If nothing warrants attention in a given 30-minute window, nothing is sent.
100
+
101
+ ---
102
+
103
+ ## How it gets smarter over time
104
+
105
+ Each run, the demon writes a summary of what it observed and what it flagged to its memory store. On subsequent runs, it has context about:
106
+
107
+ - Which repos tend to have noisy CI that you don't care about
108
+ - Which PRs have been intentionally parked (it stops re-flagging them)
109
+ - What your normal baseline looks like for each health endpoint (so it can detect deviation, not just failure)
110
+ - Patterns across projects — e.g., if deploys on one service consistently break the health check on another
111
+
112
+ You do not configure any of this. It accumulates from observation. The longer it runs, the more it knows about your specific situation.
113
+
114
+ ---
115
+
116
+ ## Adjusting the interval
117
+
118
+ 30 minutes is a reasonable default for a founder context — frequent enough to catch things before they compound, infrequent enough not to burn API budget on noise.
119
+
120
+ If your projects are high-velocity (multiple deploys per day), drop it to `15m`. If you're in a slower phase, `1h` is fine.
121
+
122
+ ```yaml
123
+ interval: 15m # high-velocity sprint
124
+ interval: 30m # default
125
+ interval: 1h # maintenance mode
126
+ ```
@@ -0,0 +1,27 @@
1
+ name: founder-sentinel
2
+ purpose: |
3
+ Watch all active projects for things that genuinely need attention.
4
+ Flag stale PRs, blocked work, degraded health, and important changes —
5
+ but only when something actually warrants it. Learn what matters over time.
6
+ Surface the right thing at the right moment, not noise.
7
+
8
+ watch:
9
+ - type: github_prs
10
+ repo: OWNER/REPO1
11
+ - type: github_ci
12
+ repo: OWNER/REPO1
13
+ - type: http
14
+ url: https://YOUR-APP.com/health
15
+ - type: github_prs
16
+ repo: OWNER/REPO2
17
+
18
+ interval: 30m
19
+ action_mode: dry_run
20
+
21
+ actions:
22
+ - provider: notify
23
+ action: send
24
+ - provider: log
25
+ action: info
26
+
27
+ model: claude-sonnet-4-6
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@josharsh/demon-cli",
3
+ "version": "0.1.0",
4
+ "description": "AI-native daemon runtime — run persistent demons (the CLI host for @josharsh/demon)",
5
+ "keywords": ["ai", "agent", "daemon", "llm", "automation", "demon"],
6
+ "author": "Harsh <engineering@spotlyte.live>",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/josharsh/demon"
11
+ },
12
+ "homepage": "https://demon.josharsh.com",
13
+ "bugs": {
14
+ "url": "https://github.com/josharsh/demon/issues"
15
+ },
16
+ "engines": {
17
+ "node": ">=18.0.0"
18
+ },
19
+ "workspaces": ["packages/*"],
20
+ "files": ["dist", "README.md", "examples", "LICENSE"],
21
+ "type": "module",
22
+ "bin": { "demon": "./dist/index.js" },
23
+ "main": "./dist/index.js",
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "build:core": "npm run build -w @josharsh/demon",
27
+ "build:site": "npm run build -w @josharsh/demon && cp -R packages/core/dist/. site/core/",
28
+ "serve:site": "python3 -m http.server 8765 --directory site",
29
+ "dev": "tsx src/index.ts",
30
+ "test": "vitest run",
31
+ "test:core": "npm run test -w @josharsh/demon",
32
+ "test:watch": "vitest",
33
+ "typecheck": "tsc --noEmit",
34
+ "lint": "eslint src --ext .ts",
35
+ "prepublishOnly": "npm run build && npm test"
36
+ },
37
+ "dependencies": {
38
+ "@josharsh/demon": "^0.1.0",
39
+ "@octokit/rest": "^21.0.0",
40
+ "axios": "^1.7.0",
41
+ "chalk": "^5.3.0",
42
+ "cli-table3": "^0.6.5",
43
+ "commander": "^12.0.0",
44
+ "fs-extra": "^11.2.0",
45
+ "inquirer": "^10.0.0",
46
+ "js-yaml": "^4.1.0",
47
+ "ms": "^2.1.3",
48
+ "ora": "^8.0.0",
49
+ "pretty-ms": "^9.0.0",
50
+ "winston": "^3.14.0"
51
+ },
52
+ "devDependencies": {
53
+ "@types/fs-extra": "^11.0.4",
54
+ "@types/inquirer": "^9.0.7",
55
+ "@types/js-yaml": "^4.0.9",
56
+ "@types/ms": "^0.7.34",
57
+ "@types/node": "^22.0.0",
58
+ "tsx": "^4.19.0",
59
+ "typescript": "^5.6.0",
60
+ "vitest": "^2.1.0"
61
+ }
62
+ }