@rikaidev/yomi 0.4.2 → 0.6.0
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/README.md +46 -4
- package/dist/ambient/ambient-event-graph.js +172 -0
- package/dist/ambient/ambient-observer.js +71 -0
- package/dist/ambient/event-graph-reducer.js +300 -0
- package/dist/ambient/ingestion-adapter.js +203 -0
- package/dist/ambient/journal/crypto.js +58 -0
- package/dist/ambient/journal/journal-store.js +284 -0
- package/dist/ambient/journal/types.js +7 -0
- package/dist/ambient/summaries/retriever.js +55 -0
- package/dist/ambient/summaries/summary-engine.js +128 -0
- package/dist/ambient/summaries/types.js +4 -0
- package/dist/attention/policy.js +101 -0
- package/dist/auth/credential-store.js +47 -17
- package/dist/auth/keychain.js +7 -54
- package/dist/cli/query.js +875 -0
- package/dist/cli/work-item.js +24 -0
- package/dist/insight/relationships.js +1 -1
- package/dist/line/auth/protocol/pwless-login-flow.js +12 -0
- package/dist/line/auth/pwless/index.js +1 -1
- package/dist/line/client/auth-service/client.js +7 -8
- package/dist/line/client/buddy-service/client.js +73 -0
- package/dist/line/client/index.js +28 -3
- package/dist/line/client/obs-media-client.js +38 -0
- package/dist/line/client/parsers.js +1 -0
- package/dist/line/client/square-service/client.js +298 -0
- package/dist/line/client/sticker-meta.js +32 -0
- package/dist/line/client/sync-service/client.js +31 -3
- package/dist/line/client/talk-service/command-client.js +9 -1
- package/dist/line/client/talk-service/contact-query.js +8 -1
- package/dist/line/client/talk-service/message-box-query.js +17 -0
- package/dist/line/client/talk-service/message-id-query.js +27 -3
- package/dist/line/client/talk-service/query-client.js +32 -26
- package/dist/line/client/talk-service/requests.js +10 -11
- package/dist/line/core/auth-session-runtime.js +17 -2
- package/dist/line/core/chat-event.js +35 -0
- package/dist/line/core/chat-runtime-service.js +18 -23
- package/dist/line/core/config.js +1 -0
- package/dist/line/core/directory-runtime-service.js +10 -2
- package/dist/line/core/e2ee/recovery/bootstrap-keys.js +8 -16
- package/dist/line/core/flex-message.js +453 -0
- package/dist/line/core/mention.js +1 -1
- package/dist/line/core/message-preview.js +60 -0
- package/dist/line/core/message-query-service.js +0 -20
- package/dist/line/core/service.js +1 -3
- package/dist/mcp/handlers/contacts.js +12 -6
- package/dist/mcp/handlers/conversations.js +91 -4
- package/dist/mcp/handlers/login.js +1 -1
- package/dist/mcp/handlers/message-actions.js +1 -1
- package/dist/mcp/handlers/search.js +1 -1
- package/dist/mcp/handlers/send.js +8 -11
- package/dist/mcp/handlers/shared.js +1 -1
- package/dist/mcp/integrator.js +168 -0
- package/dist/mcp/media.js +135 -4
- package/dist/mcp/names.js +78 -6
- package/dist/mcp/phi-guard.js +2 -2
- package/dist/search/bigram.js +2 -2
- package/dist/search/collector.js +1 -1
- package/dist/search/default-embedder.js +12 -3
- package/dist/search/embedder.js +1 -1
- package/dist/version.js +1 -1
- package/dist/work-item/adapter.js +1 -0
- package/dist/work-item/destination-contracts.js +1 -0
- package/dist/work-item/event-context.js +73 -0
- package/dist/work-item/gold-contract.js +101 -0
- package/dist/work-item/gold-coverage.js +96 -0
- package/dist/work-item/gold-derivation.js +67 -0
- package/dist/work-item/gold-split.js +143 -0
- package/dist/work-item/index.js +23 -0
- package/dist/work-item/line-adapter.js +111 -0
- package/dist/work-item/llama-cpp-work-event-adapter.js +239 -0
- package/dist/work-item/local-provider.js +38 -0
- package/dist/work-item/local-work-event-provider.js +56 -0
- package/dist/work-item/onnx-work-event-adapter.js +223 -0
- package/dist/work-item/provider.js +21 -0
- package/dist/work-item/registry.js +19 -0
- package/dist/work-item/service.js +85 -0
- package/dist/work-item/store.js +97 -0
- package/dist/work-item/types.js +79 -0
- package/dist/work-item/work-event-graph-engine.js +63 -0
- package/dist/work-item/work-event-inference.js +267 -0
- package/dist/work-item/work-event-matcher.js +105 -0
- package/dist/work-item/work-event-model-manifest.js +39 -0
- package/dist/work-item/work-event-store.js +125 -0
- package/dist/work-item/work-event.js +123 -0
- package/package.json +26 -2
- package/run.mjs +371 -2
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"engines": {
|
|
4
4
|
"node": ">=22.13.0 <23.0.0 || >=23.4.0"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.6.0",
|
|
7
7
|
"description": "Open-source personal LINE MCP server for AI agents. Read, reply, send images, and search LINE conversations locally with Letter-Sealing E2EE.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"license": "MIT",
|
|
@@ -43,10 +43,34 @@
|
|
|
43
43
|
"start": "node run.mjs",
|
|
44
44
|
"lint": "biome check src/",
|
|
45
45
|
"lint:fix": "biome check --write src/",
|
|
46
|
+
"lint:comments": "node scripts/lint-comments.mjs",
|
|
47
|
+
"hooks:install": "git config core.hooksPath .githooks",
|
|
48
|
+
"pretest": "npm run lint:comments && npm run test:fixture-hygiene",
|
|
46
49
|
"format": "biome format --write src/",
|
|
47
50
|
"test": "bun test",
|
|
51
|
+
"predesktop:build": "npm run build",
|
|
52
|
+
"desktop:build": "bash scripts/build-swift-desktop.sh",
|
|
53
|
+
"desktop:clean": "node scripts/clean-desktop-artifacts.mjs",
|
|
54
|
+
"desktop:clean:all": "node scripts/clean-desktop-artifacts.mjs --all",
|
|
55
|
+
"desktop:harness": "node scripts/desktop-harness.mjs",
|
|
56
|
+
"desktop:qos": "node scripts/desktop-qos-harness.mjs",
|
|
57
|
+
"desktop:flex-parity": "npm run build && node scripts/flex-layout-parity-harness.mjs",
|
|
58
|
+
"desktop:swift-quality": "bash scripts/swift-quality.sh",
|
|
59
|
+
"desktop:i18n-harness": "node scripts/swift-i18n-harness.mjs",
|
|
60
|
+
"desktop:restart": "node scripts/desktop-harness.mjs --restart",
|
|
61
|
+
"desktop:run": "./desktop/mac-native/Yomi.app/Contents/MacOS/Yomi",
|
|
62
|
+
"desktop:core:macos": "node scripts/package-desktop-core.mjs --platform macos --output build/desktop-core-macos --semantic full",
|
|
63
|
+
"desktop:core:windows": "node scripts/package-desktop-core.mjs --platform windows --output build/desktop-core-windows --semantic full",
|
|
64
|
+
"desktop:package:harness": "node scripts/desktop-package-harness.mjs",
|
|
65
|
+
"desktop:windows:build": "cargo build --manifest-path desktop/Cargo.toml --release --target x86_64-pc-windows-msvc",
|
|
66
|
+
"desktop:windows:lint": "cargo fmt --manifest-path desktop/Cargo.toml -- --check && cargo clippy --manifest-path desktop/Cargo.toml --target x86_64-pc-windows-msvc -- -D warnings",
|
|
67
|
+
"desktop:windows:qc": "npm run typecheck && npm run lint && npm run lint:comments && cargo test --manifest-path desktop/Cargo.toml && npm run desktop:windows:lint",
|
|
68
|
+
"desktop:windows:i18n": "node scripts/windows-i18n-harness.mjs",
|
|
69
|
+
"desktop:windows:harness": "node scripts/windows-desktop-harness.mjs",
|
|
48
70
|
"version": "node scripts/sync-version.mjs && git add src/version.ts",
|
|
49
|
-
"prepublishOnly": "npm run build"
|
|
71
|
+
"prepublishOnly": "npm run build",
|
|
72
|
+
"test:fixture-hygiene": "node scripts/test-fixture-hygiene.mjs",
|
|
73
|
+
"desktop:install-local": "bash scripts/install-swift-desktop-local.sh"
|
|
50
74
|
},
|
|
51
75
|
"dependencies": {
|
|
52
76
|
"@huggingface/transformers": "^4.2.0",
|
package/run.mjs
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* yomi version Print version
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { existsSync } from 'node:fs';
|
|
13
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
14
15
|
import { fileURLToPath } from 'node:url';
|
|
15
16
|
|
|
16
17
|
// Load the compiled `dist/` build when it exists (this is what the npm
|
|
@@ -22,13 +23,38 @@ import { fileURLToPath } from 'node:url';
|
|
|
22
23
|
// STRIPPING), which is exactly how every 0.1.x release failed to start.
|
|
23
24
|
// `files` in package.json must therefore always ship `dist/`.
|
|
24
25
|
const distUrl = new URL('./dist/', import.meta.url);
|
|
25
|
-
const
|
|
26
|
+
const sourceRoot = fileURLToPath(new URL('./src/', import.meta.url));
|
|
27
|
+
|
|
28
|
+
function newestMtime(directory) {
|
|
29
|
+
if (!existsSync(directory)) return 0;
|
|
30
|
+
let newest = 0;
|
|
31
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
32
|
+
const path = join(directory, entry.name);
|
|
33
|
+
if (entry.isDirectory()) newest = Math.max(newest, newestMtime(path));
|
|
34
|
+
else if (entry.name.endsWith('.ts')) newest = Math.max(newest, statSync(path).mtimeMs);
|
|
35
|
+
}
|
|
36
|
+
return newest;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const distServerPath = fileURLToPath(new URL('mcp/server.js', distUrl));
|
|
40
|
+
const distMtime = existsSync(distServerPath) ? statSync(distServerPath).mtimeMs : 0;
|
|
41
|
+
const sourceIsNewer = existsSync(sourceRoot) && newestMtime(sourceRoot) > distMtime;
|
|
42
|
+
const built = existsSync(distServerPath) && process.env.YOMI_DEV_SOURCE !== '1' && !sourceIsNewer;
|
|
26
43
|
const base = built ? distUrl : new URL('./src/', import.meta.url);
|
|
27
44
|
const ext = built ? '.js' : '.ts';
|
|
28
45
|
|
|
29
46
|
/** Import a module by its path under src/, resolved to dist/ when built. */
|
|
30
47
|
const load = (path) => import(new URL(path + ext, base).href);
|
|
31
48
|
|
|
49
|
+
const writeStdout = (text) =>
|
|
50
|
+
new Promise((resolve, reject) => {
|
|
51
|
+
process.stdout.write(text, (error) => {
|
|
52
|
+
if (error) reject(error);
|
|
53
|
+
else resolve();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
const writeJson = (value) => writeStdout(`${JSON.stringify(value)}\n`);
|
|
57
|
+
|
|
32
58
|
const args = process.argv.slice(2);
|
|
33
59
|
const cmd = args[0] ?? 'serve';
|
|
34
60
|
|
|
@@ -38,6 +64,349 @@ switch (cmd) {
|
|
|
38
64
|
const code = await cliLogin(args.slice(1));
|
|
39
65
|
process.exit(code);
|
|
40
66
|
}
|
|
67
|
+
case 'setup-mcp':
|
|
68
|
+
case 'integrate': {
|
|
69
|
+
const { integrateAllClients, integrateClient, getClientTargets } = await load('mcp/integrator');
|
|
70
|
+
const targetArg = args[1];
|
|
71
|
+
if (targetArg) {
|
|
72
|
+
const targets = getClientTargets();
|
|
73
|
+
const matched = targets.find(t => t.id === targetArg || t.name.toLowerCase().includes(targetArg.toLowerCase()));
|
|
74
|
+
if (matched) {
|
|
75
|
+
const res = integrateClient(matched);
|
|
76
|
+
console.log(JSON.stringify(res));
|
|
77
|
+
} else {
|
|
78
|
+
console.error(`Target client "${targetArg}" not recognized.`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
const report = integrateAllClients();
|
|
83
|
+
console.log(JSON.stringify(report));
|
|
84
|
+
}
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
case 'chats':
|
|
88
|
+
case 'conversations': {
|
|
89
|
+
const { cliListConversationsJson } = await load('cli/query');
|
|
90
|
+
const limit = args[1] && args[1] !== 'all' ? parseInt(args[1], 10) : undefined;
|
|
91
|
+
const attentionOnly = args.includes('--attention-only');
|
|
92
|
+
const semantic = args.includes('--semantic');
|
|
93
|
+
const json = await cliListConversationsJson({ limit, attentionOnly, semantic });
|
|
94
|
+
await writeJson(json);
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
case 'desktop-inbox': {
|
|
98
|
+
// Keep one authenticated LINE runtime for the complete desktop startup
|
|
99
|
+
// pass. Its resume result is the session authority; do not infer logout
|
|
100
|
+
// from an empty list, a cache miss, or a second probe process.
|
|
101
|
+
const { LineProtocolService } = await load('line/core/service');
|
|
102
|
+
const { cliListConversationsJson } = await load('cli/query');
|
|
103
|
+
const service = new LineProtocolService();
|
|
104
|
+
const connected = await service.resumeSession();
|
|
105
|
+
if (!connected) {
|
|
106
|
+
await writeJson({
|
|
107
|
+
session: { connected: false, reason: service.loginReason ?? 'unavailable' },
|
|
108
|
+
chats: [],
|
|
109
|
+
});
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
112
|
+
const chats = await cliListConversationsJson({}, service);
|
|
113
|
+
await writeJson({ session: { connected: true, reason: null }, chats });
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
case 'desktop-startup-messages': {
|
|
117
|
+
// One shared runtime warms all requested rooms at launch. This is a
|
|
118
|
+
// startup batch, not a click-triggered series of independent sessions.
|
|
119
|
+
const { LineProtocolService } = await load('line/core/service');
|
|
120
|
+
const { cliGetChatMessagesJson } = await load('cli/query');
|
|
121
|
+
const chatIds = args.slice(1).filter(Boolean);
|
|
122
|
+
const service = new LineProtocolService();
|
|
123
|
+
const connected = await service.resumeSession();
|
|
124
|
+
if (!connected) {
|
|
125
|
+
await writeJson({
|
|
126
|
+
session: { connected: false, reason: service.loginReason ?? 'unavailable' },
|
|
127
|
+
messages: {},
|
|
128
|
+
});
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
const messages = {};
|
|
132
|
+
for (const chatId of chatIds) {
|
|
133
|
+
messages[chatId] = await cliGetChatMessagesJson(chatId, 40, service);
|
|
134
|
+
}
|
|
135
|
+
await writeJson({ session: { connected: true, reason: null }, messages });
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
case 'work-item': {
|
|
139
|
+
const operation = args[1] || 'create';
|
|
140
|
+
let input = '';
|
|
141
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
142
|
+
let payload;
|
|
143
|
+
try {
|
|
144
|
+
payload = JSON.parse(input || '{}');
|
|
145
|
+
} catch {
|
|
146
|
+
console.error(JSON.stringify({ error: 'invalid JSON payload' }));
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
const { cliCreateWorkItemDraftJson, cliWorkItemOperationJson } = await load('cli/work-item');
|
|
150
|
+
try {
|
|
151
|
+
const result = operation === 'create'
|
|
152
|
+
? await cliCreateWorkItemDraftJson(
|
|
153
|
+
payload.messages || payload.message,
|
|
154
|
+
payload.options || {},
|
|
155
|
+
)
|
|
156
|
+
: await cliWorkItemOperationJson(operation, payload.draft);
|
|
157
|
+
await writeJson(result);
|
|
158
|
+
process.exit(0);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
case 'ambient-observe': {
|
|
165
|
+
const { scanAmbientObservations } = await load('ambient/ambient-observer');
|
|
166
|
+
const since = args[1] ? Number(args[1]) : undefined;
|
|
167
|
+
const report = await scanAmbientObservations(Number.isFinite(since) ? since : undefined);
|
|
168
|
+
// Work-event promotion intentionally abstains until the factorized model
|
|
169
|
+
// contract passes its independent acceptance suite. Ambient candidates
|
|
170
|
+
// remain available as revisable evidence; no flat classifier is allowed
|
|
171
|
+
// to create authoritative work items.
|
|
172
|
+
await writeJson({ ...report, workItems: [] });
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
175
|
+
case 'work-items': {
|
|
176
|
+
await writeJson([]);
|
|
177
|
+
process.exit(0);
|
|
178
|
+
}
|
|
179
|
+
case 'messages':
|
|
180
|
+
case 'chat-messages': {
|
|
181
|
+
const { cliGetChatMessagesJson } = await load('cli/query');
|
|
182
|
+
const chatId = args[1];
|
|
183
|
+
const count = args[2] ? parseInt(args[2], 10) : 30;
|
|
184
|
+
if (!chatId) {
|
|
185
|
+
console.error(JSON.stringify({ error: 'chatId required' }));
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
const json = await cliGetChatMessagesJson(chatId, count);
|
|
189
|
+
await writeJson(json);
|
|
190
|
+
process.exit(0);
|
|
191
|
+
}
|
|
192
|
+
case 'rich-menu': {
|
|
193
|
+
const chatId = args[1];
|
|
194
|
+
if (!chatId) {
|
|
195
|
+
console.error(JSON.stringify({ error: 'chatId required' }));
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
const { LineProtocolService } = await load('line/core/service');
|
|
199
|
+
const service = new LineProtocolService();
|
|
200
|
+
if (!(await service.resumeSession())) process.exit(1);
|
|
201
|
+
const menu = await service.client.getBuddyRichMenu(chatId);
|
|
202
|
+
await writeJson(menu);
|
|
203
|
+
process.exit(0);
|
|
204
|
+
}
|
|
205
|
+
case 'attention-classify': {
|
|
206
|
+
const { cliClassifyAttentionJson } = await load('cli/query');
|
|
207
|
+
let input = '';
|
|
208
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
209
|
+
const items = JSON.parse(input || '[]');
|
|
210
|
+
await writeJson(await cliClassifyAttentionJson(items));
|
|
211
|
+
process.exit(0);
|
|
212
|
+
}
|
|
213
|
+
case 'list-stickers': {
|
|
214
|
+
const { cliListStickersJson } = await load('cli/query');
|
|
215
|
+
const language = args[1] || 'zh_TW';
|
|
216
|
+
await writeJson(await cliListStickersJson(language));
|
|
217
|
+
process.exit(0);
|
|
218
|
+
}
|
|
219
|
+
case 'sticker-previews': {
|
|
220
|
+
const { cliStickerPreviewsJson } = await load('cli/query');
|
|
221
|
+
const packageId = args[1];
|
|
222
|
+
const limit = args[2] ? parseInt(args[2], 10) : 12;
|
|
223
|
+
const language = args[3] || 'en';
|
|
224
|
+
if (!packageId) {
|
|
225
|
+
console.error(JSON.stringify({ error: 'packageId required' }));
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
await writeJson(await cliStickerPreviewsJson(packageId, limit, language));
|
|
229
|
+
process.exit(0);
|
|
230
|
+
}
|
|
231
|
+
case 'send-sticker': {
|
|
232
|
+
const chatId = args[1];
|
|
233
|
+
const stickerId = args[2];
|
|
234
|
+
const packageId = args[3];
|
|
235
|
+
const version = args[4] || '1';
|
|
236
|
+
if (!chatId || !stickerId || !packageId) {
|
|
237
|
+
console.error(JSON.stringify({ error: 'chatId, stickerId and packageId required' }));
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
const { LineProtocolService } = await load('line/core/service');
|
|
241
|
+
const service = new LineProtocolService();
|
|
242
|
+
if (!(await service.resumeSession())) process.exit(1);
|
|
243
|
+
const sent = await service.sendSticker(chatId, stickerId, packageId, version);
|
|
244
|
+
console.log(JSON.stringify({ sent: true, messageId: sent?.id ?? sent?.messageId ?? null }));
|
|
245
|
+
process.exit(0);
|
|
246
|
+
}
|
|
247
|
+
case 'send-message': {
|
|
248
|
+
const chatId = args[1];
|
|
249
|
+
const text = args.slice(2).join(' ').trim();
|
|
250
|
+
if (!chatId || !text) {
|
|
251
|
+
console.error(JSON.stringify({ error: 'chatId and text required' }));
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
const { LineProtocolService } = await load('line/core/service');
|
|
255
|
+
const { buildMentionMetadata } = await load('line/core/mention');
|
|
256
|
+
const service = new LineProtocolService();
|
|
257
|
+
if (!(await service.resumeSession())) process.exit(1);
|
|
258
|
+
const targets = JSON.parse(process.env.YOMI_MENTIONS_JSON || '[]');
|
|
259
|
+
const mentions = targets.flatMap(({ mid, name }) => {
|
|
260
|
+
const start = text.indexOf(`@${name}`);
|
|
261
|
+
return start < 0 ? [] : [{ mid, start, end: start + name.length + 1 }];
|
|
262
|
+
});
|
|
263
|
+
const metadata = mentions.length ? { MENTION: buildMentionMetadata(text, mentions) } : undefined;
|
|
264
|
+
const sent = await service.sendMessage(chatId, text, metadata);
|
|
265
|
+
console.log(JSON.stringify({
|
|
266
|
+
sent: true,
|
|
267
|
+
messageId: sent?.id ?? sent?.messageId ?? null,
|
|
268
|
+
}));
|
|
269
|
+
process.exit(0);
|
|
270
|
+
}
|
|
271
|
+
case 'send-image': {
|
|
272
|
+
const chatId = args[1];
|
|
273
|
+
const imagePath = args[2];
|
|
274
|
+
if (!chatId || !imagePath) {
|
|
275
|
+
console.error(JSON.stringify({ error: 'chatId and imagePath required' }));
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
const { LineProtocolService } = await load('line/core/service');
|
|
279
|
+
const { readFile } = await import('node:fs/promises');
|
|
280
|
+
const { basename } = await import('node:path');
|
|
281
|
+
const service = new LineProtocolService();
|
|
282
|
+
if (!(await service.resumeSession())) process.exit(1);
|
|
283
|
+
const bytes = await readFile(imagePath);
|
|
284
|
+
if (bytes.length === 0) {
|
|
285
|
+
console.error(JSON.stringify({ error: 'image is empty' }));
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
const sent = await service.sendImage(chatId, bytes, basename(imagePath));
|
|
289
|
+
console.log(JSON.stringify({
|
|
290
|
+
sent: true,
|
|
291
|
+
messageId: sent?.messageId ?? sent?.id ?? null,
|
|
292
|
+
oid: sent?.oid ?? null,
|
|
293
|
+
}));
|
|
294
|
+
process.exit(0);
|
|
295
|
+
}
|
|
296
|
+
case 'send-file': {
|
|
297
|
+
const chatId = args[1];
|
|
298
|
+
const filePath = args[2];
|
|
299
|
+
if (!chatId || !filePath) {
|
|
300
|
+
console.error(JSON.stringify({ error: 'chatId and filePath required' }));
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
const { LineProtocolService } = await load('line/core/service');
|
|
304
|
+
const { readFile } = await import('node:fs/promises');
|
|
305
|
+
const { basename } = await import('node:path');
|
|
306
|
+
const service = new LineProtocolService();
|
|
307
|
+
if (!(await service.resumeSession())) process.exit(1);
|
|
308
|
+
const bytes = await readFile(filePath);
|
|
309
|
+
if (bytes.length === 0) {
|
|
310
|
+
console.error(JSON.stringify({ error: 'file is empty' }));
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
const sent = await service.sendFile(chatId, bytes, basename(filePath));
|
|
314
|
+
console.log(JSON.stringify({
|
|
315
|
+
sent: true,
|
|
316
|
+
messageId: sent?.messageId ?? sent?.id ?? null,
|
|
317
|
+
oid: sent?.oid ?? null,
|
|
318
|
+
}));
|
|
319
|
+
process.exit(0);
|
|
320
|
+
}
|
|
321
|
+
case 'send-audio': {
|
|
322
|
+
const chatId = args[1];
|
|
323
|
+
const filePath = args[2];
|
|
324
|
+
const fileName = args[3];
|
|
325
|
+
if (!chatId || !filePath) {
|
|
326
|
+
console.error(JSON.stringify({ error: 'chatId and filePath required' }));
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
const { LineProtocolService } = await load('line/core/service');
|
|
330
|
+
const { readFile } = await import('node:fs/promises');
|
|
331
|
+
const { basename } = await import('node:path');
|
|
332
|
+
const service = new LineProtocolService();
|
|
333
|
+
if (!(await service.resumeSession())) process.exit(1);
|
|
334
|
+
const bytes = await readFile(filePath);
|
|
335
|
+
if (bytes.length === 0) {
|
|
336
|
+
console.error(JSON.stringify({ error: 'audio is empty' }));
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
const sent = await service.sendAudio(chatId, bytes, fileName || basename(filePath));
|
|
340
|
+
console.log(JSON.stringify({
|
|
341
|
+
sent: true,
|
|
342
|
+
messageId: sent?.messageId ?? sent?.id ?? null,
|
|
343
|
+
oid: sent?.oid ?? null,
|
|
344
|
+
}));
|
|
345
|
+
process.exit(0);
|
|
346
|
+
}
|
|
347
|
+
case 'media-preview': {
|
|
348
|
+
const chatId = args[1];
|
|
349
|
+
const messageId = args[2];
|
|
350
|
+
if (!chatId || !messageId) {
|
|
351
|
+
console.error(JSON.stringify({ error: 'chatId and messageId required' }));
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
const { LineProtocolService } = await load('line/core/service');
|
|
355
|
+
const { fetchLineMessageMedia } = await load('mcp/media');
|
|
356
|
+
const service = new LineProtocolService();
|
|
357
|
+
if (!(await service.resumeSession())) process.exit(1);
|
|
358
|
+
const result = await fetchLineMessageMedia(service, chatId, messageId, args[3] !== 'original');
|
|
359
|
+
if (args.includes('--file-output')) {
|
|
360
|
+
const { mkdtemp, writeFile } = await import('node:fs/promises');
|
|
361
|
+
const { join } = await import('node:path');
|
|
362
|
+
const { tmpdir } = await import('node:os');
|
|
363
|
+
const directory = await mkdtemp(join(tmpdir(), 'yomi-media-'));
|
|
364
|
+
const filePath = join(directory, 'payload');
|
|
365
|
+
await writeFile(filePath, result.bytes, { mode: 0o600 });
|
|
366
|
+
await writeJson({ filePath, mimeType: result.mimeType, fileName: result.fileName ?? null });
|
|
367
|
+
process.exit(0);
|
|
368
|
+
}
|
|
369
|
+
await writeJson({
|
|
370
|
+
data: result.bytes.toString('base64'),
|
|
371
|
+
mimeType: result.mimeType,
|
|
372
|
+
fileName: result.fileName ?? null,
|
|
373
|
+
});
|
|
374
|
+
process.exit(0);
|
|
375
|
+
}
|
|
376
|
+
case 'mark-read': {
|
|
377
|
+
const { cliMarkReadJson } = await load('cli/query');
|
|
378
|
+
const chatId = args[1];
|
|
379
|
+
const msgId = args[2];
|
|
380
|
+
if (!chatId) {
|
|
381
|
+
console.error(JSON.stringify({ error: 'chatId required' }));
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
const res = await cliMarkReadJson(chatId, msgId);
|
|
385
|
+
console.log(JSON.stringify(res));
|
|
386
|
+
process.exit(0);
|
|
387
|
+
}
|
|
388
|
+
case 'set-chat-muted': {
|
|
389
|
+
const chatId = args[1];
|
|
390
|
+
const disabled = args[2] === 'true';
|
|
391
|
+
if (!chatId?.startsWith('u') || !['true', 'false'].includes(args[2])) {
|
|
392
|
+
console.error(JSON.stringify({ error: '1:1 chatId and true|false required' }));
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
const { LineProtocolService } = await load('line/core/service');
|
|
396
|
+
const service = new LineProtocolService();
|
|
397
|
+
if (!(await service.resumeSession())) process.exit(1);
|
|
398
|
+
await service.client.updateContactNotificationDisabled(chatId, disabled);
|
|
399
|
+
console.log(JSON.stringify({ success: true, chatId, isMuted: disabled }));
|
|
400
|
+
process.exit(0);
|
|
401
|
+
}
|
|
402
|
+
case 'summary':
|
|
403
|
+
case 'recent':
|
|
404
|
+
case 'query': {
|
|
405
|
+
const { cliRecentSummary } = await load('cli/query');
|
|
406
|
+
const summary = await cliRecentSummary();
|
|
407
|
+
console.log(summary);
|
|
408
|
+
process.exit(0);
|
|
409
|
+
}
|
|
41
410
|
case 'serve':
|
|
42
411
|
await load('mcp/server');
|
|
43
412
|
break;
|