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.
- package/dooray-cli.js +270 -15
- 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
|
-
|
|
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
|
-
}
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
808
|
-
|
|
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
|
-
|
|
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
|
`);
|