archer-wizard 0.1.0 → 0.2.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 (74) hide show
  1. package/README.md +200 -90
  2. package/dist/daemon/client.d.ts +4 -0
  3. package/dist/daemon/client.d.ts.map +1 -0
  4. package/dist/daemon/client.js +44 -0
  5. package/dist/daemon/client.js.map +1 -0
  6. package/dist/daemon/lifecycle.d.ts +9 -0
  7. package/dist/daemon/lifecycle.d.ts.map +1 -0
  8. package/dist/daemon/lifecycle.js +118 -0
  9. package/dist/daemon/lifecycle.js.map +1 -0
  10. package/dist/daemon/process.d.ts +2 -0
  11. package/dist/daemon/process.d.ts.map +1 -0
  12. package/dist/daemon/process.js +171 -0
  13. package/dist/daemon/process.js.map +1 -0
  14. package/dist/daemon/run.d.ts +2 -0
  15. package/dist/daemon/run.d.ts.map +1 -0
  16. package/dist/daemon/run.js +5 -0
  17. package/dist/daemon/run.js.map +1 -0
  18. package/dist/daemon/store.d.ts +9 -0
  19. package/dist/daemon/store.d.ts.map +1 -0
  20. package/dist/daemon/store.js +53 -0
  21. package/dist/daemon/store.js.map +1 -0
  22. package/dist/daemon/types.d.ts +47 -0
  23. package/dist/daemon/types.d.ts.map +1 -0
  24. package/dist/daemon/types.js +10 -0
  25. package/dist/daemon/types.js.map +1 -0
  26. package/dist/index.js +105 -21
  27. package/dist/index.js.map +1 -1
  28. package/dist/lib/ascii.d.ts.map +1 -1
  29. package/dist/lib/ascii.js +14 -18
  30. package/dist/lib/ascii.js.map +1 -1
  31. package/dist/tools/unwatch.d.ts +7 -0
  32. package/dist/tools/unwatch.d.ts.map +1 -0
  33. package/dist/tools/unwatch.js +20 -0
  34. package/dist/tools/unwatch.js.map +1 -0
  35. package/dist/tools/watch.d.ts +14 -34
  36. package/dist/tools/watch.d.ts.map +1 -1
  37. package/dist/tools/watch.js +36 -170
  38. package/dist/tools/watch.js.map +1 -1
  39. package/dist/tools/watches.d.ts +2 -0
  40. package/dist/tools/watches.d.ts.map +1 -0
  41. package/dist/tools/watches.js +33 -0
  42. package/dist/tools/watches.js.map +1 -0
  43. package/dist/wizard/detector.d.ts +1 -1
  44. package/dist/wizard/detector.d.ts.map +1 -1
  45. package/dist/wizard/detector.js +14 -10
  46. package/dist/wizard/detector.js.map +1 -1
  47. package/dist/wizard/index.js +1 -1
  48. package/dist/wizard/index.js.map +1 -1
  49. package/dist/wizard/injector.js +1 -1
  50. package/dist/wizard/injector.js.map +1 -1
  51. package/dist/wizard/rules.d.ts +1 -1
  52. package/dist/wizard/rules.d.ts.map +1 -1
  53. package/dist/wizard/rules.js +24 -9
  54. package/dist/wizard/rules.js.map +1 -1
  55. package/dist/wizard/scanner.d.ts.map +1 -1
  56. package/dist/wizard/scanner.js +71 -0
  57. package/dist/wizard/scanner.js.map +1 -1
  58. package/package.json +3 -1
  59. package/src/daemon/client.ts +50 -0
  60. package/src/daemon/lifecycle.ts +126 -0
  61. package/src/daemon/process.ts +207 -0
  62. package/src/daemon/run.ts +6 -0
  63. package/src/daemon/store.ts +60 -0
  64. package/src/daemon/types.ts +41 -0
  65. package/src/index.ts +111 -22
  66. package/src/lib/ascii.ts +16 -20
  67. package/src/tools/unwatch.ts +26 -0
  68. package/src/tools/watch.ts +46 -212
  69. package/src/tools/watches.ts +37 -0
  70. package/src/wizard/detector.ts +15 -10
  71. package/src/wizard/index.ts +1 -1
  72. package/src/wizard/injector.ts +1 -1
  73. package/src/wizard/rules.ts +24 -9
  74. package/src/wizard/scanner.ts +74 -0
