weflow-cli 1.17.0 → 1.19.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/CHANGELOG.md +30 -0
- package/README.md +3 -0
- package/bin/weflow-cli.ts +167 -0
- package/dist/bin/weflow-cli.js +199 -0
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/src/services/diagnosticReport.d.ts +6 -0
- package/dist/src/services/diagnosticReport.d.ts.map +1 -1
- package/dist/src/services/diagnosticReport.js +16 -0
- package/dist/src/services/diagnosticReport.js.map +1 -1
- package/dist/src/services/wechatCompat.d.ts +29 -0
- package/dist/src/services/wechatCompat.d.ts.map +1 -0
- package/dist/src/services/wechatCompat.js +65 -0
- package/dist/src/services/wechatCompat.js.map +1 -0
- package/docs/DATA_CONTRACT.md +25 -25
- package/docs/DECISIONS.md +26 -0
- package/docs/FUNCTIONAL-IMPROVEMENT-SPEC.md +365 -365
- package/docs/HEALTH-CHECK.md +104 -104
- package/docs/JEV/345/272/224/347/224/250/346/226/275/345/267/245/346/226/207/346/241/243.md +310 -310
- package/docs/NEXT_ACTIONS.md +236 -236
- package/docs/NPM-PUBLISH.md +130 -130
- package/docs/NT_DATABASES.md +47 -0
- package/docs/POSITIONING.md +68 -68
- package/docs/PROJECT_STATE.md +2 -1
- package/docs/RELEASING.md +4 -0
- package/package.json +1 -1
- package/requirements-3x.txt +4 -4
- package/requirements-voice.txt +15 -15
- package/resources/js/graph3d/NOTICE.txt +35 -35
- package/resources/js/graph3d/d3-binarytree.min.js +2 -2
- package/resources/js/graph3d/d3-dispatch.min.js +2 -2
- package/resources/js/graph3d/d3-force-3d.min.js +2 -2
- package/resources/js/graph3d/d3-octree.min.js +2 -2
- package/resources/js/graph3d/d3-quadtree.min.js +2 -2
- package/resources/js/graph3d/d3-timer.min.js +2 -2
- package/resources/js/graph3d/three.min.js +6 -6
- package/resources/panel/quick-menu.cjs +197 -197
- package/scripts/_utils.py +1338 -1338
- package/scripts/annual_report.py +3 -1
- package/scripts/article_notes.py +423 -423
- package/scripts/contact_schema.py +339 -339
- package/scripts/create_reading_notes.py +11 -11
- package/scripts/daily_task.ps1 +213 -213
- package/scripts/extract_todos.py +3 -1
- package/scripts/fav_notes.py +321 -321
- package/scripts/health_check.py +473 -473
- package/scripts/imported_chats.py +68 -68
- package/scripts/install_tasks.ps1 +105 -105
- package/scripts/leads.py +179 -179
- package/scripts/mcp_bridge.py +3 -1
- package/scripts/media_index.py +150 -0
- package/scripts/network.py +206 -206
- package/scripts/nt_common.py +32 -0
- package/scripts/nt_decrypt.py +19 -15
- package/scripts/opinion_timeline.py +138 -138
- package/scripts/replay.py +803 -803
- package/scripts/run_hidden.vbs +24 -24
- package/scripts/semantic_search.py +5 -1
- package/scripts/user_notes.py +315 -315
- package/scripts/voice_transcribe.py +139 -139
- package/scripts/watch_issues.py +212 -212
- package/scripts/watch_mail.py +131 -131
- package/scripts/wechat_avatar.py +185 -0
- package/scripts/wechat_ledger.py +190 -0
- package/src/services/cursorProbe.ts +222 -222
- package/src/services/diagnosticReport.ts +22 -0
- package/src/services/qchatImport.ts +327 -327
- package/src/services/setupVenv.ts +238 -238
- package/src/services/syncService.ts +363 -363
- package/src/services/syncState.ts +275 -275
- package/src/services/wechatCompat.ts +67 -0
- package/src/services/wechatMedia.ts +166 -166
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""读 `weflow-cli import qchat` 落下的存储(output/imports/qq/sessions/)。
|
|
3
|
-
|
|
4
|
-
**单一读者**:chat_notes 与 semantic_search 都从这里取导入会话。两边各解析一遍格式,
|
|
5
|
-
漂了就是"知识卡看得见、搜索看不见"这类静默分叉——`nt_common` 存在的同款理由。
|
|
6
|
-
|
|
7
|
-
存储形状(写入方是 `src/services/qchatImport.ts`,字段语义对齐 weflow-message/v1):
|
|
8
|
-
每个会话一个 JSON:schema/session{id,name,type}/importedAt/sourceFiles/messages[],
|
|
9
|
-
消息字段 serverId(=qchat messageId,全局唯一)/createTime(unix 秒)/isSend(可缺省=
|
|
10
|
-
未知)/senderUsername(QQ 号,敏感)/senderName/messageType(13 稳定名)/parsedContent/
|
|
11
|
-
rawContent。
|
|
12
|
-
|
|
13
|
-
路径:默认 `output/imports/qq/sessions`(cwd 相对,与 chat_notes 的 OUTPUT_ROOT 同一
|
|
14
|
-
约定);`WEFLOW_IMPORT_ROOT` 指到别处时用 `<root>/sessions`(测试用,TS 写入方读同
|
|
15
|
-
一个变量)。
|
|
16
|
-
"""
|
|
17
|
-
import json
|
|
18
|
-
import os
|
|
19
|
-
from pathlib import Path
|
|
20
|
-
|
|
21
|
-
SCHEMA = 'weflow-import/qq-v1'
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def sessions_dir(root=None):
|
|
25
|
-
if root is None:
|
|
26
|
-
root = os.environ.get('WEFLOW_IMPORT_ROOT')
|
|
27
|
-
if root:
|
|
28
|
-
return Path(root) / 'sessions'
|
|
29
|
-
return Path('output/imports/qq/sessions')
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def sessions(root=None):
|
|
33
|
-
"""全部已导入会话(按导入时间新→旧)。坏文件/非本 schema 的文件跳过不挡路。
|
|
34
|
-
|
|
35
|
-
返回的是存储原文(dict),消息已按 createTime 升序。
|
|
36
|
-
"""
|
|
37
|
-
d = sessions_dir(root)
|
|
38
|
-
if not d.is_dir():
|
|
39
|
-
return []
|
|
40
|
-
out = []
|
|
41
|
-
for p in sorted(d.glob('*.json')):
|
|
42
|
-
try:
|
|
43
|
-
data = json.loads(p.read_text(encoding='utf-8'))
|
|
44
|
-
except Exception:
|
|
45
|
-
continue
|
|
46
|
-
if not isinstance(data, dict):
|
|
47
|
-
continue
|
|
48
|
-
if data.get('schema') != SCHEMA or not isinstance(data.get('messages'), list):
|
|
49
|
-
continue
|
|
50
|
-
out.append(data)
|
|
51
|
-
out.sort(key=lambda s: s.get('importedAt') or '', reverse=True)
|
|
52
|
-
return out
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def session_messages(sess, days=None, now=None):
|
|
56
|
-
"""一个会话的消息,可选按"最近 days 天"过滤。
|
|
57
|
-
|
|
58
|
-
时间语义与 chat_notes/semantic_search 的窗口一致:unix 秒、含当天。
|
|
59
|
-
"""
|
|
60
|
-
from datetime import datetime, timedelta, timezone
|
|
61
|
-
|
|
62
|
-
msgs = [m for m in sess.get('messages', []) if isinstance(m, dict)]
|
|
63
|
-
if days is None:
|
|
64
|
-
return msgs
|
|
65
|
-
tz = timezone(timedelta(hours=8))
|
|
66
|
-
now = now or datetime.now(tz)
|
|
67
|
-
cutoff = int((now - timedelta(days=days)).timestamp())
|
|
68
|
-
return [m for m in msgs if (m.get('createTime') or 0) >= cutoff]
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""读 `weflow-cli import qchat` 落下的存储(output/imports/qq/sessions/)。
|
|
3
|
+
|
|
4
|
+
**单一读者**:chat_notes 与 semantic_search 都从这里取导入会话。两边各解析一遍格式,
|
|
5
|
+
漂了就是"知识卡看得见、搜索看不见"这类静默分叉——`nt_common` 存在的同款理由。
|
|
6
|
+
|
|
7
|
+
存储形状(写入方是 `src/services/qchatImport.ts`,字段语义对齐 weflow-message/v1):
|
|
8
|
+
每个会话一个 JSON:schema/session{id,name,type}/importedAt/sourceFiles/messages[],
|
|
9
|
+
消息字段 serverId(=qchat messageId,全局唯一)/createTime(unix 秒)/isSend(可缺省=
|
|
10
|
+
未知)/senderUsername(QQ 号,敏感)/senderName/messageType(13 稳定名)/parsedContent/
|
|
11
|
+
rawContent。
|
|
12
|
+
|
|
13
|
+
路径:默认 `output/imports/qq/sessions`(cwd 相对,与 chat_notes 的 OUTPUT_ROOT 同一
|
|
14
|
+
约定);`WEFLOW_IMPORT_ROOT` 指到别处时用 `<root>/sessions`(测试用,TS 写入方读同
|
|
15
|
+
一个变量)。
|
|
16
|
+
"""
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
SCHEMA = 'weflow-import/qq-v1'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def sessions_dir(root=None):
|
|
25
|
+
if root is None:
|
|
26
|
+
root = os.environ.get('WEFLOW_IMPORT_ROOT')
|
|
27
|
+
if root:
|
|
28
|
+
return Path(root) / 'sessions'
|
|
29
|
+
return Path('output/imports/qq/sessions')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def sessions(root=None):
|
|
33
|
+
"""全部已导入会话(按导入时间新→旧)。坏文件/非本 schema 的文件跳过不挡路。
|
|
34
|
+
|
|
35
|
+
返回的是存储原文(dict),消息已按 createTime 升序。
|
|
36
|
+
"""
|
|
37
|
+
d = sessions_dir(root)
|
|
38
|
+
if not d.is_dir():
|
|
39
|
+
return []
|
|
40
|
+
out = []
|
|
41
|
+
for p in sorted(d.glob('*.json')):
|
|
42
|
+
try:
|
|
43
|
+
data = json.loads(p.read_text(encoding='utf-8'))
|
|
44
|
+
except Exception:
|
|
45
|
+
continue
|
|
46
|
+
if not isinstance(data, dict):
|
|
47
|
+
continue
|
|
48
|
+
if data.get('schema') != SCHEMA or not isinstance(data.get('messages'), list):
|
|
49
|
+
continue
|
|
50
|
+
out.append(data)
|
|
51
|
+
out.sort(key=lambda s: s.get('importedAt') or '', reverse=True)
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def session_messages(sess, days=None, now=None):
|
|
56
|
+
"""一个会话的消息,可选按"最近 days 天"过滤。
|
|
57
|
+
|
|
58
|
+
时间语义与 chat_notes/semantic_search 的窗口一致:unix 秒、含当天。
|
|
59
|
+
"""
|
|
60
|
+
from datetime import datetime, timedelta, timezone
|
|
61
|
+
|
|
62
|
+
msgs = [m for m in sess.get('messages', []) if isinstance(m, dict)]
|
|
63
|
+
if days is None:
|
|
64
|
+
return msgs
|
|
65
|
+
tz = timezone(timedelta(hours=8))
|
|
66
|
+
now = now or datetime.now(tz)
|
|
67
|
+
cutoff = int((now - timedelta(days=days)).timestamp())
|
|
68
|
+
return [m for m in msgs if (m.get('createTime') or 0) >= cutoff]
|
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
# Register / update the two scheduled tasks for the official-account daily report.
|
|
2
|
-
# Idempotent: running it again overwrites the definitions with the same values.
|
|
3
|
-
#
|
|
4
|
-
# Why this cannot always be done for you: writing to Task Scheduler is refused in a
|
|
5
|
-
# restricted shell / agent sandbox, and refused as a *class* - measured on 2026-10-19 with
|
|
6
|
-
# an elevated Administrator token, all three write paths said "Access is denied" while
|
|
7
|
-
# reads kept working:
|
|
8
|
-
# schtasks /create ... -> Access is denied
|
|
9
|
-
# (New-Object -ComObject Schedule.Service) -> RegisterTaskDefinition 0x80070005
|
|
10
|
-
# New-ScheduledTaskAction / -SettingsSet ... -> 拒绝访问 (even the in-memory builders)
|
|
11
|
-
# So "I can see the tasks but cannot create them" is not an illusion, and it is not fixed
|
|
12
|
-
# by "run as administrator". The one reliable route is a terminal the sandbox does not
|
|
13
|
-
# supervise: double-click scripts\install_tasks.cmd (it asks for elevation itself), or run
|
|
14
|
-
# this file from your own PowerShell window.
|
|
15
|
-
#
|
|
16
|
-
# Usage:
|
|
17
|
-
# powershell -NoProfile -ExecutionPolicy Bypass -File <repo>\scripts\install_tasks.ps1
|
|
18
|
-
# powershell -NoProfile -ExecutionPolicy Bypass -File <repo>\scripts\install_tasks.ps1 -Uninstall
|
|
19
|
-
#
|
|
20
|
-
# The two tasks:
|
|
21
|
-
# WeFlow Daily 00:05 + at logon -> YESTERDAY (the last complete day)
|
|
22
|
-
# WeFlow Daily Noon 13:00 -> TODAY so far (half a day of pushes)
|
|
23
|
-
#
|
|
24
|
-
# Why 00:05 the next day instead of 24:00 tonight: it is the same instant, but "yesterday"
|
|
25
|
-
# then lines up with the date directory the run writes into, and it does not collide with
|
|
26
|
-
# other jobs on the hour. Official accounts keep pushing until 24:00, so this is the only
|
|
27
|
-
# moment that has the complete day.
|
|
28
|
-
#
|
|
29
|
-
# This file is intentionally ASCII-only: Windows PowerShell 5.1 reads a BOM-less file as
|
|
30
|
-
# the ANSI codepage, so non-ASCII text here turns into mojibake *and* a parse error.
|
|
31
|
-
param(
|
|
32
|
-
[switch]$Uninstall
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
$ErrorActionPreference = 'Stop'
|
|
36
|
-
|
|
37
|
-
$repo = Split-Path -Parent $PSScriptRoot
|
|
38
|
-
$script = Join-Path $repo 'scripts\daily_task.ps1'
|
|
39
|
-
if (-not (Test-Path $script)) { throw "not found: $script (this script belongs in the repo's scripts\ folder)" }
|
|
40
|
-
|
|
41
|
-
$isAdmin = (New-Object Security.Principal.WindowsPrincipal(
|
|
42
|
-
[Security.Principal.WindowsIdentity]::GetCurrent())
|
|
43
|
-
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
44
|
-
if (-not $isAdmin) {
|
|
45
|
-
Write-Host 'Need elevation: reopen PowerShell as administrator, then run this again.' -ForegroundColor Yellow
|
|
46
|
-
exit 1
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if ($Uninstall) {
|
|
50
|
-
foreach ($name in @('WeFlow Daily Noon', 'WeFlow Daily')) {
|
|
51
|
-
if (Get-ScheduledTask -TaskName $name -ErrorAction SilentlyContinue) {
|
|
52
|
-
Unregister-ScheduledTask -TaskName $name -Confirm:$false
|
|
53
|
-
Write-Host "removed: $name"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
exit 0
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -MultipleInstances IgnoreNew
|
|
60
|
-
$psExe = 'powershell.exe'
|
|
61
|
-
$common = '-NoProfile -ExecutionPolicy Bypass -File ' + $script
|
|
62
|
-
|
|
63
|
-
# Launch through wscript.exe, not powershell.exe directly.
|
|
64
|
-
#
|
|
65
|
-
# A scheduled action that runs powershell.exe is a *console* program: Windows creates the console
|
|
66
|
-
# window before PowerShell can hide it, so every run flashes a black box - and while a game is in
|
|
67
|
-
# the foreground that is a real interruption. wscript.exe is a GUI process with no console at all,
|
|
68
|
-
# and run_hidden.vbs starts PowerShell with window style 0, so nothing appears.
|
|
69
|
-
$wscript = Join-Path $env:SystemRoot 'System32\wscript.exe'
|
|
70
|
-
$vbs = Join-Path $PSScriptRoot 'run_hidden.vbs'
|
|
71
|
-
if (-not (Test-Path $vbs)) {
|
|
72
|
-
Write-Host "Missing launcher: $vbs" -ForegroundColor Red
|
|
73
|
-
exit 1
|
|
74
|
-
}
|
|
75
|
-
$action = '//B //Nologo "' + $vbs + '"'
|
|
76
|
-
|
|
77
|
-
# End of day: yesterday. The at-logon trigger is the catch-up: this machine slept for
|
|
78
|
-
# 37 hours and StartWhenAvailable did NOT actually start the missed run (NumberOfMissedRuns=2,
|
|
79
|
-
# no run record), so "it will catch up by itself" is not something to rely on.
|
|
80
|
-
Register-ScheduledTask -TaskName 'WeFlow Daily' `
|
|
81
|
-
-Action (New-ScheduledTaskAction -Execute $wscript -Argument $action) `
|
|
82
|
-
-Trigger @((New-ScheduledTaskTrigger -Daily -At '00:05'),
|
|
83
|
-
(New-ScheduledTaskTrigger -AtLogOn)) `
|
|
84
|
-
-Settings $settings `
|
|
85
|
-
-Description 'WeChat daily report for yesterday (00:05 + at logon, idempotent, no window)' -Force | Out-Null
|
|
86
|
-
|
|
87
|
-
# Noon: today so far. Deliberately NO at-logon trigger: a morning logon would produce a
|
|
88
|
-
# very thin "noon" report and write the .noon_done marker, which would then make the real
|
|
89
|
-
# 13:00 run skip itself (the guard keys on that marker).
|
|
90
|
-
Register-ScheduledTask -TaskName 'WeFlow Daily Noon' `
|
|
91
|
-
-Action (New-ScheduledTaskAction -Execute $wscript -Argument ($action + ' noon')) `
|
|
92
|
-
-Trigger (New-ScheduledTaskTrigger -Daily -At '13:00') `
|
|
93
|
-
-Settings $settings `
|
|
94
|
-
-Description 'WeChat daily report for today so far (13:00, no window)' -Force | Out-Null
|
|
95
|
-
|
|
96
|
-
foreach ($name in @('WeFlow Daily', 'WeFlow Daily Noon')) {
|
|
97
|
-
$t = Get-ScheduledTask -TaskName $name
|
|
98
|
-
$i = Get-ScheduledTaskInfo -TaskName $name
|
|
99
|
-
Write-Host ("{0,-20} {1,-8} next {2}" -f $t.TaskName, $t.State, $i.NextRunTime) -ForegroundColor Green
|
|
100
|
-
}
|
|
101
|
-
Write-Host ''
|
|
102
|
-
Write-Host 'Run one now instead of waiting:'
|
|
103
|
-
Write-Host ' Start-ScheduledTask -TaskName "WeFlow Daily Noon" # today so far'
|
|
104
|
-
Write-Host ' Start-ScheduledTask -TaskName "WeFlow Daily" # yesterday, complete'
|
|
105
|
-
Write-Host 'Logs: <repo>\logs\daily-<date>[-noon].log'
|
|
1
|
+
# Register / update the two scheduled tasks for the official-account daily report.
|
|
2
|
+
# Idempotent: running it again overwrites the definitions with the same values.
|
|
3
|
+
#
|
|
4
|
+
# Why this cannot always be done for you: writing to Task Scheduler is refused in a
|
|
5
|
+
# restricted shell / agent sandbox, and refused as a *class* - measured on 2026-10-19 with
|
|
6
|
+
# an elevated Administrator token, all three write paths said "Access is denied" while
|
|
7
|
+
# reads kept working:
|
|
8
|
+
# schtasks /create ... -> Access is denied
|
|
9
|
+
# (New-Object -ComObject Schedule.Service) -> RegisterTaskDefinition 0x80070005
|
|
10
|
+
# New-ScheduledTaskAction / -SettingsSet ... -> 拒绝访问 (even the in-memory builders)
|
|
11
|
+
# So "I can see the tasks but cannot create them" is not an illusion, and it is not fixed
|
|
12
|
+
# by "run as administrator". The one reliable route is a terminal the sandbox does not
|
|
13
|
+
# supervise: double-click scripts\install_tasks.cmd (it asks for elevation itself), or run
|
|
14
|
+
# this file from your own PowerShell window.
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# powershell -NoProfile -ExecutionPolicy Bypass -File <repo>\scripts\install_tasks.ps1
|
|
18
|
+
# powershell -NoProfile -ExecutionPolicy Bypass -File <repo>\scripts\install_tasks.ps1 -Uninstall
|
|
19
|
+
#
|
|
20
|
+
# The two tasks:
|
|
21
|
+
# WeFlow Daily 00:05 + at logon -> YESTERDAY (the last complete day)
|
|
22
|
+
# WeFlow Daily Noon 13:00 -> TODAY so far (half a day of pushes)
|
|
23
|
+
#
|
|
24
|
+
# Why 00:05 the next day instead of 24:00 tonight: it is the same instant, but "yesterday"
|
|
25
|
+
# then lines up with the date directory the run writes into, and it does not collide with
|
|
26
|
+
# other jobs on the hour. Official accounts keep pushing until 24:00, so this is the only
|
|
27
|
+
# moment that has the complete day.
|
|
28
|
+
#
|
|
29
|
+
# This file is intentionally ASCII-only: Windows PowerShell 5.1 reads a BOM-less file as
|
|
30
|
+
# the ANSI codepage, so non-ASCII text here turns into mojibake *and* a parse error.
|
|
31
|
+
param(
|
|
32
|
+
[switch]$Uninstall
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
$ErrorActionPreference = 'Stop'
|
|
36
|
+
|
|
37
|
+
$repo = Split-Path -Parent $PSScriptRoot
|
|
38
|
+
$script = Join-Path $repo 'scripts\daily_task.ps1'
|
|
39
|
+
if (-not (Test-Path $script)) { throw "not found: $script (this script belongs in the repo's scripts\ folder)" }
|
|
40
|
+
|
|
41
|
+
$isAdmin = (New-Object Security.Principal.WindowsPrincipal(
|
|
42
|
+
[Security.Principal.WindowsIdentity]::GetCurrent())
|
|
43
|
+
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
44
|
+
if (-not $isAdmin) {
|
|
45
|
+
Write-Host 'Need elevation: reopen PowerShell as administrator, then run this again.' -ForegroundColor Yellow
|
|
46
|
+
exit 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if ($Uninstall) {
|
|
50
|
+
foreach ($name in @('WeFlow Daily Noon', 'WeFlow Daily')) {
|
|
51
|
+
if (Get-ScheduledTask -TaskName $name -ErrorAction SilentlyContinue) {
|
|
52
|
+
Unregister-ScheduledTask -TaskName $name -Confirm:$false
|
|
53
|
+
Write-Host "removed: $name"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exit 0
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -MultipleInstances IgnoreNew
|
|
60
|
+
$psExe = 'powershell.exe'
|
|
61
|
+
$common = '-NoProfile -ExecutionPolicy Bypass -File ' + $script
|
|
62
|
+
|
|
63
|
+
# Launch through wscript.exe, not powershell.exe directly.
|
|
64
|
+
#
|
|
65
|
+
# A scheduled action that runs powershell.exe is a *console* program: Windows creates the console
|
|
66
|
+
# window before PowerShell can hide it, so every run flashes a black box - and while a game is in
|
|
67
|
+
# the foreground that is a real interruption. wscript.exe is a GUI process with no console at all,
|
|
68
|
+
# and run_hidden.vbs starts PowerShell with window style 0, so nothing appears.
|
|
69
|
+
$wscript = Join-Path $env:SystemRoot 'System32\wscript.exe'
|
|
70
|
+
$vbs = Join-Path $PSScriptRoot 'run_hidden.vbs'
|
|
71
|
+
if (-not (Test-Path $vbs)) {
|
|
72
|
+
Write-Host "Missing launcher: $vbs" -ForegroundColor Red
|
|
73
|
+
exit 1
|
|
74
|
+
}
|
|
75
|
+
$action = '//B //Nologo "' + $vbs + '"'
|
|
76
|
+
|
|
77
|
+
# End of day: yesterday. The at-logon trigger is the catch-up: this machine slept for
|
|
78
|
+
# 37 hours and StartWhenAvailable did NOT actually start the missed run (NumberOfMissedRuns=2,
|
|
79
|
+
# no run record), so "it will catch up by itself" is not something to rely on.
|
|
80
|
+
Register-ScheduledTask -TaskName 'WeFlow Daily' `
|
|
81
|
+
-Action (New-ScheduledTaskAction -Execute $wscript -Argument $action) `
|
|
82
|
+
-Trigger @((New-ScheduledTaskTrigger -Daily -At '00:05'),
|
|
83
|
+
(New-ScheduledTaskTrigger -AtLogOn)) `
|
|
84
|
+
-Settings $settings `
|
|
85
|
+
-Description 'WeChat daily report for yesterday (00:05 + at logon, idempotent, no window)' -Force | Out-Null
|
|
86
|
+
|
|
87
|
+
# Noon: today so far. Deliberately NO at-logon trigger: a morning logon would produce a
|
|
88
|
+
# very thin "noon" report and write the .noon_done marker, which would then make the real
|
|
89
|
+
# 13:00 run skip itself (the guard keys on that marker).
|
|
90
|
+
Register-ScheduledTask -TaskName 'WeFlow Daily Noon' `
|
|
91
|
+
-Action (New-ScheduledTaskAction -Execute $wscript -Argument ($action + ' noon')) `
|
|
92
|
+
-Trigger (New-ScheduledTaskTrigger -Daily -At '13:00') `
|
|
93
|
+
-Settings $settings `
|
|
94
|
+
-Description 'WeChat daily report for today so far (13:00, no window)' -Force | Out-Null
|
|
95
|
+
|
|
96
|
+
foreach ($name in @('WeFlow Daily', 'WeFlow Daily Noon')) {
|
|
97
|
+
$t = Get-ScheduledTask -TaskName $name
|
|
98
|
+
$i = Get-ScheduledTaskInfo -TaskName $name
|
|
99
|
+
Write-Host ("{0,-20} {1,-8} next {2}" -f $t.TaskName, $t.State, $i.NextRunTime) -ForegroundColor Green
|
|
100
|
+
}
|
|
101
|
+
Write-Host ''
|
|
102
|
+
Write-Host 'Run one now instead of waiting:'
|
|
103
|
+
Write-Host ' Start-ScheduledTask -TaskName "WeFlow Daily Noon" # today so far'
|
|
104
|
+
Write-Host ' Start-ScheduledTask -TaskName "WeFlow Daily" # yesterday, complete'
|
|
105
|
+
Write-Host 'Logs: <repo>\logs\daily-<date>[-noon].log'
|