dooray-mail-cli 0.2.1 → 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/dooray-cli.js +270 -15
  2. package/package.json +1 -1
package/dooray-cli.js CHANGED
@@ -14,6 +14,10 @@ const os = require('os');
14
14
  const CONFIG_PATH = path.join(os.homedir(), '.dooray-config.json');
15
15
  const CACHE_PATH = path.join(os.homedir(), '.dooray-mail-cache.json');
16
16
 
17
+ // 버전 정보
18
+ const packageJson = require('./package.json');
19
+ const VERSION = packageJson.version;
20
+
17
21
  // CLI 명령어 파서
18
22
  const args = process.argv.slice(2);
19
23
  const command = args[0];
@@ -69,10 +73,10 @@ async function handleCommand() {
69
73
  await configCommand();
70
74
  break;
71
75
  case 'list':
72
- await listCommand();
76
+ await listCommand(args.slice(1));
73
77
  break;
74
78
  case 'recent':
75
- await recentCommand();
79
+ await recentCommand(args.slice(1));
76
80
  break;
77
81
  case 'read':
78
82
  await readCommand(args[1]);
@@ -107,6 +111,14 @@ async function handleCommand() {
107
111
  case 'test':
108
112
  await testCommand();
109
113
  break;
114
+ case 'version':
115
+ case '--version':
116
+ case '-v':
117
+ console.log(`dooray-cli v${VERSION}`);
118
+ break;
119
+ case 'install-skill':
120
+ await installSkillCommand();
121
+ break;
110
122
  case 'help':
111
123
  case '--help':
112
124
  case '-h':
@@ -119,6 +131,54 @@ async function handleCommand() {
119
131
  }
120
132
  }
121
133
 
134
+ // 비밀번호 입력 (숨김 처리)
135
+ function readPassword(prompt) {
136
+ return new Promise((resolve) => {
137
+ const stdin = process.stdin;
138
+ const stdout = process.stdout;
139
+
140
+ stdout.write(prompt);
141
+
142
+ let password = '';
143
+ stdin.setRawMode(true);
144
+ stdin.resume();
145
+ stdin.setEncoding('utf8');
146
+
147
+ const onData = (char) => {
148
+ char = char.toString('utf8');
149
+
150
+ switch (char) {
151
+ case '\n':
152
+ case '\r':
153
+ case '\u0004':
154
+ stdin.setRawMode(false);
155
+ stdin.pause();
156
+ stdin.removeListener('data', onData);
157
+ stdout.write('\n');
158
+ resolve(password);
159
+ break;
160
+ case '\u0003':
161
+ process.exit();
162
+ break;
163
+ case '\u007f': // Backspace
164
+ if (password.length > 0) {
165
+ password = password.slice(0, -1);
166
+ stdout.write('\b \b');
167
+ }
168
+ break;
169
+ default:
170
+ if (char.charCodeAt(0) >= 32) {
171
+ password += char;
172
+ stdout.write('*');
173
+ }
174
+ break;
175
+ }
176
+ };
177
+
178
+ stdin.on('data', onData);
179
+ });
180
+ }
181
+
122
182
  // config: 설정 저장
123
183
  async function configCommand() {
124
184
  const readline = require('readline').createInterface({
@@ -132,7 +192,9 @@ async function configCommand() {
132
192
  console.log('\n📧 Dooray Configuration Setup\n');
133
193
 
134
194
  const email = await question('Email: ');
135
- const password = await question('Password: ');
195
+ readline.close();
196
+
197
+ const password = await readPassword('Password: ');
136
198
 
137
199
  const security = new SecurityManager();
138
200
  const encryptedPassword = await security.encryptToken(password);
@@ -146,25 +208,29 @@ async function configCommand() {
146
208
 
147
209
  saveConfig(config);
148
210
  console.log(`\n✅ Configuration saved to ${CONFIG_PATH}`);
149
- } finally {
211
+ } catch (error) {
150
212
  readline.close();
213
+ throw error;
151
214
  }
152
215
  }
153
216
 
154
217
  // list: 읽지 않은 메일 목록
155
- async function listCommand() {
218
+ async function listCommand(args) {
219
+ // 첫 번째 인자로 개수 지정 가능
220
+ const limit = args && args[0] && !isNaN(args[0]) ? parseInt(args[0]) : 1000;
221
+
156
222
  const config = loadConfig();
157
223
  const client = await createClient(config);
158
224
 
159
225
  try {
160
- const mails = await client.getUnreadMail(10);
226
+ const mails = await client.getUnreadMail(limit);
161
227
 
162
228
  if (mails.length === 0) {
163
229
  console.log('📭 No unread mails');
164
230
  return;
165
231
  }
166
232
 
167
- console.log(`\n📬 Unread Mails (${mails.length})\n`);
233
+ console.log(`\n📬 Unread Mails (${mails.length}${limit < 1000 ? ` / showing ${limit}` : ''})\n`);
168
234
  mails.forEach((mail, idx) => {
169
235
  const fromText = typeof mail.from === 'object' ? `${mail.from.name} <${mail.from.email}>` : mail.from;
170
236
  console.log(`${idx + 1}. [UID: ${mail.id}] ${fromText}`);
@@ -177,25 +243,58 @@ async function listCommand() {
177
243
  }
178
244
 
179
245
  // recent: 최근 메일 목록 (읽음/안읽음 모두)
180
- async function recentCommand() {
246
+ async function recentCommand(args) {
247
+ // 옵션 파싱
248
+ let limit = 1000;
249
+ let since, before;
250
+
251
+ if (args) {
252
+ for (let i = 0; i < args.length; i++) {
253
+ if (args[i] === '--since' && args[i + 1]) {
254
+ since = args[++i];
255
+ } else if (args[i] === '--before' && args[i + 1]) {
256
+ before = args[++i];
257
+ } else if (!isNaN(args[i])) {
258
+ limit = parseInt(args[i]);
259
+ }
260
+ }
261
+ }
262
+
181
263
  const config = loadConfig();
182
264
  const client = await createClient(config);
183
265
 
184
266
  try {
185
- const mails = await client.getRecentMail(10);
267
+ let mails;
268
+
269
+ // 날짜 필터가 있으면 searchMail 사용
270
+ if (since || before) {
271
+ mails = await client.searchMail([], { since, before });
272
+ // limit 적용
273
+ if (mails.length > limit) {
274
+ mails = mails.slice(0, limit);
275
+ }
276
+ } else {
277
+ // 날짜 필터 없으면 기존 getRecentMail 사용
278
+ mails = await client.getRecentMail(limit);
279
+ }
186
280
 
187
281
  if (mails.length === 0) {
188
282
  console.log('📭 No mails found');
189
283
  return;
190
284
  }
191
285
 
192
- console.log(`\n📬 Recent Mails (${mails.length})\n`);
286
+ const filters = [];
287
+ if (since) filters.push(`since: ${since}`);
288
+ if (before) filters.push(`before: ${before}`);
289
+ const filterStr = filters.length > 0 ? ` (${filters.join(', ')})` : '';
290
+
291
+ console.log(`\n📬 Recent Mails${filterStr} (${mails.length}${limit < 1000 ? ` / showing ${limit}` : ''})\n`);
193
292
  mails.forEach((mail, idx) => {
194
293
  const readStatus = mail.isRead ? '✓' : '●';
195
294
  const fromText = typeof mail.from === 'object' ? `${mail.from.name} <${mail.from.email}>` : mail.from;
196
- console.log(`${idx + 1}. ${readStatus} [UID: ${mail.id}] ${fromText}`);
295
+ console.log(`${idx + 1}. ${readStatus} [UID: ${mail.id || mail.uid}] ${fromText}`);
197
296
  console.log(` Subject: ${mail.subject}`);
198
- console.log(` Date: ${mail.receivedAt}\n`);
297
+ console.log(` Date: ${mail.receivedAt || mail.date}\n`);
199
298
  });
200
299
  } finally {
201
300
  await client.close();
@@ -795,6 +894,149 @@ async function testCommand() {
795
894
  }
796
895
  }
797
896
 
897
+ // OpenClaw 설치 위치 찾기
898
+ function findOpenClawInstallation() {
899
+ const { execSync } = require('child_process');
900
+
901
+ try {
902
+ // where (Windows) 또는 which (Unix) 명령어로 실행 파일 찾기
903
+ const command = process.platform === 'win32' ? 'where openclaw' : 'which openclaw';
904
+ const result = execSync(command, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
905
+ const openclawPath = result.trim().split('\n')[0];
906
+
907
+ if (openclawPath && fs.existsSync(openclawPath)) {
908
+ // 실행 파일의 디렉토리에서 상위로 올라가며 skills 폴더 찾기
909
+ let currentPath = path.dirname(openclawPath);
910
+ for (let i = 0; i < 5; i++) {
911
+ const skillsPath = path.join(currentPath, 'skills');
912
+ if (fs.existsSync(skillsPath)) {
913
+ return skillsPath;
914
+ }
915
+ const newPath = path.dirname(currentPath);
916
+ if (newPath === currentPath) break;
917
+ currentPath = newPath;
918
+ }
919
+
920
+ return path.dirname(openclawPath);
921
+ }
922
+ } catch (error) {
923
+ // OpenClaw 실행 파일을 찾지 못함
924
+ }
925
+
926
+ return null;
927
+ }
928
+
929
+ // install-skill: OpenClaw Skill 자동 설치
930
+ async function installSkillCommand() {
931
+ const readline = require('readline').createInterface({
932
+ input: process.stdin,
933
+ output: process.stdout
934
+ });
935
+
936
+ const question = (q) => new Promise(resolve => readline.question(q, resolve));
937
+
938
+ try {
939
+ console.log('\n📦 Installing Dooray Skill for OpenClaw...\n');
940
+
941
+ // 1. SKILL.md 위치 찾기
942
+ const currentDir = __dirname || path.dirname(process.argv[1]);
943
+ const localSkillMd = path.join(currentDir, 'SKILL.md');
944
+
945
+ if (!fs.existsSync(localSkillMd)) {
946
+ console.error('❌ SKILL.md not found in package directory');
947
+ console.log('💡 Try reinstalling: npm install -g dooray-mail-cli');
948
+ readline.close();
949
+ process.exit(1);
950
+ }
951
+
952
+ // 2. OpenClaw 설치 위치 찾기
953
+ console.log('🔍 Searching for OpenClaw installation...');
954
+ const openclawInstall = findOpenClawInstallation();
955
+
956
+ if (openclawInstall) {
957
+ console.log(`✅ Found OpenClaw installation: ${openclawInstall}`);
958
+ } else {
959
+ console.log('⚠️ OpenClaw executable not found in PATH');
960
+ }
961
+
962
+ // 3. OpenClaw skills 디렉토리 찾기 (여러 가능한 위치)
963
+ const possiblePaths = [
964
+ openclawInstall ? path.join(openclawInstall, 'skills') : null,
965
+ path.join(os.homedir(), 'skills'),
966
+ path.join(os.homedir(), '.openclaw', 'skills'),
967
+ path.join(os.homedir(), 'OpenClaw', 'skills'),
968
+ path.join(os.homedir(), '.config', 'openclaw', 'skills'),
969
+ ].filter(p => p !== null);
970
+
971
+ let skillsDir = null;
972
+
973
+ // 기존 skills 폴더 찾기
974
+ for (const dir of possiblePaths) {
975
+ if (fs.existsSync(dir)) {
976
+ skillsDir = dir;
977
+ console.log(`✅ Found OpenClaw skills directory: ${skillsDir}`);
978
+ break;
979
+ }
980
+ }
981
+
982
+ // 찾지 못한 경우 사용자에게 물어보기
983
+ if (!skillsDir) {
984
+ console.log('\n⚠️ OpenClaw skills directory not found in common locations.');
985
+ console.log('\nCommon locations:');
986
+ possiblePaths.forEach((p, i) => console.log(` ${i + 1}. ${p}`));
987
+ console.log(' 0. Enter custom path');
988
+
989
+ const choice = await question('\nSelect location (0-' + possiblePaths.length + '): ');
990
+ const idx = parseInt(choice);
991
+
992
+ if (idx === 0) {
993
+ const customPath = await question('Enter skills directory path: ');
994
+ skillsDir = customPath.trim();
995
+ } else if (idx >= 1 && idx <= possiblePaths.length) {
996
+ skillsDir = possiblePaths[idx - 1];
997
+ } else {
998
+ skillsDir = possiblePaths[0]; // 기본값
999
+ }
1000
+
1001
+ console.log(`\nUsing: ${skillsDir}`);
1002
+ }
1003
+
1004
+ const dooraySkillDir = path.join(skillsDir, 'dooray');
1005
+
1006
+ // 디렉토리 생성
1007
+ if (!fs.existsSync(skillsDir)) {
1008
+ fs.mkdirSync(skillsDir, { recursive: true });
1009
+ console.log(`✅ Created skills directory: ${skillsDir}`);
1010
+ }
1011
+
1012
+ if (!fs.existsSync(dooraySkillDir)) {
1013
+ fs.mkdirSync(dooraySkillDir, { recursive: true });
1014
+ console.log(`✅ Created dooray skill directory: ${dooraySkillDir}`);
1015
+ }
1016
+
1017
+ // 4. SKILL.md 복사
1018
+ const targetSkillMd = path.join(dooraySkillDir, 'SKILL.md');
1019
+ fs.copyFileSync(localSkillMd, targetSkillMd);
1020
+
1021
+ console.log(`✅ SKILL.md copied to: ${targetSkillMd}`);
1022
+ console.log('\n🎉 Dooray Skill installed successfully!');
1023
+ console.log('\n💡 Next steps:');
1024
+ console.log(' 1. Restart OpenClaw');
1025
+ console.log(' 2. Try: "dooray 메일 확인해줘"');
1026
+ console.log(`\nSkill location: ${dooraySkillDir}`);
1027
+
1028
+ } catch (error) {
1029
+ console.error('❌ Installation failed:', error.message);
1030
+ console.log('\n💡 Manual installation:');
1031
+ console.log(` 1. Find your OpenClaw skills directory`);
1032
+ console.log(` 2. Create subdirectory: dooray`);
1033
+ console.log(` 3. Copy SKILL.md to: [skills]/dooray/SKILL.md`);
1034
+ process.exit(1);
1035
+ } finally {
1036
+ readline.close();
1037
+ }
1038
+ }
1039
+
798
1040
  // help: 도움말
799
1041
  function showHelp() {
800
1042
  console.log(`
@@ -804,8 +1046,12 @@ Usage: dooray-cli <command> [options]
804
1046
 
805
1047
  Commands:
806
1048
  config Set up email configuration
807
- list List unread mails only
808
- recent List recent mails (read + unread)
1049
+ list [count] List unread mails (default: 1000)
1050
+ list 50 Show 50 unread mails
1051
+ recent [count] List recent mails (default: 1000)
1052
+ recent 100 Show 100 recent mails
1053
+ recent --since <YYYY-MM-DD> --before <YYYY-MM-DD>
1054
+ Filter by date range
809
1055
  read <uid> Read a specific mail by UID
810
1056
  send Send a new mail (interactive)
811
1057
  send --to <email> --subject <subject> --body <body>
@@ -841,12 +1087,19 @@ Commands:
841
1087
  Search before date
842
1088
  unread Show unread mail count
843
1089
  test Test IMAP/SMTP connection
844
- help Show this help message
1090
+ install-skill Install Dooray skill for OpenClaw
1091
+ version, -v, --version
1092
+ Show version number
1093
+ help, -h, --help Show this help message
845
1094
 
846
1095
  Examples:
847
1096
  dooray-cli config
848
1097
  dooray-cli list
1098
+ dooray-cli list 50
849
1099
  dooray-cli recent
1100
+ dooray-cli recent 100
1101
+ dooray-cli recent --since "2026-01-01" --before "2026-02-01"
1102
+ dooray-cli recent 50 --since "2026-02-01"
850
1103
  dooray-cli read 123
851
1104
  dooray-cli send --to "user@example.com" --subject "Hello" --body "Test"
852
1105
  dooray-cli send --to "user@example.com" --subject "Hello" --body "Test" --cc "cc@example.com" --attach "./file.pdf"
@@ -866,6 +1119,8 @@ Examples:
866
1119
  dooray-cli search "report" --since "2026-01-01"
867
1120
  dooray-cli search "invoice" --before "2026-02-01"
868
1121
  dooray-cli unread
1122
+ dooray-cli install-skill
1123
+ dooray-cli --version
869
1124
 
870
1125
  Configuration file: ${CONFIG_PATH}
871
1126
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dooray-mail-cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.4",
4
4
  "description": "Dooray mail CLI for OpenClaw Skill - IMAP/SMTP integration",
5
5
  "main": "./dist/mail-client.js",
6
6
  "bin": {