package/README.md CHANGED
@@ -1,140 +1,250 @@
1
- # 🏹 Archer
2
-
3
- **Event intelligence for AI agents.** One command connects your backend to Cursor, Claude Code, Windsurf, and any MCP-compatible agent.
1
+ <div align="center">
4
2
 
5
3
  ```
6
- npx archer
4
+ █████╗ ██████╗ ██████╗██╗ ██╗███████╗██████╗
5
+ ██╔══██╗██╔══██╗██╔════╝██║ ██║██╔════╝██╔══██╗
6
+ ███████║██████╔╝██║ ███████║█████╗ ██████╔╝
7
+ ██╔══██║██╔══██╗██║ ██╔══██║██╔══╝ ██╔══██╗
8
+ ██║ ██║██║ ██║╚██████╗██║ ██║███████╗██║ ██║
9
+ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
7
10
  ```
8
11
 
9
- ## What it does
12
+ **event intelligence layer for AI agents**
10
13
 
11
- Archer gives your AI agent a `watch` tool. The agent can monitor real-time events from your backend — database changes, auth events, webhooks — and react when conditions match.
14
+ </div>
12
15
 
13
- ```
14
- "Watch my users table for new signups from @company.com"
15
- ```
16
+ ---
17
+
18
+ ## what is Archer
19
+
20
+ Zapier was built for apps talking to apps.
21
+ **Archer was built for the world talking to AI agents.**
16
22
 
17
- Archer subscribes to the event, filters by condition, and POSTs to your webhook URL.
23
+ Every AI agent today is reactive. Cursor, Claude Code, opencode — they sit completely idle until you manually talk to them. Nobody built the layer that lets them feel what's happening in real time and act on their own.
18
24
 
19
- ## Supported Sources
25
+ Archer is that layer.
20
26
 
21
- | Source | Events | Status |
22
- |--------|--------|--------|
23
- | **Supabase** | `auth.signup`, `table.insert`, `table.update`, `table.delete` | ✅ Available |
24
- | **Firebase** | Auth, Firestore, Realtime DB | 🔜 Coming soon |
25
- | **Stripe** | Payments, subscriptions, invoices | 🔜 Coming soon |
26
- | **GitHub** | Push, PR, issues, deployments | 🔜 Coming soon |
27
- | **Custom Webhooks** | Any incoming webhook | 🔜 Coming soon |
27
+ You define a condition once in plain english. Archer watches your data sources 24/7. The moment that condition is true — your AI agent wakes up, already loaded with full context, and acts immediately. No prompting. No polling. No manual triggers.
28
28
 
