@phnx-labs/agents-cli 1.20.61 → 1.20.63
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/CHANGELOG.md +57 -0
- package/README.md +10 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/browser.js +13 -3
- package/dist/commands/exec.js +51 -1
- package/dist/commands/funnel.d.ts +5 -0
- package/dist/commands/funnel.js +62 -0
- package/dist/commands/hosts.js +42 -0
- package/dist/commands/repo.d.ts +4 -4
- package/dist/commands/repo.js +30 -19
- package/dist/commands/routines.js +73 -16
- package/dist/commands/sessions-sync.d.ts +1 -0
- package/dist/commands/sessions-sync.js +16 -2
- package/dist/commands/sessions.js +8 -1
- package/dist/commands/setup.js +9 -0
- package/dist/commands/ssh.js +72 -2
- package/dist/commands/sync-provision.d.ts +23 -0
- package/dist/commands/sync-provision.js +107 -0
- package/dist/commands/webhook.d.ts +9 -0
- package/dist/commands/webhook.js +93 -0
- package/dist/index.js +6 -2
- package/dist/lib/agents.d.ts +26 -0
- package/dist/lib/agents.js +100 -28
- package/dist/lib/browser/ipc.js +5 -4
- package/dist/lib/browser/profiles.d.ts +13 -0
- package/dist/lib/browser/profiles.js +17 -0
- package/dist/lib/browser/service.d.ts +12 -1
- package/dist/lib/browser/service.js +48 -13
- package/dist/lib/browser/sessions-list.d.ts +40 -0
- package/dist/lib/browser/sessions-list.js +190 -0
- package/dist/lib/commands.js +29 -0
- package/dist/lib/convert.d.ts +11 -0
- package/dist/lib/convert.js +22 -0
- package/dist/lib/daemon.js +2 -0
- package/dist/lib/devices/fleet.d.ts +62 -0
- package/dist/lib/devices/fleet.js +128 -0
- package/dist/lib/doctor-diff.js +17 -1
- package/dist/lib/funnel.d.ts +5 -0
- package/dist/lib/funnel.js +23 -0
- package/dist/lib/git.d.ts +21 -5
- package/dist/lib/git.js +64 -14
- package/dist/lib/goose-commands.d.ts +41 -0
- package/dist/lib/goose-commands.js +176 -0
- package/dist/lib/hooks.js +134 -0
- package/dist/lib/hosts/credentials.d.ts +28 -0
- package/dist/lib/hosts/credentials.js +48 -0
- package/dist/lib/hosts/dispatch.d.ts +25 -0
- package/dist/lib/hosts/dispatch.js +68 -2
- package/dist/lib/hosts/passthrough.d.ts +13 -10
- package/dist/lib/hosts/passthrough.js +119 -29
- package/dist/lib/mailbox-gc.js +4 -16
- package/dist/lib/migrate.d.ts +12 -0
- package/dist/lib/migrate.js +55 -1
- package/dist/lib/permissions.d.ts +15 -0
- package/dist/lib/permissions.js +99 -0
- package/dist/lib/plugins.d.ts +20 -0
- package/dist/lib/plugins.js +118 -0
- package/dist/lib/resources/permissions.js +2 -0
- package/dist/lib/routines.d.ts +29 -10
- package/dist/lib/routines.js +47 -15
- package/dist/lib/session/active.d.ts +8 -1
- package/dist/lib/session/active.js +1 -0
- package/dist/lib/session/parse.js +12 -0
- package/dist/lib/session/state.d.ts +33 -0
- package/dist/lib/session/state.js +40 -0
- package/dist/lib/session/sync/agents.d.ts +2 -0
- package/dist/lib/session/sync/agents.js +39 -1
- package/dist/lib/session/sync/config.d.ts +8 -0
- package/dist/lib/session/sync/config.js +6 -1
- package/dist/lib/session/sync/provision.d.ts +49 -0
- package/dist/lib/session/sync/provision.js +91 -0
- package/dist/lib/session/sync/sync.d.ts +3 -0
- package/dist/lib/session/sync/sync.js +26 -6
- package/dist/lib/session/sync/transcript-crypto.d.ts +77 -0
- package/dist/lib/session/sync/transcript-crypto.js +147 -0
- package/dist/lib/staleness/detectors/permissions.js +23 -0
- package/dist/lib/staleness/detectors/subagents.js +36 -0
- package/dist/lib/staleness/writers/commands.js +7 -0
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/staleness/writers/subagents.js +22 -3
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +11 -0
- package/dist/lib/state.d.ts +10 -2
- package/dist/lib/state.js +14 -2
- package/dist/lib/subagents.d.ts +32 -0
- package/dist/lib/subagents.js +238 -0
- package/dist/lib/triggers/webhook.d.ts +70 -27
- package/dist/lib/triggers/webhook.js +264 -43
- package/package.json +1 -1
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Public webhook trigger receiver for routines.
|
|
3
3
|
*
|
|
4
4
|
* A routine may declare a `trigger` block instead of (or alongside) a cron
|
|
5
5
|
* `schedule` (see `JobConfig.trigger` in `../routines.ts`). This module turns
|
|
6
|
-
*
|
|
7
|
-
* dispatches those routines through the exact same path a cron fire uses
|
|
6
|
+
* incoming GitHub or Linear webhooks into the set of routines they should fire,
|
|
7
|
+
* and dispatches those routines through the exact same path a cron fire uses
|
|
8
8
|
* (`executeJobDetached`).
|
|
9
9
|
*
|
|
10
|
-
* The matching logic
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* The matching logic is pure, so it can be unit-tested without a daemon or HTTP
|
|
11
|
+
* server. The listener adds the public-ingress requirements: raw-body HMAC
|
|
12
|
+
* verification, idempotency, source allow-listing, and rate limiting.
|
|
13
13
|
*/
|
|
14
|
+
import * as crypto from 'crypto';
|
|
14
15
|
import * as http from 'http';
|
|
15
|
-
import {
|
|
16
|
+
import { jobRunsOnThisDevice, listJobs } from '../routines.js';
|
|
16
17
|
import { executeJobDetached } from '../runner.js';
|
|
17
18
|
/** Read `repository.full_name` (`owner/name`) from a webhook payload, if present. */
|
|
18
19
|
export function webhookRepo(payload) {
|
|
@@ -59,10 +60,30 @@ export function webhookBranches(event, payload) {
|
|
|
59
60
|
}
|
|
60
61
|
return [...branches];
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
function linearAction(payload) {
|
|
64
|
+
return typeof payload.action === 'string' ? payload.action : null;
|
|
65
|
+
}
|
|
66
|
+
function linearTeamKey(payload) {
|
|
67
|
+
const data = payload.data;
|
|
68
|
+
const identifier = data?.identifier;
|
|
69
|
+
if (typeof identifier === 'string') {
|
|
70
|
+
const match = /^([A-Z][A-Z0-9]*)-\d+$/.exec(identifier);
|
|
71
|
+
if (match)
|
|
72
|
+
return match[1];
|
|
73
|
+
}
|
|
74
|
+
const team = data?.team;
|
|
75
|
+
return typeof team?.key === 'string' ? team.key : null;
|
|
76
|
+
}
|
|
77
|
+
function linearLabels(payload) {
|
|
78
|
+
const data = payload.data;
|
|
79
|
+
const labels = data?.labels;
|
|
80
|
+
const nodes = Array.isArray(labels?.nodes) ? labels.nodes : [];
|
|
81
|
+
return nodes
|
|
82
|
+
.map((n) => n.name)
|
|
83
|
+
.filter((n) => typeof n === 'string' && n.length > 0);
|
|
84
|
+
}
|
|
85
|
+
function githubTriggerMatches(trigger, webhook) {
|
|
86
|
+
if (webhook.source !== 'github')
|
|
66
87
|
return false;
|
|
67
88
|
if (trigger.event !== webhook.event)
|
|
68
89
|
return false;
|
|
@@ -78,15 +99,51 @@ export function jobMatchesWebhook(job, webhook) {
|
|
|
78
99
|
}
|
|
79
100
|
return true;
|
|
80
101
|
}
|
|
102
|
+
function linearTriggerMatches(trigger, webhook) {
|
|
103
|
+
if (webhook.source !== 'linear')
|
|
104
|
+
return false;
|
|
105
|
+
if (trigger.event !== webhook.event)
|
|
106
|
+
return false;
|
|
107
|
+
if (trigger.action && linearAction(webhook.payload) !== trigger.action)
|
|
108
|
+
return false;
|
|
109
|
+
if (trigger.teamKey && linearTeamKey(webhook.payload) !== trigger.teamKey)
|
|
110
|
+
return false;
|
|
111
|
+
if (trigger.label) {
|
|
112
|
+
const expected = trigger.label.toLowerCase();
|
|
113
|
+
if (!linearLabels(webhook.payload).some((name) => name.toLowerCase() === expected))
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
/** True when a single job's trigger matches the given webhook. Pure. */
|
|
119
|
+
export function jobMatchesWebhook(job, webhook) {
|
|
120
|
+
const trigger = job.trigger;
|
|
121
|
+
if (!trigger)
|
|
122
|
+
return false;
|
|
123
|
+
if (trigger.type === 'github_event')
|
|
124
|
+
return githubTriggerMatches(trigger, webhook);
|
|
125
|
+
if (trigger.type === 'linear_event')
|
|
126
|
+
return linearTriggerMatches(trigger, webhook);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
81
129
|
/**
|
|
82
130
|
* Pure matcher: given a set of jobs and an incoming webhook, return the jobs
|
|
83
|
-
* whose `trigger` matches
|
|
84
|
-
*
|
|
85
|
-
* time-based jobs are unaffected by webhook delivery.
|
|
131
|
+
* whose `trigger` matches. Jobs without a trigger (schedule-only routines) are
|
|
132
|
+
* never selected — proving time-based jobs are unaffected by webhook delivery.
|
|
86
133
|
*/
|
|
87
134
|
export function matchJobsToWebhook(jobs, webhook) {
|
|
88
135
|
return jobs.filter((job) => job.enabled !== false && jobRunsOnThisDevice(job) && jobMatchesWebhook(job, webhook));
|
|
89
136
|
}
|
|
137
|
+
export class WebhookDispatchError extends Error {
|
|
138
|
+
fired;
|
|
139
|
+
failures;
|
|
140
|
+
constructor(message, fired, failures) {
|
|
141
|
+
super(message);
|
|
142
|
+
this.fired = fired;
|
|
143
|
+
this.failures = failures;
|
|
144
|
+
this.name = 'WebhookDispatchError';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
90
147
|
/**
|
|
91
148
|
* Match an incoming webhook against the persisted routines and fire each match
|
|
92
149
|
* through the cron dispatch path. Returns one entry per fired job.
|
|
@@ -94,47 +151,211 @@ export function matchJobsToWebhook(jobs, webhook) {
|
|
|
94
151
|
export async function fireWebhookJobs(webhook, options = {}) {
|
|
95
152
|
const jobs = options.jobs ?? listJobs();
|
|
96
153
|
const dispatch = options.dispatch ?? executeJobDetached;
|
|
154
|
+
const skipJobNames = options.skipJobNames ?? new Set();
|
|
97
155
|
const matched = matchJobsToWebhook(jobs, webhook);
|
|
98
156
|
const fired = [];
|
|
157
|
+
const failures = [];
|
|
99
158
|
for (const job of matched) {
|
|
100
|
-
|
|
101
|
-
|
|
159
|
+
if (skipJobNames.has(job.name))
|
|
160
|
+
continue;
|
|
161
|
+
try {
|
|
162
|
+
const meta = await dispatch(job);
|
|
163
|
+
const firedJob = { jobName: job.name, runId: meta.runId };
|
|
164
|
+
fired.push(firedJob);
|
|
165
|
+
options.onJobFired?.(job, firedJob);
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
failures.push({ jobName: job.name, error: err });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (failures.length > 0) {
|
|
172
|
+
throw new WebhookDispatchError(`failed to dispatch ${failures.length} webhook routine(s): ${failures.map((f) => f.jobName).join(', ')}`, fired, failures);
|
|
102
173
|
}
|
|
103
174
|
return fired;
|
|
104
175
|
}
|
|
176
|
+
function header(headers, name) {
|
|
177
|
+
const value = headers[name.toLowerCase()];
|
|
178
|
+
if (Array.isArray(value))
|
|
179
|
+
return value[0];
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
function timingSafeHexEqual(received, expected) {
|
|
183
|
+
if (!received || !/^[a-f0-9]+$/i.test(received))
|
|
184
|
+
return false;
|
|
185
|
+
const a = Buffer.from(received, 'hex');
|
|
186
|
+
const b = Buffer.from(expected, 'hex');
|
|
187
|
+
return a.length === b.length && crypto.timingSafeEqual(a, b);
|
|
188
|
+
}
|
|
189
|
+
function hmacHex(secret, rawBody) {
|
|
190
|
+
return crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
|
|
191
|
+
}
|
|
192
|
+
export function verifyGithubSignature(headers, rawBody, secret) {
|
|
193
|
+
const received = header(headers, 'x-hub-signature-256');
|
|
194
|
+
const signature = received?.startsWith('sha256=') ? received.slice('sha256='.length) : undefined;
|
|
195
|
+
return timingSafeHexEqual(signature, hmacHex(secret, rawBody));
|
|
196
|
+
}
|
|
197
|
+
export function verifyLinearSignature(headers, rawBody, secret) {
|
|
198
|
+
return timingSafeHexEqual(header(headers, 'linear-signature'), hmacHex(secret, rawBody));
|
|
199
|
+
}
|
|
200
|
+
export function verifyLinearTimestamp(payload, now = Date.now(), toleranceMs = 60_000) {
|
|
201
|
+
const ts = payload.webhookTimestamp;
|
|
202
|
+
return typeof ts === 'number' && Math.abs(now - ts) <= toleranceMs;
|
|
203
|
+
}
|
|
204
|
+
export function createMemoryDeliveryStore(maxEntries = 1000) {
|
|
205
|
+
const seen = new Map();
|
|
206
|
+
const touch = (id) => {
|
|
207
|
+
let current = seen.get(id);
|
|
208
|
+
if (!current) {
|
|
209
|
+
current = { complete: false, jobs: new Set(), updatedAt: Date.now() };
|
|
210
|
+
seen.set(id, current);
|
|
211
|
+
}
|
|
212
|
+
current.updatedAt = Date.now();
|
|
213
|
+
while (seen.size > maxEntries) {
|
|
214
|
+
let oldestId = null;
|
|
215
|
+
let oldestAt = Number.POSITIVE_INFINITY;
|
|
216
|
+
for (const [key, value] of seen) {
|
|
217
|
+
if (value.updatedAt < oldestAt) {
|
|
218
|
+
oldestAt = value.updatedAt;
|
|
219
|
+
oldestId = key;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (!oldestId)
|
|
223
|
+
break;
|
|
224
|
+
seen.delete(oldestId);
|
|
225
|
+
}
|
|
226
|
+
return current;
|
|
227
|
+
};
|
|
228
|
+
return {
|
|
229
|
+
seen: (id) => seen.get(id)?.complete === true,
|
|
230
|
+
mark: (id) => {
|
|
231
|
+
touch(id).complete = true;
|
|
232
|
+
},
|
|
233
|
+
completedJobs: (id) => new Set(seen.get(id)?.jobs ?? []),
|
|
234
|
+
markJob: (id, jobName) => {
|
|
235
|
+
touch(id).jobs.add(jobName);
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
export function createMemoryRateLimiter(limit, windowMs) {
|
|
240
|
+
const buckets = new Map();
|
|
241
|
+
return {
|
|
242
|
+
take: (key) => {
|
|
243
|
+
const now = Date.now();
|
|
244
|
+
const current = buckets.get(key);
|
|
245
|
+
if (!current || now >= current.resetAt) {
|
|
246
|
+
buckets.set(key, { resetAt: now + windowMs, count: 1 });
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
if (current.count >= limit)
|
|
250
|
+
return false;
|
|
251
|
+
current.count += 1;
|
|
252
|
+
return true;
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function deliveryId(source, headers, rawBody) {
|
|
257
|
+
const named = source === 'github'
|
|
258
|
+
? header(headers, 'x-github-delivery')
|
|
259
|
+
: header(headers, 'linear-delivery');
|
|
260
|
+
return `${source}:${named ?? crypto.createHash('sha256').update(rawBody).digest('hex')}`;
|
|
261
|
+
}
|
|
262
|
+
function sourceFromPath(pathname) {
|
|
263
|
+
const match = /^\/hooks\/(github|linear)\/?$/.exec(pathname ?? '');
|
|
264
|
+
return match ? match[1] : null;
|
|
265
|
+
}
|
|
266
|
+
async function readRawBody(req, maxBytes) {
|
|
267
|
+
const chunks = [];
|
|
268
|
+
let total = 0;
|
|
269
|
+
for await (const chunk of req) {
|
|
270
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
271
|
+
total += buf.length;
|
|
272
|
+
if (total > maxBytes)
|
|
273
|
+
throw new Error(`payload exceeds ${maxBytes} bytes`);
|
|
274
|
+
chunks.push(buf);
|
|
275
|
+
}
|
|
276
|
+
return Buffer.concat(chunks);
|
|
277
|
+
}
|
|
105
278
|
/**
|
|
106
|
-
* Start a
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* the
|
|
279
|
+
* Start a localhost-bound receiver. It accepts only:
|
|
280
|
+
* POST /hooks/github with X-Hub-Signature-256
|
|
281
|
+
* POST /hooks/linear with Linear-Signature + fresh webhookTimestamp
|
|
282
|
+
*
|
|
283
|
+
* Returns the underlying server so callers can `close()` it.
|
|
111
284
|
*/
|
|
112
|
-
export function startWebhookServer(options
|
|
285
|
+
export function startWebhookServer(options) {
|
|
286
|
+
const deliveryStore = options.deliveryStore ?? createMemoryDeliveryStore();
|
|
287
|
+
const rateLimiter = options.rateLimiter ?? createMemoryRateLimiter(options.rateLimitPerMinute ?? 60, 60_000);
|
|
288
|
+
const maxBodyBytes = options.maxBodyBytes ?? 1024 * 1024;
|
|
113
289
|
const server = http.createServer((req, res) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
290
|
+
void (async () => {
|
|
291
|
+
if (req.method !== 'POST') {
|
|
292
|
+
res.writeHead(405, { 'content-type': 'text/plain' });
|
|
293
|
+
res.end('method not allowed');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const source = sourceFromPath(req.url?.split('?')[0]);
|
|
297
|
+
if (!source) {
|
|
298
|
+
res.writeHead(404, { 'content-type': 'text/plain' });
|
|
299
|
+
res.end('not found');
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const secret = options.secrets[source];
|
|
303
|
+
if (!secret) {
|
|
304
|
+
res.writeHead(503, { 'content-type': 'application/json' });
|
|
305
|
+
res.end(JSON.stringify({ ok: false, error: `missing ${source} webhook secret` }));
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
const rawBody = await readRawBody(req, maxBodyBytes);
|
|
310
|
+
const valid = source === 'github'
|
|
311
|
+
? verifyGithubSignature(req.headers, rawBody, secret)
|
|
312
|
+
: verifyLinearSignature(req.headers, rawBody, secret);
|
|
313
|
+
if (!valid) {
|
|
314
|
+
res.writeHead(401, { 'content-type': 'application/json' });
|
|
315
|
+
res.end(JSON.stringify({ ok: false, error: 'invalid signature' }));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const id = deliveryId(source, req.headers, rawBody);
|
|
319
|
+
if (deliveryStore.seen(id)) {
|
|
129
320
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
130
|
-
res.end(JSON.stringify({ ok: true,
|
|
321
|
+
res.end(JSON.stringify({ ok: true, duplicate: true, fired: [] }));
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const payload = rawBody.length > 0 ? JSON.parse(rawBody.toString('utf-8')) : {};
|
|
325
|
+
if (source === 'linear' && !verifyLinearTimestamp(payload)) {
|
|
326
|
+
res.writeHead(401, { 'content-type': 'application/json' });
|
|
327
|
+
res.end(JSON.stringify({ ok: false, error: 'stale linear webhook timestamp' }));
|
|
328
|
+
return;
|
|
131
329
|
}
|
|
132
|
-
|
|
133
|
-
res.writeHead(
|
|
134
|
-
res.end(JSON.stringify({ ok: false, error:
|
|
330
|
+
if (!rateLimiter.take(source)) {
|
|
331
|
+
res.writeHead(429, { 'content-type': 'application/json' });
|
|
332
|
+
res.end(JSON.stringify({ ok: false, error: 'rate limit exceeded' }));
|
|
333
|
+
return;
|
|
135
334
|
}
|
|
136
|
-
|
|
137
|
-
|
|
335
|
+
const webhook = {
|
|
336
|
+
source,
|
|
337
|
+
event: source === 'github' ? (header(req.headers, 'x-github-event') ?? '') : String(payload.type ?? ''),
|
|
338
|
+
payload,
|
|
339
|
+
};
|
|
340
|
+
const fireOptions = options.fire ?? {};
|
|
341
|
+
const fired = await fireWebhookJobs(webhook, {
|
|
342
|
+
...fireOptions,
|
|
343
|
+
skipJobNames: deliveryStore.completedJobs(id),
|
|
344
|
+
onJobFired: (job, firedJob) => {
|
|
345
|
+
deliveryStore.markJob(id, job.name);
|
|
346
|
+
fireOptions.onJobFired?.(job, firedJob);
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
deliveryStore.mark(id);
|
|
350
|
+
options.onDelivery?.(webhook, fired);
|
|
351
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
352
|
+
res.end(JSON.stringify({ ok: true, fired: fired.map((f) => f.jobName), runs: fired }));
|
|
353
|
+
}
|
|
354
|
+
catch (err) {
|
|
355
|
+
res.writeHead(400, { 'content-type': 'application/json' });
|
|
356
|
+
res.end(JSON.stringify({ ok: false, error: err.message }));
|
|
357
|
+
}
|
|
358
|
+
})();
|
|
138
359
|
});
|
|
139
360
|
server.listen(options.port ?? 0, options.host ?? '127.0.0.1');
|
|
140
361
|
return server;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.63",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|