@jshookmcp/jshook 0.2.5 → 0.2.6
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 +5 -5
- package/README.zh.md +5 -5
- package/dist/packages/extension-sdk/src/workflow.d.ts +17 -2
- package/dist/packages/extension-sdk/src/workflow.js +36 -0
- package/dist/src/modules/browser/BrowserPool.d.ts +49 -0
- package/dist/src/modules/browser/BrowserPool.js +288 -0
- package/dist/src/modules/deobfuscator/AdvancedDeobfuscator.d.ts +5 -0
- package/dist/src/modules/deobfuscator/AdvancedDeobfuscator.js +43 -2
- package/dist/src/modules/deobfuscator/Deobfuscator.js +5 -0
- package/dist/src/modules/external/ExternalToolRunner.js +1 -1
- package/dist/src/server/MCPServer.context.d.ts +1 -0
- package/dist/src/server/domains/browser/handlers/stealth-injection.d.ts +1 -0
- package/dist/src/server/domains/browser/handlers/stealth-injection.js +3 -0
- package/dist/src/server/domains/shared-state-board/definitions.d.ts +2 -0
- package/dist/src/server/domains/shared-state-board/definitions.js +78 -0
- package/dist/src/server/domains/shared-state-board/handlers.impl.d.ts +58 -0
- package/dist/src/server/domains/shared-state-board/handlers.impl.js +419 -0
- package/dist/src/server/domains/shared-state-board/index.d.ts +2 -0
- package/dist/src/server/domains/shared-state-board/index.js +2 -0
- package/dist/src/server/domains/shared-state-board/manifest.d.ts +57 -0
- package/dist/src/server/domains/shared-state-board/manifest.js +74 -0
- package/dist/src/server/http/SseStream.d.ts +21 -0
- package/dist/src/server/http/SseStream.js +129 -0
- package/dist/src/server/teams/TeamManager.d.ts +43 -0
- package/dist/src/server/teams/TeamManager.js +238 -0
- package/dist/src/server/teams/index.d.ts +1 -0
- package/dist/src/server/teams/index.js +1 -0
- package/dist/src/server/workflows/WorkflowContract.d.ts +20 -4
- package/dist/src/server/workflows/WorkflowContract.js +40 -0
- package/dist/src/server/workflows/WorkflowEngine.js +190 -13
- package/dist/src/types/deobfuscator.d.ts +1 -0
- package/dist/src/utils/cache/CachedDecorator.d.ts +8 -0
- package/dist/src/utils/cache/CachedDecorator.js +55 -0
- package/dist/src/utils/cache/PersistentCache.d.ts +33 -0
- package/dist/src/utils/cache/PersistentCache.js +246 -0
- package/dist/src/utils/cache/index.d.ts +2 -0
- package/dist/src/utils/cache/index.js +2 -0
- package/package.json +11 -12
- package/scripts/postinstall.cjs +54 -27
- package/workflows/anti-bot-diagnoser/.jshook-install.json +14 -0
- package/workflows/anti-bot-diagnoser/LICENSE +21 -0
- package/workflows/anti-bot-diagnoser/README.md +105 -0
- package/workflows/anti-bot-diagnoser/docs/agent-recipes.md +44 -0
- package/workflows/anti-bot-diagnoser/meta.yaml +6 -0
- package/workflows/anti-bot-diagnoser/package.json +22 -0
- package/workflows/anti-bot-diagnoser/tsconfig.json +15 -0
- package/workflows/anti-bot-diagnoser/workflow.ts +224 -0
- package/workflows/api-openapi-probe/.jshook-install.json +14 -0
- package/workflows/api-openapi-probe/meta.yaml +6 -0
- package/workflows/api-openapi-probe/package.json +22 -0
- package/workflows/api-openapi-probe/pnpm-lock.yaml +819 -0
- package/workflows/api-openapi-probe/tsconfig.json +15 -0
- package/workflows/api-openapi-probe/workflow.ts +40 -0
- package/workflows/api-probe-batch/.jshook-install.json +14 -0
- package/workflows/api-probe-batch/LICENSE +21 -0
- package/workflows/api-probe-batch/README.md +45 -0
- package/workflows/api-probe-batch/meta.yaml +4 -0
- package/workflows/api-probe-batch/package.json +23 -0
- package/workflows/api-probe-batch/tsconfig.json +16 -0
- package/workflows/api-probe-batch/workflow.ts +111 -0
- package/workflows/auth-bootstrap/.jshook-install.json +14 -0
- package/workflows/auth-bootstrap/LICENSE +21 -0
- package/workflows/auth-bootstrap/README.md +74 -0
- package/workflows/auth-bootstrap/meta.yaml +4 -0
- package/workflows/auth-bootstrap/package.json +23 -0
- package/workflows/auth-bootstrap/tsconfig.json +16 -0
- package/workflows/auth-bootstrap/workflow.ts +141 -0
- package/workflows/auth-extract/.jshook-install.json +14 -0
- package/workflows/auth-extract/meta.yaml +6 -0
- package/workflows/auth-extract/package.json +22 -0
- package/workflows/auth-extract/pnpm-lock.yaml +819 -0
- package/workflows/auth-extract/tsconfig.json +15 -0
- package/workflows/auth-extract/workflow.ts +36 -0
- package/workflows/auth-surface-mapper/.jshook-install.json +14 -0
- package/workflows/auth-surface-mapper/meta.yaml +6 -0
- package/workflows/auth-surface-mapper/package.json +22 -0
- package/workflows/auth-surface-mapper/pnpm-lock.yaml +819 -0
- package/workflows/auth-surface-mapper/tsconfig.json +15 -0
- package/workflows/auth-surface-mapper/workflow.ts +104 -0
- package/workflows/batch-register/.jshook-install.json +14 -0
- package/workflows/batch-register/LICENSE +21 -0
- package/workflows/batch-register/README.md +39 -0
- package/workflows/batch-register/meta.yaml +4 -0
- package/workflows/batch-register/package.json +23 -0
- package/workflows/batch-register/tsconfig.json +16 -0
- package/workflows/batch-register/workflow.ts +67 -0
- package/workflows/bundle-recovery/.jshook-install.json +14 -0
- package/workflows/bundle-recovery/LICENSE +21 -0
- package/workflows/bundle-recovery/README.md +105 -0
- package/workflows/bundle-recovery/docs/agent-recipes.md +44 -0
- package/workflows/bundle-recovery/meta.yaml +6 -0
- package/workflows/bundle-recovery/package.json +22 -0
- package/workflows/bundle-recovery/tsconfig.json +15 -0
- package/workflows/bundle-recovery/workflow.ts +179 -0
- package/workflows/challenge-detector/.jshook-install.json +14 -0
- package/workflows/challenge-detector/meta.yaml +14 -0
- package/workflows/challenge-detector/package.json +22 -0
- package/workflows/challenge-detector/pnpm-lock.yaml +819 -0
- package/workflows/challenge-detector/tsconfig.json +15 -0
- package/workflows/challenge-detector/workflow.ts +298 -0
- package/workflows/deobfuscation-pipeline/.jshook-install.json +14 -0
- package/workflows/deobfuscation-pipeline/meta.yaml +6 -0
- package/workflows/deobfuscation-pipeline/package.json +22 -0
- package/workflows/deobfuscation-pipeline/pnpm-lock.yaml +819 -0
- package/workflows/deobfuscation-pipeline/tsconfig.json +15 -0
- package/workflows/deobfuscation-pipeline/workflow.ts +119 -0
- package/workflows/electron-bridge-mapper/.jshook-install.json +14 -0
- package/workflows/electron-bridge-mapper/meta.yaml +6 -0
- package/workflows/electron-bridge-mapper/package.json +22 -0
- package/workflows/electron-bridge-mapper/pnpm-lock.yaml +819 -0
- package/workflows/electron-bridge-mapper/tsconfig.json +15 -0
- package/workflows/electron-bridge-mapper/workflow.ts +125 -0
- package/workflows/evidence-pack/.jshook-install.json +14 -0
- package/workflows/evidence-pack/LICENSE +21 -0
- package/workflows/evidence-pack/README.md +105 -0
- package/workflows/evidence-pack/docs/agent-recipes.md +44 -0
- package/workflows/evidence-pack/meta.yaml +6 -0
- package/workflows/evidence-pack/package.json +22 -0
- package/workflows/evidence-pack/tsconfig.json +15 -0
- package/workflows/evidence-pack/workflow.ts +154 -0
- package/workflows/js-bundle-search/.jshook-install.json +14 -0
- package/workflows/js-bundle-search/LICENSE +21 -0
- package/workflows/js-bundle-search/README.md +46 -0
- package/workflows/js-bundle-search/meta.yaml +4 -0
- package/workflows/js-bundle-search/package.json +23 -0
- package/workflows/js-bundle-search/tsconfig.json +16 -0
- package/workflows/js-bundle-search/workflow.ts +118 -0
- package/workflows/protocol-registry/.jshook-install.json +14 -0
- package/workflows/protocol-registry/meta.yaml +6 -0
- package/workflows/protocol-registry/package.json +22 -0
- package/workflows/protocol-registry/pnpm-lock.yaml +819 -0
- package/workflows/protocol-registry/tsconfig.json +15 -0
- package/workflows/protocol-registry/workflow.ts +107 -0
- package/workflows/qwen-mail-open-latest/meta.yaml +7 -0
- package/workflows/qwen-mail-open-latest/package.json +22 -0
- package/workflows/qwen-mail-open-latest/pnpm-lock.yaml +819 -0
- package/workflows/qwen-mail-open-latest/tsconfig.json +15 -0
- package/workflows/qwen-mail-open-latest/workflow.ts +77 -0
- package/workflows/register-account-flow/.jshook-install.json +14 -0
- package/workflows/register-account-flow/LICENSE +21 -0
- package/workflows/register-account-flow/README.md +64 -0
- package/workflows/register-account-flow/meta.yaml +4 -0
- package/workflows/register-account-flow/package.json +23 -0
- package/workflows/register-account-flow/tsconfig.json +16 -0
- package/workflows/register-account-flow/workflow.ts +127 -0
- package/workflows/replay-lab/.jshook-install.json +14 -0
- package/workflows/replay-lab/meta.yaml +6 -0
- package/workflows/replay-lab/package.json +22 -0
- package/workflows/replay-lab/pnpm-lock.yaml +819 -0
- package/workflows/replay-lab/tsconfig.json +15 -0
- package/workflows/replay-lab/workflow.ts +106 -0
- package/workflows/script-evidence-scan/.jshook-install.json +14 -0
- package/workflows/script-evidence-scan/LICENSE +21 -0
- package/workflows/script-evidence-scan/README.md +61 -0
- package/workflows/script-evidence-scan/meta.yaml +4 -0
- package/workflows/script-evidence-scan/package.json +23 -0
- package/workflows/script-evidence-scan/tsconfig.json +16 -0
- package/workflows/script-evidence-scan/workflow.ts +89 -0
- package/workflows/signature-hunter/.jshook-install.json +14 -0
- package/workflows/signature-hunter/LICENSE +21 -0
- package/workflows/signature-hunter/README.md +105 -0
- package/workflows/signature-hunter/docs/agent-recipes.md +44 -0
- package/workflows/signature-hunter/meta.yaml +6 -0
- package/workflows/signature-hunter/package.json +22 -0
- package/workflows/signature-hunter/tsconfig.json +15 -0
- package/workflows/signature-hunter/workflow.ts +170 -0
- package/workflows/signing-lineage/.jshook-install.json +14 -0
- package/workflows/signing-lineage/meta.yaml +6 -0
- package/workflows/signing-lineage/package.json +22 -0
- package/workflows/signing-lineage/pnpm-lock.yaml +819 -0
- package/workflows/signing-lineage/tsconfig.json +15 -0
- package/workflows/signing-lineage/workflow.ts +120 -0
- package/workflows/temp-mail-extract-link/.jshook-install.json +14 -0
- package/workflows/temp-mail-extract-link/LICENSE +21 -0
- package/workflows/temp-mail-extract-link/README.md +71 -0
- package/workflows/temp-mail-extract-link/meta.yaml +4 -0
- package/workflows/temp-mail-extract-link/package.json +23 -0
- package/workflows/temp-mail-extract-link/tsconfig.json +16 -0
- package/workflows/temp-mail-extract-link/workflow.ts +221 -0
- package/workflows/temp-mail-open-latest/.jshook-install.json +14 -0
- package/workflows/temp-mail-open-latest/LICENSE +21 -0
- package/workflows/temp-mail-open-latest/README.md +61 -0
- package/workflows/temp-mail-open-latest/meta.yaml +4 -0
- package/workflows/temp-mail-open-latest/package.json +23 -0
- package/workflows/temp-mail-open-latest/tsconfig.json +16 -0
- package/workflows/temp-mail-open-latest/workflow.ts +136 -0
- package/workflows/template/.jshook-install.json +14 -0
- package/workflows/template/LICENSE +21 -0
- package/workflows/template/README.md +45 -0
- package/workflows/template/docs/SKILL.md +111 -0
- package/workflows/template/meta.yaml +6 -0
- package/workflows/template/package.json +22 -0
- package/workflows/template/pnpm-lock.yaml +819 -0
- package/workflows/template/tsconfig.json +15 -0
- package/workflows/template/workflow.ts +73 -0
- package/workflows/web-api-capture-session/.jshook-install.json +14 -0
- package/workflows/web-api-capture-session/LICENSE +21 -0
- package/workflows/web-api-capture-session/README.md +64 -0
- package/workflows/web-api-capture-session/meta.yaml +4 -0
- package/workflows/web-api-capture-session/package.json +23 -0
- package/workflows/web-api-capture-session/tsconfig.json +16 -0
- package/workflows/web-api-capture-session/workflow.ts +124 -0
- package/workflows/ws-protocol-lifter/.jshook-install.json +14 -0
- package/workflows/ws-protocol-lifter/LICENSE +21 -0
- package/workflows/ws-protocol-lifter/README.md +105 -0
- package/workflows/ws-protocol-lifter/docs/agent-recipes.md +44 -0
- package/workflows/ws-protocol-lifter/meta.yaml +6 -0
- package/workflows/ws-protocol-lifter/package.json +22 -0
- package/workflows/ws-protocol-lifter/tsconfig.json +15 -0
- package/workflows/ws-protocol-lifter/workflow.ts +163 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"noEmit": false,
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"rootDir": ".",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"types": ["node"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["workflow.ts"],
|
|
14
|
+
"exclude": ["dist", "node_modules"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createWorkflow,
|
|
3
|
+
type WorkflowExecutionContext,
|
|
4
|
+
SequenceNodeBuilder,
|
|
5
|
+
} from '@jshookmcp/extension-sdk/workflow';
|
|
6
|
+
|
|
7
|
+
const workflowId = 'workflow.signing-lineage.v1';
|
|
8
|
+
|
|
9
|
+
export default createWorkflow(workflowId, 'Signing Lineage')
|
|
10
|
+
.description(
|
|
11
|
+
'Traces the full signing lineage from ciphertext back to plaintext: intercepts signed requests, locates the signing function via initiator stacks, extracts the parameter normalization chain, hooks intermediate transforms, and captures the complete plaintext → normalize → concat → hash/encrypt → inject pipeline.',
|
|
12
|
+
)
|
|
13
|
+
.tags(['reverse', 'signature', 'lineage', 'crypto', 'trace', 'hook', 'parameter', 'mission'])
|
|
14
|
+
.timeoutMs(12 * 60_000)
|
|
15
|
+
.defaultMaxConcurrency(3)
|
|
16
|
+
.buildGraph((ctx: WorkflowExecutionContext) => {
|
|
17
|
+
const prefix = 'workflows.signingLineage';
|
|
18
|
+
const url = String(ctx.getConfig(`${prefix}.url`, 'https://example.com'));
|
|
19
|
+
const waitUntil = String(ctx.getConfig(`${prefix}.waitUntil`, 'networkidle0'));
|
|
20
|
+
const targetParam = String(ctx.getConfig(`${prefix}.targetParam`, 'sign'));
|
|
21
|
+
const requestTail = Number(ctx.getConfig(`${prefix}.requestTail`, 30));
|
|
22
|
+
const traceDepth = Number(ctx.getConfig(`${prefix}.traceDepth`, 5));
|
|
23
|
+
const searchKeywords = String(
|
|
24
|
+
ctx.getConfig(
|
|
25
|
+
`${prefix}.searchKeywords`,
|
|
26
|
+
'sign,signature,encrypt,hmac,md5,sha,hash,token,digest,secret,key,iv,nonce',
|
|
27
|
+
),
|
|
28
|
+
);
|
|
29
|
+
const maxConcurrency = Number(ctx.getConfig(`${prefix}.parallel.maxConcurrency`, 3));
|
|
30
|
+
|
|
31
|
+
const root = new SequenceNodeBuilder('signing-lineage-root');
|
|
32
|
+
|
|
33
|
+
root
|
|
34
|
+
// Phase 1: Network Setup & Navigate
|
|
35
|
+
.tool('enable-network', 'network_enable', { input: { enableExceptions: true } })
|
|
36
|
+
.tool('navigate', 'page_navigate', { input: { url, waitUntil } })
|
|
37
|
+
|
|
38
|
+
// Phase 2: Initial Request Capture
|
|
39
|
+
.tool('capture-requests', 'network_get_requests', { input: { tail: requestTail } })
|
|
40
|
+
|
|
41
|
+
// Phase 3: Parallel Source Analysis
|
|
42
|
+
.parallel('analyse-sources', (p) => {
|
|
43
|
+
p.maxConcurrency(maxConcurrency)
|
|
44
|
+
.failFast(false)
|
|
45
|
+
.tool('search-signing', 'search_in_scripts', {
|
|
46
|
+
input: { query: searchKeywords, matchType: 'any' },
|
|
47
|
+
})
|
|
48
|
+
.tool('detect-crypto', 'detect_crypto', { input: {} })
|
|
49
|
+
.tool('collect-code', 'collect_code', { input: { includeInline: true, limit: 30 } });
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// Phase 4: Deep Function Tree — find the signing entry point
|
|
53
|
+
.tool('extract-signing-tree', 'extract_function_tree', {
|
|
54
|
+
input: { targetParam, depth: traceDepth },
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// Phase 5: Inject Function Tracer on signing path
|
|
58
|
+
.tool('inject-tracer', 'console_inject_function_tracer', {
|
|
59
|
+
input: { persistent: false },
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// Phase 6: Hook the signing chain at multiple levels
|
|
63
|
+
.tool('hook-signing', 'manage_hooks', {
|
|
64
|
+
input: { action: 'add', targetParam, captureArgs: true, captureReturn: true },
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Phase 7: Interceptor injection for XHR/Fetch to catch signed requests
|
|
68
|
+
.tool('inject-xhr-interceptor', 'console_inject_xhr_interceptor', {
|
|
69
|
+
input: { persistent: false },
|
|
70
|
+
})
|
|
71
|
+
.tool('inject-fetch-interceptor', 'console_inject_fetch_interceptor', {
|
|
72
|
+
input: { persistent: false },
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// Phase 8: Re-capture after instrumentation
|
|
76
|
+
.tool('recapture-requests', 'network_get_requests', { input: { tail: requestTail } })
|
|
77
|
+
|
|
78
|
+
// Phase 9: Auth Surface Extraction
|
|
79
|
+
.tool('extract-auth', 'network_extract_auth', { input: { minConfidence: 0.2 } })
|
|
80
|
+
|
|
81
|
+
// Phase 10: Evidence Recording
|
|
82
|
+
.tool('create-evidence-session', 'instrumentation_session_create', {
|
|
83
|
+
input: {
|
|
84
|
+
name: `signing-lineage-${targetParam}-${new Date().toISOString().slice(0, 10)}`,
|
|
85
|
+
metadata: { url, targetParam, workflowId },
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
.tool('record-artifact', 'instrumentation_artifact_record', {
|
|
89
|
+
input: {
|
|
90
|
+
type: 'signing_lineage',
|
|
91
|
+
label: `Signing lineage for "${targetParam}" on ${url}`,
|
|
92
|
+
metadata: { url, targetParam, traceDepth },
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Phase 11: Session Insight
|
|
97
|
+
.tool('emit-insight', 'append_session_insight', {
|
|
98
|
+
input: {
|
|
99
|
+
insight: JSON.stringify({
|
|
100
|
+
status: 'signing_lineage_complete',
|
|
101
|
+
workflowId,
|
|
102
|
+
url,
|
|
103
|
+
targetParam,
|
|
104
|
+
traceDepth,
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return root;
|
|
110
|
+
})
|
|
111
|
+
.onStart((ctx) => {
|
|
112
|
+
ctx.emitMetric('workflow_runs_total', 1, 'counter', { workflowId, mission: 'signing_lineage', stage: 'start' });
|
|
113
|
+
})
|
|
114
|
+
.onFinish((ctx) => {
|
|
115
|
+
ctx.emitMetric('workflow_runs_total', 1, 'counter', { workflowId, mission: 'signing_lineage', stage: 'finish' });
|
|
116
|
+
})
|
|
117
|
+
.onError((ctx, error) => {
|
|
118
|
+
ctx.emitMetric('workflow_errors_total', 1, 'counter', { workflowId, mission: 'signing_lineage', stage: 'error', error: error.name });
|
|
119
|
+
})
|
|
120
|
+
.build();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"kind": "workflow",
|
|
4
|
+
"slug": "temp-mail-extract-link",
|
|
5
|
+
"id": "workflow.temp-mail-extract-link.v1",
|
|
6
|
+
"source": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"repo": "https://github.com/vmoranv/jshook_workflow_temp_mail_extract_link",
|
|
9
|
+
"ref": "main",
|
|
10
|
+
"commit": "84c14de",
|
|
11
|
+
"subpath": ".",
|
|
12
|
+
"entry": "workflow.ts"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vmoranv
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# temp-mail-extract-link workflow
|
|
2
|
+
|
|
3
|
+
Declarative workflow for extracting activation / verification links from a temporary-mail detail page. It is generic and driven by selectors plus href/text filters rather than any Qwen-specific assumptions.
|
|
4
|
+
|
|
5
|
+
## Entry File
|
|
6
|
+
|
|
7
|
+
- `workflow.ts`
|
|
8
|
+
|
|
9
|
+
## Workflow ID
|
|
10
|
+
|
|
11
|
+
- `workflow.temp-mail-extract-link.v1`
|
|
12
|
+
|
|
13
|
+
## Structure
|
|
14
|
+
|
|
15
|
+
This workflow wraps generic browser primitives into a reusable mail-detail routine:
|
|
16
|
+
|
|
17
|
+
- Optional `page_navigate` to a mail detail page
|
|
18
|
+
- Initial wait after navigation
|
|
19
|
+
- Retry-aware `page_evaluate` extraction step that:
|
|
20
|
+
- waits through redirect/challenge style titles
|
|
21
|
+
- checks optional `readySelector` / `readyText`
|
|
22
|
+
- extracts matched links
|
|
23
|
+
- optionally returns fallback link dumps
|
|
24
|
+
- optionally auto-opens the first matched link
|
|
25
|
+
- Optional post-open wait step
|
|
26
|
+
- `console_execute` summary step for downstream orchestration
|
|
27
|
+
|
|
28
|
+
## Tools Used
|
|
29
|
+
|
|
30
|
+
- `page_navigate`
|
|
31
|
+
- `page_evaluate`
|
|
32
|
+
- `console_execute`
|
|
33
|
+
|
|
34
|
+
## Config
|
|
35
|
+
|
|
36
|
+
Prefix: `workflows.tempMailExtractLink.*`
|
|
37
|
+
|
|
38
|
+
- `detailUrl`
|
|
39
|
+
- `waitUntil`
|
|
40
|
+
- `initialWaitMs`
|
|
41
|
+
- `retryWaitMs`
|
|
42
|
+
- `maxWaitAttempts`
|
|
43
|
+
- `readySelector`
|
|
44
|
+
- `readyText`
|
|
45
|
+
- `titleBlocklist`
|
|
46
|
+
- `linkSelector`
|
|
47
|
+
- `hrefIncludes`
|
|
48
|
+
- `textIncludes`
|
|
49
|
+
- `regexPattern`
|
|
50
|
+
- `regexFlags`
|
|
51
|
+
- `maxLinks`
|
|
52
|
+
- `includeFallbackLinks`
|
|
53
|
+
- `fallbackMaxLinks`
|
|
54
|
+
- `openFirstMatch`
|
|
55
|
+
- `waitAfterOpenMs`
|
|
56
|
+
|
|
57
|
+
## Example Use Cases
|
|
58
|
+
|
|
59
|
+
- Extract `/api/v1/auths/activate` links from a temporary mailbox
|
|
60
|
+
- Extract generic `/verify` or `/confirm` links from a magic-link email
|
|
61
|
+
- Run on a page that briefly shows `Redirecting` before actual content loads
|
|
62
|
+
- Return fallback link dumps when no verification link is found yet
|
|
63
|
+
|
|
64
|
+
## Local Validation
|
|
65
|
+
|
|
66
|
+
1. Run `pnpm install`.
|
|
67
|
+
2. Run `pnpm typecheck`.
|
|
68
|
+
3. Put this repo under a configured `workflows/` extension root.
|
|
69
|
+
4. Run `extensions_reload` in `jshookmcp`.
|
|
70
|
+
5. Confirm the workflow appears in `list_extension_workflows`.
|
|
71
|
+
6. Execute the workflow on a mail detail page and verify that the extraction result includes either matched links or fallback links.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jshookmcpextension/workflow-temp-mail-extract-link",
|
|
3
|
+
"scripts": {
|
|
4
|
+
"build": "tsc -p tsconfig.json",
|
|
5
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
6
|
+
},
|
|
7
|
+
"type": "module",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@jshookmcp/extension-sdk": "^0.3.0",
|
|
10
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
11
|
+
"dotenv": "^17.3.1"
|
|
12
|
+
},
|
|
13
|
+
"version": "0.1.0",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=20.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^25.3.0",
|
|
19
|
+
"typescript": "^5.9.3"
|
|
20
|
+
},
|
|
21
|
+
"private": true,
|
|
22
|
+
"packageManager": "pnpm@10.28.2"
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"rootDir": ".",
|
|
12
|
+
"outDir": "dist"
|
|
13
|
+
},
|
|
14
|
+
"include": ["workflow.ts"],
|
|
15
|
+
"exclude": ["dist", "node_modules"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { WorkflowContract } from '@jshookmcp/extension-sdk/workflow';
|
|
2
|
+
import { toolNode, sequenceNode, branchNode } from '@jshookmcp/extension-sdk/workflow';
|
|
3
|
+
|
|
4
|
+
const workflowId = 'workflow.temp-mail-extract-link.v1';
|
|
5
|
+
|
|
6
|
+
const workflow: WorkflowContract = {
|
|
7
|
+
kind: 'workflow-contract',
|
|
8
|
+
version: 1,
|
|
9
|
+
id: workflowId,
|
|
10
|
+
displayName: 'Temp Mail Extract Link',
|
|
11
|
+
description:
|
|
12
|
+
'Navigate or reuse a temp-mail detail page, wait through redirects/challenges, scan main document and accessible iframes, classify fallback links, and optionally auto-open the first verification match.',
|
|
13
|
+
tags: ['workflow', 'mail', 'temp-mail', 'verification', 'link-extract'],
|
|
14
|
+
timeoutMs: 3 * 60_000,
|
|
15
|
+
defaultMaxConcurrency: 1,
|
|
16
|
+
|
|
17
|
+
build(ctx) {
|
|
18
|
+
const prefix = 'workflows.tempMailExtractLink';
|
|
19
|
+
const detailUrl = ctx.getConfig<string>(`${prefix}.detailUrl`, '');
|
|
20
|
+
const waitUntil = ctx.getConfig<string>(`${prefix}.waitUntil`, 'domcontentloaded');
|
|
21
|
+
const initialWaitMs = ctx.getConfig<number>(`${prefix}.initialWaitMs`, 1200);
|
|
22
|
+
const retryWaitMs = ctx.getConfig<number>(`${prefix}.retryWaitMs`, 1000);
|
|
23
|
+
const maxWaitAttempts = ctx.getConfig<number>(`${prefix}.maxWaitAttempts`, 5);
|
|
24
|
+
const readySelector = ctx.getConfig<string>(`${prefix}.readySelector`, '');
|
|
25
|
+
const readyText = ctx.getConfig<string>(`${prefix}.readyText`, '');
|
|
26
|
+
const titleBlocklist = ctx.getConfig<string[]>(`${prefix}.titleBlocklist`, ['Redirecting']);
|
|
27
|
+
const bodyBlocklist = ctx.getConfig<string[]>(`${prefix}.bodyBlocklist`, ['Checking your browser', 'Just a moment']);
|
|
28
|
+
const expectedContextHints = ctx.getConfig<string[]>(`${prefix}.expectedContextHints`, ['邮件', 'mail', '发件人', 'subject']);
|
|
29
|
+
const linkSelector = ctx.getConfig<string>(`${prefix}.linkSelector`, 'a');
|
|
30
|
+
const hrefIncludes = ctx.getConfig<string[]>(`${prefix}.hrefIncludes`, []);
|
|
31
|
+
const textIncludes = ctx.getConfig<string[]>(`${prefix}.textIncludes`, []);
|
|
32
|
+
const regexPattern = ctx.getConfig<string>(`${prefix}.regexPattern`, '');
|
|
33
|
+
const regexFlags = ctx.getConfig<string>(`${prefix}.regexFlags`, 'i');
|
|
34
|
+
const maxLinks = ctx.getConfig<number>(`${prefix}.maxLinks`, 20);
|
|
35
|
+
const includeFallbackLinks = ctx.getConfig<boolean>(`${prefix}.includeFallbackLinks`, true);
|
|
36
|
+
const fallbackMaxLinks = ctx.getConfig<number>(`${prefix}.fallbackMaxLinks`, 20);
|
|
37
|
+
const openFirstMatch = ctx.getConfig<boolean>(`${prefix}.openFirstMatch`, false);
|
|
38
|
+
const waitAfterOpenMs = ctx.getConfig<number>(`${prefix}.waitAfterOpenMs`, 2000);
|
|
39
|
+
|
|
40
|
+
return sequenceNode('temp-mail-extract-link-root')
|
|
41
|
+
.step(branchNode('maybe-navigate-detail', 'temp_mail_extract_link_has_detail_url')
|
|
42
|
+
.predicateFn(() => Boolean(detailUrl))
|
|
43
|
+
.whenTrue(toolNode('navigate-detail', 'page_navigate').input({ url: detailUrl, waitUntil, enableNetworkMonitoring: true }))
|
|
44
|
+
.whenFalse(toolNode('skip-navigate-detail', 'console_execute').input({
|
|
45
|
+
expression: '({ skipped: true, step: "navigate-detail", reason: "detailUrl not provided" })',
|
|
46
|
+
})))
|
|
47
|
+
.step(toolNode('initial-wait', 'page_evaluate')
|
|
48
|
+
.input({ code: `new Promise(resolve => setTimeout(() => resolve({ waitedMs: ${initialWaitMs} }), ${initialWaitMs}))` })
|
|
49
|
+
.timeout(Math.max(10_000, initialWaitMs + 2_000)))
|
|
50
|
+
.step(toolNode('extract-links', 'page_evaluate')
|
|
51
|
+
.input({
|
|
52
|
+
code: `(async function(){
|
|
53
|
+
const settings = {
|
|
54
|
+
readySelector: ${JSON.stringify(readySelector)},
|
|
55
|
+
readyText: ${JSON.stringify(readyText)},
|
|
56
|
+
titleBlocklist: ${JSON.stringify(titleBlocklist)},
|
|
57
|
+
bodyBlocklist: ${JSON.stringify(bodyBlocklist)},
|
|
58
|
+
expectedContextHints: ${JSON.stringify(expectedContextHints)},
|
|
59
|
+
linkSelector: ${JSON.stringify(linkSelector)},
|
|
60
|
+
hrefIncludes: ${JSON.stringify(hrefIncludes)},
|
|
61
|
+
textIncludes: ${JSON.stringify(textIncludes)},
|
|
62
|
+
regexPattern: ${JSON.stringify(regexPattern)},
|
|
63
|
+
regexFlags: ${JSON.stringify(regexFlags)},
|
|
64
|
+
maxLinks: ${JSON.stringify(maxLinks)},
|
|
65
|
+
includeFallbackLinks: ${JSON.stringify(includeFallbackLinks)},
|
|
66
|
+
fallbackMaxLinks: ${JSON.stringify(fallbackMaxLinks)},
|
|
67
|
+
openFirstMatch: ${JSON.stringify(openFirstMatch)},
|
|
68
|
+
retryWaitMs: ${JSON.stringify(retryWaitMs)},
|
|
69
|
+
maxWaitAttempts: ${JSON.stringify(maxWaitAttempts)}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
73
|
+
const normalize = (value) => (value || '').trim();
|
|
74
|
+
const regex = settings.regexPattern ? new RegExp(settings.regexPattern, settings.regexFlags) : null;
|
|
75
|
+
|
|
76
|
+
const classifyLink = (item) => {
|
|
77
|
+
const combined = (item.href + ' ' + item.text).toLowerCase();
|
|
78
|
+
if (/activate|activation/.test(combined)) return 'activation';
|
|
79
|
+
if (/verify|verification|confirm/.test(combined)) return 'verification';
|
|
80
|
+
if (/mail\\/view/.test(combined)) return 'mail_view';
|
|
81
|
+
if (/login|signin|signup|register|auth/.test(combined)) return 'auth';
|
|
82
|
+
if (/privacy|terms|policy|contact|help/.test(combined)) return 'navigation';
|
|
83
|
+
return 'other';
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const getDocuments = () => {
|
|
87
|
+
const docs = [{ label: 'main', doc: document }];
|
|
88
|
+
const frames = Array.from(document.querySelectorAll('iframe'));
|
|
89
|
+
for (let index = 0; index < frames.length; index += 1) {
|
|
90
|
+
const frame = frames[index];
|
|
91
|
+
try {
|
|
92
|
+
const frameDoc = frame.contentDocument || frame.contentWindow?.document;
|
|
93
|
+
if (frameDoc) {
|
|
94
|
+
docs.push({ label: 'iframe:' + index, doc: frameDoc });
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
// cross-origin iframe; ignore safely
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return docs;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const collectLinks = () => {
|
|
104
|
+
const docs = getDocuments();
|
|
105
|
+
const allLinks = [];
|
|
106
|
+
for (const item of docs) {
|
|
107
|
+
const anchors = Array.from(item.doc.querySelectorAll(settings.linkSelector));
|
|
108
|
+
for (const anchor of anchors) {
|
|
109
|
+
const href = normalize(anchor.href || anchor.getAttribute('href') || '');
|
|
110
|
+
const text = normalize(anchor.innerText || anchor.textContent || '');
|
|
111
|
+
if (!href && !text) continue;
|
|
112
|
+
allLinks.push({ href, text, source: item.label, kind: classifyLink({ href, text }) });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const matches = allLinks.filter((item) => {
|
|
117
|
+
const combined = (item.href + ' ' + item.text).trim();
|
|
118
|
+
const hrefOk = settings.hrefIncludes.length === 0 || settings.hrefIncludes.some((value) => item.href.includes(value));
|
|
119
|
+
const textOk = settings.textIncludes.length === 0 || settings.textIncludes.some((value) => item.text.includes(value));
|
|
120
|
+
const regexOk = !regex || regex.test(combined);
|
|
121
|
+
return hrefOk && textOk && regexOk;
|
|
122
|
+
}).slice(0, settings.maxLinks);
|
|
123
|
+
|
|
124
|
+
const fallbackLinks = settings.includeFallbackLinks
|
|
125
|
+
? allLinks.slice(0, settings.fallbackMaxLinks)
|
|
126
|
+
: [];
|
|
127
|
+
|
|
128
|
+
const fallbackSummary = allLinks.reduce((acc, item) => {
|
|
129
|
+
acc[item.kind] = (acc[item.kind] || 0) + 1;
|
|
130
|
+
return acc;
|
|
131
|
+
}, {});
|
|
132
|
+
|
|
133
|
+
return { docsCount: docs.length, allLinksCount: allLinks.length, matches, fallbackLinks, fallbackSummary };
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
for (let attempt = 1; attempt <= settings.maxWaitAttempts; attempt++) {
|
|
137
|
+
const title = document.title || '';
|
|
138
|
+
const href = window.location.href || '';
|
|
139
|
+
const bodyText = document.body?.innerText || '';
|
|
140
|
+
const titleBlocked = settings.titleBlocklist.some((value) => value && title.includes(value));
|
|
141
|
+
const bodyBlocked = settings.bodyBlocklist.some((value) => value && bodyText.includes(value));
|
|
142
|
+
const selectorReady = !settings.readySelector || !!document.querySelector(settings.readySelector);
|
|
143
|
+
const textReady = !settings.readyText || bodyText.includes(settings.readyText);
|
|
144
|
+
const contextHintMatched = settings.expectedContextHints.length === 0 || settings.expectedContextHints.some((value) => title.includes(value) || bodyText.includes(value) || href.includes(value));
|
|
145
|
+
const linkData = collectLinks();
|
|
146
|
+
|
|
147
|
+
if (!titleBlocked && !bodyBlocked && selectorReady && textReady) {
|
|
148
|
+
const firstMatch = linkData.matches.length > 0 ? linkData.matches[0] : null;
|
|
149
|
+
let opened = false;
|
|
150
|
+
if (settings.openFirstMatch && firstMatch && firstMatch.href) {
|
|
151
|
+
window.location.href = firstMatch.href;
|
|
152
|
+
opened = true;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
success: linkData.matches.length > 0, attempt, title, href,
|
|
156
|
+
titleBlocked, bodyBlocked, selectorReady, textReady, contextHintMatched,
|
|
157
|
+
contextWarning: contextHintMatched ? null : 'current page does not look like a mail-detail context',
|
|
158
|
+
matchedCount: linkData.matches.length, firstMatch, opened,
|
|
159
|
+
matches: linkData.matches, fallbackLinks: linkData.fallbackLinks,
|
|
160
|
+
fallbackSummary: linkData.fallbackSummary,
|
|
161
|
+
allLinksCount: linkData.allLinksCount, docsCount: linkData.docsCount,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (attempt < settings.maxWaitAttempts) {
|
|
166
|
+
await sleep(settings.retryWaitMs);
|
|
167
|
+
} else {
|
|
168
|
+
return {
|
|
169
|
+
success: false, attempt, title, href,
|
|
170
|
+
titleBlocked, bodyBlocked, selectorReady, textReady, contextHintMatched,
|
|
171
|
+
contextWarning: contextHintMatched ? null : 'current page does not look like a mail-detail context',
|
|
172
|
+
matchedCount: linkData.matches.length,
|
|
173
|
+
firstMatch: linkData.matches.length > 0 ? linkData.matches[0] : null,
|
|
174
|
+
opened: false,
|
|
175
|
+
matches: linkData.matches, fallbackLinks: linkData.fallbackLinks,
|
|
176
|
+
fallbackSummary: linkData.fallbackSummary,
|
|
177
|
+
allLinksCount: linkData.allLinksCount, docsCount: linkData.docsCount,
|
|
178
|
+
reason: 'ready_or_match_conditions_not_met',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return { success: false, reason: 'unreachable' };
|
|
184
|
+
})()`,
|
|
185
|
+
})
|
|
186
|
+
.timeout(Math.max(15_000, initialWaitMs + maxWaitAttempts * retryWaitMs + 5_000)))
|
|
187
|
+
.step(branchNode('maybe-wait-after-open', 'temp_mail_extract_link_wait_after_open')
|
|
188
|
+
.predicateFn(() => openFirstMatch)
|
|
189
|
+
.whenTrue(toolNode('wait-after-open', 'page_evaluate')
|
|
190
|
+
.input({ code: `new Promise(resolve => setTimeout(() => resolve({ waitedMs: ${waitAfterOpenMs} }), ${waitAfterOpenMs}))` })
|
|
191
|
+
.timeout(Math.max(10_000, waitAfterOpenMs + 2_000)))
|
|
192
|
+
.whenFalse(toolNode('skip-wait-after-open', 'console_execute').input({
|
|
193
|
+
expression: '({ skipped: true, step: "wait-after-open", reason: "openFirstMatch=false" })',
|
|
194
|
+
})))
|
|
195
|
+
.step(toolNode('emit-summary', 'console_execute').input({
|
|
196
|
+
expression: `(${JSON.stringify({
|
|
197
|
+
workflowId,
|
|
198
|
+
detailUrl, waitUntil, initialWaitMs, retryWaitMs, maxWaitAttempts,
|
|
199
|
+
readySelector, readyText, titleBlocklist, bodyBlocklist, expectedContextHints,
|
|
200
|
+
linkSelector, hrefIncludes, textIncludes, regexPattern, regexFlags,
|
|
201
|
+
maxLinks, includeFallbackLinks, fallbackMaxLinks, openFirstMatch, waitAfterOpenMs,
|
|
202
|
+
note: 'Inspect extract-links output for matched href/text pairs, contextWarning, fallbackLinks, fallbackSummary, and docsCount.',
|
|
203
|
+
})})`,
|
|
204
|
+
}))
|
|
205
|
+
.build();
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
onStart(ctx) {
|
|
209
|
+
ctx.emitMetric('workflow_runs_total', 1, 'counter', { workflowId, stage: 'start' });
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
onFinish(ctx) {
|
|
213
|
+
ctx.emitMetric('workflow_runs_total', 1, 'counter', { workflowId, stage: 'finish' });
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
onError(ctx, error) {
|
|
217
|
+
ctx.emitMetric('workflow_errors_total', 1, 'counter', { workflowId, error: error.name });
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export default workflow;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"kind": "workflow",
|
|
4
|
+
"slug": "temp-mail-open-latest",
|
|
5
|
+
"id": "workflow.temp-mail-open-latest.v1",
|
|
6
|
+
"source": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"repo": "https://github.com/vmoranv/jshook_workflow_temp_mail_open_latest",
|
|
9
|
+
"ref": "main",
|
|
10
|
+
"commit": "c0c067c",
|
|
11
|
+
"subpath": ".",
|
|
12
|
+
"entry": "workflow.ts"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vmoranv
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# temp-mail-open-latest workflow
|
|
2
|
+
|
|
3
|
+
Declarative workflow for opening the latest relevant message in a temporary mailbox.
|
|
4
|
+
|
|
5
|
+
## Entry File
|
|
6
|
+
|
|
7
|
+
- `workflow.ts`
|
|
8
|
+
|
|
9
|
+
## Workflow ID
|
|
10
|
+
|
|
11
|
+
- `workflow.temp-mail-open-latest.v1`
|
|
12
|
+
|
|
13
|
+
## What It Does
|
|
14
|
+
|
|
15
|
+
- Navigates to a mailbox page
|
|
16
|
+
- Waits for an inbox-ready selector
|
|
17
|
+
- Optionally clicks a refresh button or trigger
|
|
18
|
+
- Waits for inbox updates to settle
|
|
19
|
+
- Scans mailbox anchors with configurable selectors and matching rules
|
|
20
|
+
- Opens the latest relevant message by navigating to its link
|
|
21
|
+
- Emits a small summary payload for downstream workflows or operators
|
|
22
|
+
|
|
23
|
+
## Tools Used
|
|
24
|
+
|
|
25
|
+
- `page_navigate`
|
|
26
|
+
- `page_wait_for_selector`
|
|
27
|
+
- `page_evaluate`
|
|
28
|
+
- `console_execute`
|
|
29
|
+
|
|
30
|
+
## Config
|
|
31
|
+
|
|
32
|
+
All config keys live under `workflows.tempMailOpenLatest.*`:
|
|
33
|
+
|
|
34
|
+
- `mailboxUrl`
|
|
35
|
+
- `waitUntil`
|
|
36
|
+
- `readySelector`
|
|
37
|
+
- `timeoutMs`
|
|
38
|
+
- `refreshSelector`
|
|
39
|
+
- `refreshWaitMs`
|
|
40
|
+
- `itemSelector`
|
|
41
|
+
- `hrefIncludes`
|
|
42
|
+
- `hrefRegex`
|
|
43
|
+
- `textIncludes`
|
|
44
|
+
- `textRegex`
|
|
45
|
+
- `openOrder` (`first` or `last`)
|
|
46
|
+
|
|
47
|
+
## Matching Strategy
|
|
48
|
+
|
|
49
|
+
A candidate anchor is selected from `itemSelector` and filtered by:
|
|
50
|
+
|
|
51
|
+
- href contains `hrefIncludes` (if provided)
|
|
52
|
+
- href matches `hrefRegex` (if provided)
|
|
53
|
+
- text contains `textIncludes` (if provided)
|
|
54
|
+
- text matches `textRegex` (if provided)
|
|
55
|
+
|
|
56
|
+
Then the workflow opens either the `first` or `last` matching item.
|
|
57
|
+
|
|
58
|
+
## Notes
|
|
59
|
+
|
|
60
|
+
- This workflow is intentionally provider-agnostic and is not tied to Qwen.
|
|
61
|
+
- It is designed to pair well with a separate link-extraction workflow on the message detail page.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jshookmcpextension/workflow-temp-mail-open-latest",
|
|
3
|
+
"scripts": {
|
|
4
|
+
"build": "tsc -p tsconfig.json",
|
|
5
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
6
|
+
},
|
|
7
|
+
"type": "module",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@jshookmcp/extension-sdk": "^0.3.0",
|
|
10
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
11
|
+
"dotenv": "^17.3.1"
|
|
12
|
+
},
|
|
13
|
+
"version": "0.1.0",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=20.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^25.3.0",
|
|
19
|
+
"typescript": "^5.9.3"
|
|
20
|
+
},
|
|
21
|
+
"private": true,
|
|
22
|
+
"packageManager": "pnpm@10.28.2"
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"rootDir": ".",
|
|
12
|
+
"outDir": "dist"
|
|
13
|
+
},
|
|
14
|
+
"include": ["workflow.ts"],
|
|
15
|
+
"exclude": ["dist", "node_modules"]
|
|
16
|
+
}
|