@codejeet/oadm 0.0.1 → 0.0.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.
package/dist/cli.js CHANGED
@@ -3,8 +3,9 @@ import { Command } from 'commander';
3
3
  import chalk from 'chalk';
4
4
  import { readConfig, writeConfig } from './config.js';
5
5
  import { getJson, postJson } from './http.js';
6
+ import pkg from '../package.json' with { type: 'json' };
6
7
  const program = new Command();
7
- program.name('oadm').description('OADM Inbox CLI').version('0.0.1');
8
+ program.name('oadm').description('OADM Inbox CLI').version(pkg.version);
8
9
  program
9
10
  .option('--api <url>', 'API base URL (or set OADM_API_URL)', '')
10
11
  .hook('preAction', (thisCommand) => {
@@ -21,7 +22,11 @@ program
21
22
  .requiredOption('--password <password>')
22
23
  .action(async (opts) => {
23
24
  const cfg = readConfig();
24
- await postJson(`${cfg.apiUrl}/v1/register`, { name: opts.name, password: opts.password, inviteCode: process.env.OADM_INVITE_CODE });
25
+ await postJson(`${cfg.apiUrl}/v1/register`, {
26
+ name: opts.name,
27
+ password: opts.password,
28
+ inviteCode: process.env.OADM_INVITE_CODE,
29
+ });
25
30
  console.log(chalk.green('✓ registered'));
26
31
  console.log('Next: oadm login --name <name> --password <pw>');
27
32
  });
@@ -31,7 +36,10 @@ program
31
36
  .requiredOption('--password <password>')
32
37
  .action(async (opts) => {
33
38
  const cfg = readConfig();
34
- const data = await postJson(`${cfg.apiUrl}/v1/login`, { name: opts.name, password: opts.password });
39
+ const data = await postJson(`${cfg.apiUrl}/v1/login`, {
40
+ name: opts.name,
41
+ password: opts.password,
42
+ });
35
43
  cfg.name = opts.name;
36
44
  cfg.token = data.token;
37
45
  writeConfig(cfg);
@@ -51,22 +59,36 @@ program
51
59
  program
52
60
  .command('inbox')
53
61
  .option('--unread', 'Only unread', false)
62
+ .option('--sent', 'Show sent messages (outbox)', false)
63
+ .option('--all', 'Show both received and sent', false)
64
+ .option('--since <timestamp>', 'Only messages since timestamp (ISO 8601 or unix)', '')
54
65
  .option('--json', 'JSON output', false)
55
66
  .option('--ack', 'Ack returned messages', false)
56
67
  .action(async (opts) => {
57
68
  const cfg = readConfig();
58
69
  if (!cfg.token)
59
70
  throw new Error('not_logged_in');
71
+ if (opts.sent && opts.all)
72
+ throw new Error('conflicting_flags_sent_all');
60
73
  const q = new URLSearchParams();
61
74
  if (opts.unread)
62
75
  q.set('unread', '1');
76
+ if (opts.sent)
77
+ q.set('sent', '1');
78
+ if (opts.all)
79
+ q.set('all', '1');
80
+ if (opts.since)
81
+ q.set('since', opts.since);
63
82
  const data = await getJson(`${cfg.apiUrl}/v1/messages/inbox?${q.toString()}`, cfg.token);
64
83
  if (opts.json) {
65
84
  console.log(JSON.stringify(data, null, 2));
66
85
  }
67
86
  else {
68
87
  for (const m of data.messages) {
69
- console.log(`${m.id} from:${m.fromName} ${new Date(m.createdAt).toLocaleString()}`);
88
+ const direction = m.direction ?? (opts.sent ? 'out' : 'in');
89
+ const peer = direction === 'out' ? `to:${m.toName}` : `from:${m.fromName}`;
90
+ const ackedAt = m.ackedAt ? ` acked:${new Date(m.ackedAt).toLocaleString()}` : '';
91
+ console.log(`${m.id} ${peer} ${new Date(m.createdAt).toLocaleString()}${direction === 'out' ? ackedAt : ''}`);
70
92
  console.log(m.text);
71
93
  console.log('---');
72
94
  }
@@ -75,6 +97,8 @@ program
75
97
  }
76
98
  if (opts.ack) {
77
99
  for (const m of data.messages) {
100
+ if (m.direction === 'out')
101
+ continue;
78
102
  await postJson(`${cfg.apiUrl}/v1/messages/ack/${m.id}`, {}, cfg.token);
79
103
  }
80
104
  }
@@ -89,6 +113,48 @@ program
89
113
  await postJson(`${cfg.apiUrl}/v1/messages/ack/${msgId}`, {}, cfg.token);
90
114
  console.log(chalk.green('✓ acked'), msgId);
91
115
  });
116
+ // Webhooks
117
+ program
118
+ .command('webhook:create')
119
+ .requiredOption('--url <url>')
120
+ .action(async (opts) => {
121
+ const cfg = readConfig();
122
+ if (!cfg.token)
123
+ throw new Error('not_logged_in');
124
+ // Server generates the secret. Do NOT send user secret.
125
+ const data = await postJson(`${cfg.apiUrl}/v1/webhooks`, { url: opts.url }, cfg.token);
126
+ console.log(chalk.green('✓ webhook created'), data.webhook.id);
127
+ if (data.secret)
128
+ console.log('secret:', data.secret);
129
+ });
130
+ program
131
+ .command('webhook:list')
132
+ .option('--json', 'JSON output', false)
133
+ .action(async (opts) => {
134
+ const cfg = readConfig();
135
+ if (!cfg.token)
136
+ throw new Error('not_logged_in');
137
+ const data = await getJson(`${cfg.apiUrl}/v1/webhooks`, cfg.token);
138
+ if (opts.json) {
139
+ console.log(JSON.stringify(data, null, 2));
140
+ return;
141
+ }
142
+ for (const hook of data.webhooks) {
143
+ console.log(`${hook.id} ${hook.url} enabled:${hook.enabled}`);
144
+ }
145
+ if (!data.webhooks.length)
146
+ console.log('(empty)');
147
+ });
148
+ program
149
+ .command('webhook:delete')
150
+ .argument('<webhookId>')
151
+ .action(async (webhookId) => {
152
+ const cfg = readConfig();
153
+ if (!cfg.token)
154
+ throw new Error('not_logged_in');
155
+ await postJson(`${cfg.apiUrl}/v1/webhooks/${webhookId}`, undefined, cfg.token, 'DELETE');
156
+ console.log(chalk.green('✓ webhook deleted'), webhookId);
157
+ });
92
158
  program.parseAsync(process.argv).catch((e) => {
93
159
  console.error(chalk.red('error:'), e?.message ?? String(e));
94
160
  process.exit(1);
package/dist/config.js CHANGED
@@ -1,29 +1,28 @@
1
+ import fs from 'node:fs';
1
2
  import os from 'node:os';
2
3
  import path from 'node:path';
3
- import fs from 'node:fs';
4
- export function configDir() {
5
- return path.join(os.homedir(), '.oadm');
6
- }
7
- export function configPath() {
8
- return path.join(configDir(), 'config.json');
9
- }
4
+ const CONFIG_DIR = path.join(os.homedir(), '.oadm');
5
+ const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
10
6
  export function readConfig() {
11
- const p = configPath();
12
- if (!fs.existsSync(p)) {
13
- return { apiUrl: process.env.OADM_API_URL ?? 'http://localhost:3000' };
14
- }
15
- const raw = fs.readFileSync(p, 'utf8');
16
- const cfg = JSON.parse(raw);
17
- cfg.apiUrl = cfg.apiUrl ?? process.env.OADM_API_URL ?? 'http://localhost:3000';
18
- return cfg;
19
- }
20
- export function writeConfig(cfg) {
21
- fs.mkdirSync(configDir(), { recursive: true });
22
- fs.writeFileSync(configPath(), JSON.stringify(cfg, null, 2));
23
7
  try {
24
- fs.chmodSync(configPath(), 0o600);
8
+ const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
9
+ const cfg = JSON.parse(raw);
10
+ const apiUrl = process.env.OADM_API_URL ??
11
+ (typeof cfg.apiUrl === 'string' ? cfg.apiUrl : '') ??
12
+ '';
13
+ return {
14
+ apiUrl: apiUrl || 'https://api-zeta-jet-48.vercel.app',
15
+ name: typeof cfg.name === 'string' ? cfg.name : undefined,
16
+ token: typeof cfg.token === 'string' ? cfg.token : undefined,
17
+ };
25
18
  }
26
19
  catch {
27
- // ignore on platforms that don't support chmod
20
+ const apiUrl = process.env.OADM_API_URL ?? 'https://api-zeta-jet-48.vercel.app';
21
+ return { apiUrl };
28
22
  }
29
23
  }
24
+ export function writeConfig(cfg) {
25
+ if (!fs.existsSync(CONFIG_DIR))
26
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
27
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2));
28
+ }
package/dist/http.js CHANGED
@@ -1,31 +1,32 @@
1
- export async function postJson(url, body, token) {
2
- const r = await fetch(url, {
3
- method: 'POST',
1
+ import fetch from 'node-fetch';
2
+ export async function getJson(url, token) {
3
+ const res = await fetch(url, {
4
+ method: 'GET',
4
5
  headers: {
5
- 'content-type': 'application/json',
6
- ...(token ? { authorization: `Bearer ${token}` } : {}),
6
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
7
7
  },
8
- body: JSON.stringify(body),
9
8
  });
10
- const text = await r.text();
11
- const data = text ? JSON.parse(text) : null;
12
- if (!r.ok) {
13
- const msg = data?.error ? `${data.error}` : `http_${r.status}`;
14
- throw new Error(msg);
9
+ if (!res.ok) {
10
+ const text = await res.text().catch(() => '');
11
+ throw new Error(`http_${res.status}${text ? `: ${text}` : ''}`);
15
12
  }
16
- return data;
13
+ return (await res.json());
17
14
  }
18
- export async function getJson(url, token) {
19
- const r = await fetch(url, {
15
+ export async function postJson(url, body, token, method = 'POST') {
16
+ const res = await fetch(url, {
17
+ method,
20
18
  headers: {
21
- ...(token ? { authorization: `Bearer ${token}` } : {}),
19
+ 'Content-Type': 'application/json',
20
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
22
21
  },
22
+ body: body === undefined ? undefined : JSON.stringify(body),
23
23
  });
24
- const text = await r.text();
25
- const data = text ? JSON.parse(text) : null;
26
- if (!r.ok) {
27
- const msg = data?.error ? `${data.error}` : `http_${r.status}`;
28
- throw new Error(msg);
24
+ if (!res.ok) {
25
+ const text = await res.text().catch(() => '');
26
+ throw new Error(`http_${res.status}${text ? `: ${text}` : ''}`);
29
27
  }
30
- return data;
28
+ const ct = res.headers.get('content-type') || '';
29
+ if (!ct.includes('application/json'))
30
+ return {};
31
+ return (await res.json());
31
32
  }
package/package.json CHANGED
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "name": "@codejeet/oadm",
3
- "version": "0.0.1",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "oadm": "dist/cli.js"
7
7
  },
8
- "files": ["dist"],
8
+ "files": [
9
+ "dist"
10
+ ],
9
11
  "scripts": {
10
12
  "dev": "tsx src/cli.ts",
11
13
  "build": "tsc -p tsconfig.json",
14
+ "prepack": "tsc -p tsconfig.json",
12
15
  "typecheck": "tsc -p tsconfig.json --noEmit"
13
16
  },
14
17
  "dependencies": {