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.
- package/README.md +200 -90
- package/dist/daemon/client.d.ts +4 -0
- package/dist/daemon/client.d.ts.map +1 -0
- package/dist/daemon/client.js +44 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/lifecycle.d.ts +9 -0
- package/dist/daemon/lifecycle.d.ts.map +1 -0
- package/dist/daemon/lifecycle.js +118 -0
- package/dist/daemon/lifecycle.js.map +1 -0
- package/dist/daemon/process.d.ts +2 -0
- package/dist/daemon/process.d.ts.map +1 -0
- package/dist/daemon/process.js +171 -0
- package/dist/daemon/process.js.map +1 -0
- package/dist/daemon/run.d.ts +2 -0
- package/dist/daemon/run.d.ts.map +1 -0
- package/dist/daemon/run.js +5 -0
- package/dist/daemon/run.js.map +1 -0
- package/dist/daemon/store.d.ts +9 -0
- package/dist/daemon/store.d.ts.map +1 -0
- package/dist/daemon/store.js +53 -0
- package/dist/daemon/store.js.map +1 -0
- package/dist/daemon/types.d.ts +47 -0
- package/dist/daemon/types.d.ts.map +1 -0
- package/dist/daemon/types.js +10 -0
- package/dist/daemon/types.js.map +1 -0
- package/dist/index.js +105 -21
- package/dist/index.js.map +1 -1
- package/dist/lib/ascii.d.ts.map +1 -1
- package/dist/lib/ascii.js +14 -18
- package/dist/lib/ascii.js.map +1 -1
- package/dist/tools/unwatch.d.ts +7 -0
- package/dist/tools/unwatch.d.ts.map +1 -0
- package/dist/tools/unwatch.js +20 -0
- package/dist/tools/unwatch.js.map +1 -0
- package/dist/tools/watch.d.ts +14 -34
- package/dist/tools/watch.d.ts.map +1 -1
- package/dist/tools/watch.js +36 -170
- package/dist/tools/watch.js.map +1 -1
- package/dist/tools/watches.d.ts +2 -0
- package/dist/tools/watches.d.ts.map +1 -0
- package/dist/tools/watches.js +33 -0
- package/dist/tools/watches.js.map +1 -0
- package/dist/wizard/detector.d.ts +1 -1
- package/dist/wizard/detector.d.ts.map +1 -1
- package/dist/wizard/detector.js +14 -10
- package/dist/wizard/detector.js.map +1 -1
- package/dist/wizard/index.js +1 -1
- package/dist/wizard/index.js.map +1 -1
- package/dist/wizard/injector.js +1 -1
- package/dist/wizard/injector.js.map +1 -1
- package/dist/wizard/rules.d.ts +1 -1
- package/dist/wizard/rules.d.ts.map +1 -1
- package/dist/wizard/rules.js +24 -9
- package/dist/wizard/rules.js.map +1 -1
- package/dist/wizard/scanner.d.ts.map +1 -1
- package/dist/wizard/scanner.js +71 -0
- package/dist/wizard/scanner.js.map +1 -1
- package/package.json +3 -1
- package/src/daemon/client.ts +50 -0
- package/src/daemon/lifecycle.ts +126 -0
- package/src/daemon/process.ts +207 -0
- package/src/daemon/run.ts +6 -0
- package/src/daemon/store.ts +60 -0
- package/src/daemon/types.ts +41 -0
- package/src/index.ts +111 -22
- package/src/lib/ascii.ts +16 -20
- package/src/tools/unwatch.ts +26 -0
- package/src/tools/watch.ts +46 -212
- package/src/tools/watches.ts +37 -0
- package/src/wizard/detector.ts +15 -10
- package/src/wizard/index.ts +1 -1
- package/src/wizard/injector.ts +1 -1
- package/src/wizard/rules.ts +24 -9
- package/src/wizard/scanner.ts +74 -0
package/README.md
CHANGED
|
@@ -1,140 +1,250 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
4
|
+
█████╗ ██████╗ ██████╗██╗ ██╗███████╗██████╗
|
|
5
|
+
██╔══██╗██╔══██╗██╔════╝██║ ██║██╔════╝██╔══██╗
|
|
6
|
+
███████║██████╔╝██║ ███████║█████╗ ██████╔╝
|
|
7
|
+
██╔══██║██╔══██╗██║ ██╔══██║██╔══╝ ██╔══██╗
|
|
8
|
+
██║ ██║██║ ██║╚██████╗██║ ██║███████╗██║ ██║
|
|
9
|
+
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
|
|
7
10
|
```
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
**event intelligence layer for AI agents**
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
</div>
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25
|
+
Archer is that layer.
|
|
20
26
|
|
|
21
|
-
|
|
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
|
-
|
|
29
|
+
---
|
|
30
30
|
|
|
31
|
-
##
|
|
31
|
+
## install
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
npx archer
|
|
34
|
+
npx archer@latest
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Run this inside any project folder. Archer handles everything else automatically.
|
|
38
38
|
|
|
39
|
-
|
|
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
|
-
##
|
|
41
|
+
## how it works
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
+
---
|
|
56
54
|
|
|
57
|
-
|
|
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
|
-
|
|
57
|
+
```bash
|
|
58
|
+
# 1. run inside your project
|
|
59
|
+
npx archer-wizard@latest
|
|
66
60
|
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
87
|
+
**just talk to your agent:**
|
|
93
88
|
|
|
94
|
-
|
|
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": "
|
|
122
|
+
"watchId": "uuid",
|
|
100
123
|
"event": "table.insert",
|
|
101
124
|
"source": "supabase",
|
|
102
|
-
"firedAt": "
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
171
|
+
.env.local
|
|
172
|
+
.env
|
|
173
|
+
.env.development
|
|
174
|
+
.env.production
|
|
113
175
|
```
|
|
114
176
|
|
|
115
|
-
|
|
177
|
+
It recognizes all common aliases automatically:
|
|
116
178
|
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
+
*agents stop waiting. the world starts talking.*
|
|
139
249
|
|
|
140
|
-
|
|
250
|
+
</div>
|
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/daemon/process.ts"],"names":[],"mappings":"AA2HA,wBAAgB,kBAAkB,IAAI,IAAI,CAmFzC"}
|