29
- > Want a source added? [Open an issue →](https://github.com/archer-mcp/archer/issues)
29
+ ---
30
30
 
31
- ## Setup
31
+ ## install
32
32
 
33
33
  ```bash
34
- npx archer
34
+ npx archer@latest
35
35
  ```
36
36
 
37
- The interactive wizard will:
37
+ Run this inside any project folder. Archer handles everything else automatically.
38
38
 
39
- 1. **Scan** your project for credentials (`.env`, `.env.local`, etc.)
40
- 2. **Detect** installed AI agents (Cursor, Claude Code, Windsurf, etc.)
41
- 3. **Inject** the Archer MCP server into each agent's config
42
- 4. **Write** agent rules so the LLM knows how to use the tool
39
+ ---
43
40
 
44
- ## Supported Agents
41
+ ## how it works
45
42
 
46
- | Agent | Config Path | Status |
47
- |-------|------------|--------|
48
- | Cursor | `~/.cursor/mcp.json` | ✅ |
49
- | Claude Code | Platform-specific | ✅ |
50
- | Windsurf | `~/.codeium/windsurf/mcp_config.json` | ✅ |
51
- | OpenCode | `~/.config/opencode/config.json` | ✅ |
43
+ ```
44
+ your data source ──→ Archer watches 24/7 ──→ condition met ──→ agent fires
45
+ ```
52
46
 
53
- ## The `archer_watch` Tool
47
+ 1. **scans** your project for data source credentials automatically
48
+ 2. **detects** which AI agents you have installed on your machine
49
+ 3. **injects** itself into all their configs — one confirmation, no manual JSON
50
+ 4. **teaches** every agent when to call `archer.watch()` automatically via global rules
51
+ 5. your agent now has a nervous system — it feels the world and acts on its own
54
52
 
55
- ### Parameters
53
+ ---
56
54
 
57
- | Parameter | Type | Required | Description |
58
- |-----------|------|----------|-------------|
59
- | `source` | `string` | Yes | Event source (`"supabase"`, more coming) |
60
- | `event` | `string` | Yes | Event type (source-specific) |
61
- | `table` | `string` | Depends | Resource name (e.g. table for Supabase) |
62
- | `condition` | `string` | No | Filter like `"email ends with @gmail.com"` |
63
- | `webhookUrl` | `string` | Yes | URL to receive POST notifications |
55
+ ## quickstart
64
56
 
65
- ### Condition Operators
57
+ ```bash
58
+ # 1. run inside your project
59
+ npx archer-wizard@latest
66
60
 
67
- - `ends with` `"email ends with @company.com"`
68
- - `starts with` — `"name starts with John"`
69
- - `contains` — `"status contains active"`
70
- - `equals` — `"role equals admin"`
61
+ # 2. Archer scans your .env, finds your credentials, injects into your agents
71
62
 
72
- ### Example: Supabase
63
+ # 3. open your AI agent and say:
64
+ "watch my users table for new signups and fire https://your-webhook-url"
73
65
 
74
- ```json
75
- {
76
- "source": "supabase",
77
- "event": "auth.signup",
78
- "webhookUrl": "https://hooks.example.com/signups"
79
- }
66
+ # 4. insert a row in your database
67
+
68
+ # 5. your agent fires automatically
80
69
  ```
81
70
 
82
- ```json
83
- {
84
- "source": "supabase",
85
- "event": "table.insert",
86
- "table": "orders",
87
- "condition": "total equals 0",
88
- "webhookUrl": "https://hooks.example.com/free-orders"
89
- }
71
+ ---
72
+
73
+ ## the tool — `archer.watch()`
74
+
75
+ Once Archer is set up, your AI agent has access to this tool natively. You never call it directly — your agent calls it when you describe what you want.
76
+
77
+ ```typescript
78
+ archer.watch({
79
+ source: string, // data source
80
+ event: string, // what to listen for
81
+ table?: string, // table name for table events
82
+ condition?: string, // plain english condition (optional)
83
+ webhookUrl: string // where to fire when condition is met
84
+ })
90
85
  ```
91
86
 
92
- ### Webhook Payload
87
+ **just talk to your agent:**
93
88
 
94
- Every webhook POST follows the same envelope format, regardless of source:
89
+ ```
90
+ "watch my users table and fire https://your-webhook.com
91
+ when a new user signs up with a .edu email"
92
+ ```
93
+
94
+ ```
95
+ "watch my orders table for new inserts and notify https://your-webhook.com"
96
+ ```
97
+
98
+ ```
99
+ "fire https://your-webhook.com every time a row is deleted from sessions"
100
+ ```
101
+
102
+ ---
103
+
104
+ ## supported events (currently only supabase, p.s. we will add more soon)
105
+
106
+ | event | description |
107
+ |---|---|
108
+ | `auth.signup` | new user registers |
109
+ | `table.insert` | new row inserted into any table |
110
+ | `table.update` | existing row updated |
111
+ | `table.delete` | row deleted |
112
+
113
+ ---
114
+
115
+ ## webhook payload
116
+
117
+ Every time Archer fires, your agent receives this:
95
118
 
96
119
  ```json
97
120
  {
98
121
  "archer": {
99
- "watchId": "watch_a1b2c3d4",
122
+ "watchId": "uuid",
100
123
  "event": "table.insert",
101
124
  "source": "supabase",
102
- "firedAt": "2025-01-15T10:30:00.000Z"
125
+ "firedAt": "ISO timestamp"
103
126
  },
104
- "data": { ... }
127
+ "data": {
128
+ "id": "row-id",
129
+ "email": "user@university.edu",
130
+ "created_at": "timestamp"
131
+ }
105
132
  }
106
133
  ```
107
134
 
108
- ## Architecture
135
+ ---
136
+
137
+ ## supported agents
138
+
139
+ Archer auto-detects and injects into all of these:
140
+
141
+ | agent | status |
142
+ |---|---|
143
+ | Cursor | ✓ supported |
144
+ | Claude Code | ✓ supported |
145
+ | opencode | ✓ supported |
146
+ | Google Antigravity | ✓ supported |
147
+ | Windsurf | ✓ supported |
148
+
149
+ ---
150
+
151
+ ## supported data sources
152
+
153
+ Archer is built universal — any data source, any platform.
154
+
155
+ | source | status |
156
+ |---|---|
157
+ | Supabase | ✓ available now |
158
+ | GitHub | coming soon |
159
+ | Stripe | coming soon |
160
+ | Linear | coming soon |
161
+ | Vercel | coming soon |
162
+ | custom webhooks | coming soon |
163
+
164
+ ---
165
+
166
+ ## credentials
167
+
168
+ Archer scans these files automatically in priority order:
109
169
 
110
170
  ```
111
- npx archer → Interactive wizard (scan, detect, inject, configure)
112
- npx archer --mcp → MCP server mode (stdio transport, used by agents)
171
+ .env.local
172
+ .env
173
+ .env.development
174
+ .env.production
113
175
  ```
114
176
 
115
- The wizard injects this into each agent's config:
177
+ It recognizes all common aliases automatically:
116
178
 
117
- ```json
118
- {
119
- "mcpServers": {
120
- "archer": {
121
- "command": "npx",
122
- "args": ["-y", "archer", "--mcp"],
123
- "env": {
124
- "SUPABASE_URL": "...",
125
- "SUPABASE_SERVICE_ROLE_KEY": "..."
126
- }
127
- }
128
- }
129
- }
179
+ ```bash
180
+ # standard
181
+ SUPABASE_URL=https://yourproject.supabase.co
182
+ SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
183
+
184
+ # Next.js also recognized automatically
185
+ NEXT_PUBLIC_SUPABASE_URL=...
186
+
187
+ # Vite — also recognized automatically
188
+ VITE_SUPABASE_URL=...
189
+ ```
190
+
191
+ You never paste credentials manually. Archer finds them.
192
+
193
+ ---
194
+
195
+ ## requirements
196
+
197
+ - Node.js 18 or higher
198
+ - at least one supported AI agent installed
199
+ - a `.env` file with your data source credentials
200
+ - realtime enabled on tables you want to watch
201
+
202
+ ---
203
+
204
+ ## architecture
205
+
206
+ ```
207
+ developer's machine data source
208
+ ─────────────────── ───────────
209
+ npx archer-wizard@latest realtime channel
210
+ ↓ ↓
211
+ wizard scans .env Archer subscribes to changes 24/7
212
+ ↓ ↓
213
+ injects into agents condition matched → webhook fires
214
+ ↓ ↓
215
+ agent calls archer.watch() AI agent wakes up with full context
216
+ ```
217
+
218
+ No AI at runtime. Once a condition is defined it is pure logic — fast, cheap, reliable.
219
+
220
+ ---
221
+
222
+ ## what v1 does not include
223
+
224
+ - no dashboard
225
+ - no account required
226
+ - no sign in
227
+ - no API key
228
+ - no cloud service
229
+
230
+ Everything runs locally on your machine using your own credentials. The architecture is universal from day one — the simplicity is intentional.
231
+
232
+
233
+
234
+ ```bash
235
+ # Install from npm
236
+ npx archer-wizard@latest
237
+
238
+ # Or from source:
239
+ git clone https://github.com/amirlan-labs/archer-mcp
240
+ cd archer-mcp
241
+ npm install
242
+ npm run dev
130
243
  ```
131
244
 
132
- ## Requirements
133
245
 
134
- - Node.js 18+
135
- - At least one supported AI agent installed
136
- - Credentials for your event source (e.g. Supabase URL + service role key)
246
+ <div align="center">
137
247
 
138
- ## License
248
+ *agents stop waiting. the world starts talking.*
139
249
 
140
- MIT
250
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { IpcRequest, IpcResponse } from './types.js';
2
+ export declare function sendCommand(req: IpcRequest): Promise<IpcResponse>;
3
+ export declare function isDaemonRunning(): Promise<boolean>;
4
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/daemon/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI1D,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAgCjE;AAID,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAOxD"}
@@ -0,0 +1,44 @@
1
+ import net from 'node:net';
2
+ import { DAEMON_PORT, DAEMON_HOST } from './types.js';
3
+ // ─── Send Command to Daemon ─────────────────────────────────
4
+ export function sendCommand(req) {
5
+ return new Promise((resolve, reject) => {
6
+ const socket = net.createConnection({ host: DAEMON_HOST, port: DAEMON_PORT }, () => {
7
+ socket.write(JSON.stringify(req) + '\n');
8
+ });
9
+ let buffer = '';
10
+ socket.on('data', (data) => {
11
+ buffer += data.toString();
12
+ const newlineIdx = buffer.indexOf('\n');
13
+ if (newlineIdx !== -1) {
14
+ const line = buffer.slice(0, newlineIdx).trim();
15
+ socket.end();
16
+ try {
17
+ resolve(JSON.parse(line));
18
+ }
19
+ catch (err) {
20
+ reject(new Error(`invalid daemon response: ${line}`));
21
+ }
22
+ }
23
+ });
24
+ socket.on('error', (err) => {
25
+ reject(new Error(`daemon connection failed: ${err.message}`));
26
+ });
27
+ // Timeout after 5 seconds
28
+ socket.setTimeout(5_000, () => {
29
+ socket.destroy();
30
+ reject(new Error('daemon response timeout'));
31
+ });
32
+ });
33
+ }
34
+ // ─── Check if Daemon is Running ─────────────────────────────
35
+ export async function isDaemonRunning() {
36
+ try {
37
+ const res = await sendCommand({ type: 'ping' });
38
+ return res.ok && res.type === 'pong';
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ }
44
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/daemon/client.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGtD,+DAA+D;AAE/D,MAAM,UAAU,WAAW,CAAC,GAAe;IACzC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE;YACjF,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;QAEH,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1B,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;gBACtB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;gBAChD,MAAM,CAAC,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC;oBACH,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAgB,CAAC,CAAC;gBAC3C,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,MAAM,CAAC,IAAI,KAAK,CAAC,4BAA4B,IAAI,EAAE,CAAC,CAAC,CAAC;gBACxD,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACzB,MAAM,CAAC,IAAI,KAAK,CAAC,6BAA6B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAChE,CAAC,CAAC,CAAC;QAEH,0BAA0B;QAC1B,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE;YAC5B,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,+DAA+D;AAE/D,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAChD,OAAO,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ export declare function startDaemon(): Promise<{
2
+ pid: number;
3
+ alreadyRunning: boolean;
4
+ }>;
5
+ export declare function stopDaemon(): Promise<boolean>;
6
+ export declare function ensureDaemon(): Promise<{
7
+ pid: number;
8
+ }>;
9
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/daemon/lifecycle.ts"],"names":[],"mappings":"AASA,wBAAsB,WAAW,IAAI,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,CAAC,CAgErF;AAID,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAcnD;AAID,wBAAsB,YAAY,IAAI,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAG7D"}
@@ -0,0 +1,118 @@
1
+ import { spawn } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { PID_FILE, ARCHER_DIR, LOG_FILE } from './types.js';
6
+ import { isDaemonRunning } from './client.js';
7
+ // ─── Start Daemon ───────────────────────────────────────────
8
+ export async function startDaemon() {
9
+ // Check if already running
10
+ if (await isDaemonRunning()) {
11
+ const pid = readPid();
12
+ return { pid: pid ?? 0, alreadyRunning: true };
13
+ }
14
+ // Ensure directory exists
15
+ if (!fs.existsSync(ARCHER_DIR)) {
16
+ fs.mkdirSync(ARCHER_DIR, { recursive: true });
17
+ }
18
+ // Resolve the daemon entry script
19
+ // In compiled mode this is dist/daemon/process.js
20
+ // In dev mode with tsx this is src/daemon/process.ts
21
+ const thisFile = fileURLToPath(import.meta.url);
22
+ const thisDir = path.dirname(thisFile);
23
+ // Look for the daemon runner script
24
+ const daemonRunner = path.join(thisDir, 'run.js');
25
+ const daemonRunnerTs = path.join(thisDir, 'run.ts');
26
+ let cmd;
27
+ let args;
28
+ if (fs.existsSync(daemonRunner)) {
29
+ // Compiled mode
30
+ cmd = process.execPath; // node
31
+ args = [daemonRunner];
32
+ }
33
+ else if (fs.existsSync(daemonRunnerTs)) {
34
+ // Dev mode — use tsx
35
+ cmd = 'npx';
36
+ args = ['tsx', daemonRunnerTs];
37
+ }
38
+ else {
39
+ // Fallback: assume we're in dist/ and run process.js directly
40
+ const processJs = path.join(thisDir, 'process.js');
41
+ cmd = process.execPath;
42
+ args = [processJs, '--run-daemon'];
43
+ }
44
+ // Open log file for daemon output
45
+ const logFd = fs.openSync(LOG_FILE, 'a');
46
+ const child = spawn(cmd, args, {
47
+ detached: true,
48
+ stdio: ['ignore', logFd, logFd],
49
+ env: { ...process.env },
50
+ });
51
+ child.unref();
52
+ fs.closeSync(logFd);
53
+ const pid = child.pid ?? 0;
54
+ // Wait briefly for daemon to start, then verify
55
+ await sleep(500);
56
+ const running = await isDaemonRunning();
57
+ if (!running) {
58
+ // Give it another moment
59
+ await sleep(1000);
60
+ }
61
+ return { pid, alreadyRunning: false };
62
+ }
63
+ // ─── Stop Daemon ────────────────────────────────────────────
64
+ export async function stopDaemon() {
65
+ const pid = readPid();
66
+ if (pid === null)
67
+ return false;
68
+ try {
69
+ process.kill(pid, 'SIGTERM');
70
+ // Wait for it to die
71
+ await sleep(500);
72
+ return true;
73
+ }
74
+ catch {
75
+ // Process already dead — clean up PID file
76
+ try {
77
+ fs.unlinkSync(PID_FILE);
78
+ }
79
+ catch { }
80
+ return false;
81
+ }
82
+ }
83
+ // ─── Ensure Daemon is Running ───────────────────────────────
84
+ export async function ensureDaemon() {
85
+ const result = await startDaemon();
86
+ return { pid: result.pid };
87
+ }
88
+ // ─── Helpers ────────────────────────────────────────────────
89
+ function readPid() {
90
+ try {
91
+ if (!fs.existsSync(PID_FILE))
92
+ return null;
93
+ const raw = fs.readFileSync(PID_FILE, 'utf-8').trim();
94
+ const pid = parseInt(raw, 10);
95
+ if (isNaN(pid))
96
+ return null;
97
+ // Check if process is alive
98
+ try {
99
+ process.kill(pid, 0); // Signal 0 = existence check
100
+ return pid;
101
+ }
102
+ catch {
103
+ // Process is dead — clean up stale PID
104
+ try {
105
+ fs.unlinkSync(PID_FILE);
106
+ }
107
+ catch { }
108
+ return null;
109
+ }
110
+ }
111
+ catch {
112
+ return null;
113
+ }
114
+ }
115
+ function sleep(ms) {
116
+ return new Promise((resolve) => setTimeout(resolve, ms));
117
+ }
118
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/daemon/lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function startDaemonProcess(): void;
2
+ //# sourceMappingURL=process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/daemon/process.ts"],"names":[],"mappings":"AA2HA,wBAAgB,kBAAkB,IAAI,IAAI,CAmFzC"}