vessels 0.2.2 → 0.2.4

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 (2) hide show
  1. package/dist/index.js +85 -4
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -23,16 +23,39 @@ function writeConfig(config) {
23
23
  if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
24
24
  writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
25
25
  }
26
- function getToken() {
27
- return readConfig().access_token ?? null;
26
+ function isTokenExpired(token) {
27
+ try {
28
+ const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString());
29
+ return payload.exp * 1e3 < Date.now() + 6e4;
30
+ } catch {
31
+ return true;
32
+ }
33
+ }
34
+ async function getFreshToken() {
35
+ const config = readConfig();
36
+ if (!config.access_token) return null;
37
+ if (!isTokenExpired(config.access_token)) return config.access_token;
38
+ if (config.refresh_token) {
39
+ try {
40
+ const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
41
+ const { data } = await supabase.auth.refreshSession({ refresh_token: config.refresh_token });
42
+ if (data.session) {
43
+ writeConfig({ ...config, access_token: data.session.access_token, refresh_token: data.session.refresh_token });
44
+ return data.session.access_token;
45
+ }
46
+ } catch {
47
+ }
48
+ }
49
+ return null;
28
50
  }
29
51
  async function api(path, options = {}) {
30
- const token = getToken();
52
+ const token = await getFreshToken();
53
+ if (!token) throw new Error("Not logged in. Run: vessels login --email <email>");
31
54
  const res = await fetch(`${BASE_URL}${path}`, {
32
55
  ...options,
33
56
  headers: {
34
57
  "Content-Type": "application/json",
35
- ...token ? { Authorization: `Bearer ${token}` } : {},
58
+ Authorization: `Bearer ${token}`,
36
59
  ...options.headers ?? {}
37
60
  }
38
61
  });
@@ -184,6 +207,59 @@ async function cmdWebhooksToggle(id, active) {
184
207
  });
185
208
  console.log(`Webhook ${data.endpoint.id} ${data.endpoint.active ? "enabled" : "disabled"}.`);
186
209
  }
210
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
211
+ async function resolveVesselId(input) {
212
+ if (UUID_RE.test(input)) return input;
213
+ const data = await api("/api/v1/vessels?limit=200&archived=true");
214
+ const vessel = (data.vessels ?? []).find((v) => v.external_id === input);
215
+ if (!vessel) {
216
+ console.error(`Vessel not found: ${input}`);
217
+ process.exit(1);
218
+ }
219
+ return vessel.id;
220
+ }
221
+ async function cmdMessage(args) {
222
+ const flags = parseFlags(args);
223
+ if (!flags.vessel || !flags.message) {
224
+ console.error("Usage: vessels message --vessel <id> --message <text>");
225
+ process.exit(1);
226
+ }
227
+ const vesselId = await resolveVesselId(flags.vessel);
228
+ const since = new Date(Date.now() - 5e3).toISOString();
229
+ const data = await api(`/api/v1/vessels/${vesselId}/messages`, {
230
+ method: "POST",
231
+ body: JSON.stringify({ message: flags.message })
232
+ });
233
+ console.log(`
234
+ Sent.`);
235
+ console.log(` vessel_id ${vesselId}`);
236
+ console.log(` message_id ${data.message_id}`);
237
+ const logsData = await api(`/api/v1/webhooks/deliveries?since=${encodeURIComponent(since)}&limit=10`);
238
+ const deliveries = logsData.deliveries ?? [];
239
+ if (!deliveries.length) {
240
+ console.log("\nNo webhook deliveries \u2014 add an endpoint at vessels-two.vercel.app/settings/webhooks");
241
+ return;
242
+ }
243
+ console.log(`
244
+ Webhook deliveries (${deliveries.length}):
245
+ `);
246
+ for (const d of deliveries) {
247
+ const ok = d.response_status !== null && d.response_status >= 200 && d.response_status < 300;
248
+ const icon = ok ? "\u2713" : "\u2717";
249
+ const status = d.response_status ?? "no response";
250
+ const url = d.endpoint_url ?? "(deleted endpoint)";
251
+ const retry = d.attempt > 1 ? ` \xB7 attempt ${d.attempt}` : "";
252
+ const time = new Date(d.delivered_at).toLocaleTimeString([], { timeStyle: "medium" });
253
+ console.log(` ${icon} ${status} ${url}`);
254
+ console.log(` ${d.event_type}${retry} \xB7 ${time}`);
255
+ if (d.response_body) {
256
+ const body = d.response_body.length > 300 ? d.response_body.slice(0, 300) + "\u2026" : d.response_body;
257
+ console.log(`
258
+ ${body.split("\n").join("\n ")}`);
259
+ }
260
+ console.log();
261
+ }
262
+ }
187
263
  async function cmdPush(args) {
188
264
  const flags = parseFlags(args);
189
265
  const apiKey = flags.key ?? process.env.VESSELS_API_KEY;
@@ -233,6 +309,10 @@ Commands:
233
309
  vessels push --vessel <id> --message <text> --key <api_key>
234
310
  (--key can be omitted if VESSELS_API_KEY is set)
235
311
 
312
+ vessels message --vessel <id> --message <text>
313
+ Send a message as the logged-in user and print webhook delivery logs.
314
+ Accepts vessel UUID or external_id (e.g. booking-123).
315
+
236
316
  Agent/Claude Code setup (fully non-interactive):
237
317
  vessels login --email me@example.com
238
318
  # tell your agent the OTP from the email
@@ -291,6 +371,7 @@ Run: vessels help`);
291
371
  process.exit(1);
292
372
  }
293
373
  if (cmd === "push") return cmdPush([sub, ...rest].filter(Boolean));
374
+ if (cmd === "message") return cmdMessage([sub, ...rest].filter(Boolean));
294
375
  console.error(`Unknown command: ${cmd}
295
376
  Run: vessels help`);
296
377
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vessels",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Vessels CLI — manage your agent communication layer from the terminal",
5
5
  "type": "module",
6
6
  "bin": {