@lievclouds/dsh-cortex 0.5.4 → 1.0.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.
Potentially problematic release.
This version of @lievclouds/dsh-cortex might be problematic. Click here for more details.
- package/README.md +1 -1
- package/client.js +434 -412
- package/index.js +7 -0
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +115 -110
- package/src/cortex-ui/dsh/css-overrides.css +8 -0
- package/src/cortex-ui/renderer/App.tsx +39 -0
- package/src/cortex-ui/renderer/components/miniapp/FileTree.tsx +161 -0
- package/src/cortex-ui/renderer/components/miniapp/MiniappCard.tsx +263 -0
- package/src/cortex-ui/renderer/components/miniapp/MiniappDetail.tsx +1493 -0
- package/src/cortex-ui/renderer/components/retest/CurlImportDialog.tsx +158 -0
- package/src/cortex-ui/renderer/components/retest/RepeaterCollectionsDialog.tsx +327 -0
- package/src/cortex-ui/renderer/components/retest/RepeaterHistoryDialog.tsx +242 -0
- package/src/cortex-ui/renderer/components/retest/RepeaterPanel.tsx +291 -137
- package/src/cortex-ui/renderer/components/retest/YakitSendPanel.tsx +411 -0
- package/src/cortex-ui/renderer/i18n/messages/common.ts +2 -0
- package/src/cortex-ui/renderer/i18n/messages/fuzzer.ts +32 -0
- package/src/cortex-ui/renderer/i18n/messages/index.ts +5 -1
- package/src/cortex-ui/renderer/i18n/messages/miniapp.ts +392 -0
- package/src/cortex-ui/renderer/i18n/messages/retest.ts +144 -2
- package/src/cortex-ui/renderer/lib/curl.ts +635 -0
- package/src/cortex-ui/renderer/lib/miniappApi.ts +459 -0
- package/src/cortex-ui/renderer/lib/navigation.ts +13 -1
- package/src/cortex-ui/renderer/lib/repeaterRaw.ts +102 -0
- package/src/cortex-ui/renderer/lib/repeaterStoreApi.ts +163 -0
- package/src/cortex-ui/renderer/lib/retestResponse.ts +64 -17
- package/src/cortex-ui/renderer/pages/FuzzerPage.tsx +212 -0
- package/src/cortex-ui/renderer/pages/MiniappPage.tsx +664 -0
- package/src/host/cortex/index.js +42 -1
- package/src/host/cortex/repeater-store.js +411 -0
- package/src/host/cortex/repeater.js +15 -2
- package/src/host/cortex/tools.js +2 -0
- package/src/host/miniapp/analyze.js +1078 -0
- package/src/host/miniapp/assets-import.js +294 -0
- package/src/host/miniapp/detect.js +426 -0
- package/src/host/miniapp/format.js +263 -0
- package/src/host/miniapp/roots.js +360 -0
- package/src/host/miniapp/routes.js +304 -0
- package/src/host/miniapp/service.js +559 -0
- package/src/host/miniapp/store.js +756 -0
- package/src/host/miniapp/tools.js +335 -0
- package/src/host/miniapp/unpack.js +1010 -0
- package/src/host/miniapp/wxapkg.js +416 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tools for the local mini-program (wxapkg) workbench.
|
|
3
|
+
*
|
|
4
|
+
* Seven `cortex_*_miniapp*` tools let a DSH conversation drive the same local
|
|
5
|
+
* pipeline as the UI: scan → list → unpack → overview → search/read → import
|
|
6
|
+
* assets. They never return binary content, never execute unpacked code and
|
|
7
|
+
* never send package data anywhere.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { badRequest } from '../http.js'
|
|
11
|
+
import {
|
|
12
|
+
analyzeSourceRecord,
|
|
13
|
+
fileForSource,
|
|
14
|
+
importAssetsForSource,
|
|
15
|
+
listMiniapps,
|
|
16
|
+
miniappStore,
|
|
17
|
+
requireSource,
|
|
18
|
+
scan,
|
|
19
|
+
searchSource,
|
|
20
|
+
unpackSourceRecord,
|
|
21
|
+
} from './service.js'
|
|
22
|
+
|
|
23
|
+
const LOG = '[cortex]'
|
|
24
|
+
|
|
25
|
+
const OUTPUT_SCHEMA = {
|
|
26
|
+
type: 'object',
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
properties: { text: { type: 'string' } },
|
|
29
|
+
required: ['text'],
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const renderText = (_args, value) => [
|
|
33
|
+
{ type: 'text', text: String(value && value.text !== undefined ? value.text : value) },
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
/** Same definition style as the original Cortex tools (`cortex/tools.js`). */
|
|
37
|
+
function defineTool(name, description, properties, required, execute) {
|
|
38
|
+
return {
|
|
39
|
+
name,
|
|
40
|
+
description,
|
|
41
|
+
parameters: { type: 'object', properties, required, additionalProperties: false },
|
|
42
|
+
output: { schema: OUTPUT_SCHEMA, render: renderText },
|
|
43
|
+
execute: async (args) => {
|
|
44
|
+
const result = await execute(args && typeof args === 'object' ? args : {})
|
|
45
|
+
return { text: result.text }
|
|
46
|
+
},
|
|
47
|
+
presentCall: (args) => ({ card: 'generic', title: name, kind: 'other', rawInput: args }),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function text(value) {
|
|
52
|
+
return value === null || value === undefined ? '' : String(value)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formatBytes(bytes) {
|
|
56
|
+
const value = Number(bytes) || 0
|
|
57
|
+
if (value < 1024) return `${value} B`
|
|
58
|
+
if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KiB`
|
|
59
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MiB`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function failure(name, error) {
|
|
63
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
64
|
+
return { text: `${LOG} ${name} failed: ${message}` }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function requireAppid(args) {
|
|
68
|
+
const appid = text(args.appid).trim()
|
|
69
|
+
if (appid.length === 0) throw badRequest('appid is required')
|
|
70
|
+
return appid
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @param {{holder:any, config:any, store?:any}} dataAccess */
|
|
74
|
+
export function createMiniappToolDefinitions({ holder } = {}) {
|
|
75
|
+
const getStore = () => miniappStore()
|
|
76
|
+
|
|
77
|
+
const scanTool = defineTool(
|
|
78
|
+
'cortex_scan_miniapps',
|
|
79
|
+
'Scan this machine for WeChat mini-program packages (wxapkg): WeChat 4.x xwechat cache, classic WeChat Files, devtools cache and saved custom roots. Read-only, local-only. Returns package/app counts and the detected apps. Run this first when the user asks about mini-program reversing; only authorized targets.',
|
|
80
|
+
{
|
|
81
|
+
force: { type: 'boolean', description: 'Rescan even when a scan already ran (scan is always read-only and safe)' },
|
|
82
|
+
},
|
|
83
|
+
[],
|
|
84
|
+
async (args) => {
|
|
85
|
+
try {
|
|
86
|
+
const result = await scan(getStore())
|
|
87
|
+
const lines = [
|
|
88
|
+
`${LOG} miniapp scan complete: sources=${result.totalSources} apps=${result.appCount} packages=${result.totalPackages} added=${result.added} updated=${result.updated} unchanged=${result.unchanged}`,
|
|
89
|
+
]
|
|
90
|
+
if (result.errors && result.errors.length > 0) {
|
|
91
|
+
lines.push(`scan warnings: ${result.errors.length} skipped path(s) (permission/locked/limit)`)
|
|
92
|
+
}
|
|
93
|
+
for (const root of Array.isArray(result.roots) ? result.roots : []) {
|
|
94
|
+
lines.push(
|
|
95
|
+
`root: ${root.kind} ${root.path} => ${root.exists ? `${root.filesFound ?? 0} wxapkg file(s)` : 'missing'}`,
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
lines.push(
|
|
99
|
+
'note: only wxapkg packages cached on this PC are detectable; mini programs opened only on the phone have no local package. Use a custom scan root for exported packages.',
|
|
100
|
+
)
|
|
101
|
+
for (const item of result.miniapps.slice(0, 40)) {
|
|
102
|
+
const kind = item.kind === 'plugin' ? '[插件] ' : item.kind === 'library' ? '[公共库] ' : ''
|
|
103
|
+
lines.push(
|
|
104
|
+
`${kind}${item.app_name ? `${item.app_name} · ` : ''}${item.appid}@${item.version || '-'} | ${item.status} | ${item.format} | ${formatBytes(item.size_bytes)} | files=${item.file_count} | versions=${item.version_count}${item.source_count > 1 ? ` | copies=${item.source_count}` : ''} | id=${item.id}`,
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
if (result.miniapps.length > 40) lines.push(`… ${result.miniapps.length - 40} more; use cortex_list_miniapps`)
|
|
108
|
+
if (result.miniapps.length === 0) {
|
|
109
|
+
lines.push('No wxapkg packages found in the known roots. The user can add a custom scan root in the 小程序逆向 UI.')
|
|
110
|
+
}
|
|
111
|
+
return { text: lines.join('\n') }
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return failure('scan', error)
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
const listTool = defineTool(
|
|
119
|
+
'cortex_list_miniapps',
|
|
120
|
+
'List detected mini-program packages from the local plugin database (appid, version, format, status, size, updated). Supports a substring search over appid/version/status. Call after cortex_scan_miniapps.',
|
|
121
|
+
{
|
|
122
|
+
search: { type: 'string', description: 'Substring match on appid/version/status/root kind' },
|
|
123
|
+
},
|
|
124
|
+
[],
|
|
125
|
+
async (args) => {
|
|
126
|
+
try {
|
|
127
|
+
const listing = listMiniapps(getStore(), { search: text(args.search).trim() })
|
|
128
|
+
const lines = [`${LOG} ${listing.total} miniapp sources · ${listing.appCount} apps`]
|
|
129
|
+
for (const item of listing.miniapps) {
|
|
130
|
+
const kind = item.kind === 'plugin' ? '[插件] ' : item.kind === 'library' ? '[公共库] ' : ''
|
|
131
|
+
lines.push(
|
|
132
|
+
`${kind}${item.app_name ? `${item.app_name} · ` : ''}${item.appid}@${item.version || '-'} | ${item.status} | ${item.format} | ${formatBytes(item.size_bytes)} | files=${item.file_count} | versions=${item.version_count}${item.source_count > 1 ? ` | copies=${item.source_count}` : ''} | analysis=${item.has_analysis ? 'yes' : 'no'} | id=${item.id}`,
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
if (listing.total === 0) lines.push('No sources yet — run cortex_scan_miniapps first.')
|
|
136
|
+
return { text: lines.join('\n') }
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return failure('list', error)
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
const unpackTool = defineTool(
|
|
144
|
+
'cortex_unpack_miniapp',
|
|
145
|
+
'Decrypt (PC WeChat V1MMWX) and unpack one wxapkg source into the local workspace ~/.cortex/miniapps/<appid>/<version>/unpacked. Read-only on the WeChat cache; never executes unpacked code. Reports the real status: unpacked | unsupported (client format not supported) | failed. Omit version for the newest version.',
|
|
146
|
+
{
|
|
147
|
+
appid: { type: 'string', minLength: 1, maxLength: 64 },
|
|
148
|
+
version: { type: 'string', maxLength: 64, description: 'Omit for the newest detected version' },
|
|
149
|
+
force: { type: 'boolean', description: 'Re-unpack even when an unpacked workspace already exists' },
|
|
150
|
+
},
|
|
151
|
+
['appid'],
|
|
152
|
+
async (args) => {
|
|
153
|
+
try {
|
|
154
|
+
const source = requireSource(getStore(), requireAppid(args), text(args.version).trim())
|
|
155
|
+
if (!source) {
|
|
156
|
+
return { text: `${LOG} unpack failed: miniapp ${args.appid}${args.version ? `@${args.version}` : ''} not found — run cortex_scan_miniapps first` }
|
|
157
|
+
}
|
|
158
|
+
const result = await unpackSourceRecord({ store: getStore(), source, force: args.force === true })
|
|
159
|
+
if (!result.ok) {
|
|
160
|
+
return {
|
|
161
|
+
text: [
|
|
162
|
+
`${LOG} unpack ${source.appid}@${source.version || '-'} => ${result.status}`,
|
|
163
|
+
`reason: ${result.error || 'unknown'}`,
|
|
164
|
+
'The source is marked unsupported/failed in the local database; other packages are unaffected.',
|
|
165
|
+
].join('\n'),
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const manifest = result.manifest || {}
|
|
169
|
+
const totals = (manifest && manifest.totals) || result.totals || {}
|
|
170
|
+
return {
|
|
171
|
+
text: [
|
|
172
|
+
`${LOG} unpacked ${source.appid}@${source.version || '-'} (${result.cached ? 'cached' : 'fresh'})`,
|
|
173
|
+
`workspace: ${result.unpackedPath}`,
|
|
174
|
+
`packages: ${totals.packages ?? '-'} · files: ${totals.files ?? '-'} · bytes: ${formatBytes(totals.bytes)} · conflicts: ${totals.conflicts ?? 0} · skipped: ${totals.skipped ?? 0}`,
|
|
175
|
+
`next: cortex_get_miniapp_overview {appid:"${source.appid}"${source.version ? `, version:"${source.version}"` : ''}}`,
|
|
176
|
+
].join('\n'),
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
return failure('unpack', error)
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
const overviewTool = defineTool(
|
|
185
|
+
'cortex_get_miniapp_overview',
|
|
186
|
+
'Summarise an unpacked mini-program: pages, subpackages, business domains, endpoint candidates, hard-coded secret counts (masked) and candidate test parameters. Capped at ~8000 characters. Requires unpack first.',
|
|
187
|
+
{
|
|
188
|
+
appid: { type: 'string', minLength: 1, maxLength: 64 },
|
|
189
|
+
version: { type: 'string', maxLength: 64, description: 'Omit for the newest detected version' },
|
|
190
|
+
},
|
|
191
|
+
['appid'],
|
|
192
|
+
async (args) => {
|
|
193
|
+
try {
|
|
194
|
+
const source = requireSource(getStore(), requireAppid(args), text(args.version).trim())
|
|
195
|
+
if (!source) return { text: `${LOG} overview failed: miniapp not found — run cortex_scan_miniapps first` }
|
|
196
|
+
if (source.status !== 'unpacked') {
|
|
197
|
+
return {
|
|
198
|
+
text: `${LOG} overview failed: ${source.appid}@${source.version || '-'} is not unpacked (status: ${source.status}). Call cortex_unpack_miniapp first.`,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const result = analyzeSourceRecord({ store: getStore(), source })
|
|
202
|
+
return { text: result.overviewText }
|
|
203
|
+
} catch (error) {
|
|
204
|
+
return failure('overview', error)
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
const readTool = defineTool(
|
|
210
|
+
'cortex_read_miniapp_file',
|
|
211
|
+
'Read a text slice of one unpacked mini-program file by workspace-relative path. Path must stay inside the unpacked workspace; binary files are rejected. Max 200 lines and 100 KiB per call; use offset to page. Never returns a whole package.',
|
|
212
|
+
{
|
|
213
|
+
appid: { type: 'string', minLength: 1, maxLength: 64 },
|
|
214
|
+
version: { type: 'string', maxLength: 64 },
|
|
215
|
+
path: { type: 'string', minLength: 1, maxLength: 4000, description: 'Relative path inside the unpacked workspace, e.g. "app-service.js"' },
|
|
216
|
+
offset: { type: 'integer', minimum: 0, maximum: 10000000, description: '0-based line offset (default 0)' },
|
|
217
|
+
limit: { type: 'integer', minimum: 1, maximum: 200, description: 'Max lines (default 120, hard cap 200)' },
|
|
218
|
+
},
|
|
219
|
+
['appid', 'path'],
|
|
220
|
+
async (args) => {
|
|
221
|
+
try {
|
|
222
|
+
const source = requireSource(getStore(), requireAppid(args), text(args.version).trim())
|
|
223
|
+
if (!source) return { text: `${LOG} read failed: miniapp not found` }
|
|
224
|
+
if (source.status !== 'unpacked') {
|
|
225
|
+
return { text: `${LOG} read failed: ${source.appid}@${source.version || '-'} is not unpacked (status: ${source.status})` }
|
|
226
|
+
}
|
|
227
|
+
const relPath = text(args.path).trim()
|
|
228
|
+
if (relPath.length === 0) return { text: `${LOG} read failed: path is required` }
|
|
229
|
+
const offset = Number.isInteger(args.offset) && args.offset > 0 ? args.offset : 0
|
|
230
|
+
const limit = Math.min(Number.isInteger(args.limit) && args.limit > 0 ? args.limit : 120, 200)
|
|
231
|
+
const result = fileForSource(source, relPath, { offset, limit })
|
|
232
|
+
if (result.binary) {
|
|
233
|
+
return { text: `${LOG} read ${relPath}: binary file (${formatBytes(result.size)}); text preview is not allowed. Use search for text files instead.` }
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
text: [
|
|
237
|
+
`${LOG} ${source.appid}@${source.version || '-'} ${result.path} (${formatBytes(result.size)}, lines ${offset + 1}-${offset + (result.content ? result.content.split('\n').length : 0)} of ${result.totalLines}${result.truncated ? ', truncated' : ''})`,
|
|
238
|
+
'```',
|
|
239
|
+
result.content,
|
|
240
|
+
'```',
|
|
241
|
+
].join('\n'),
|
|
242
|
+
}
|
|
243
|
+
} catch (error) {
|
|
244
|
+
return failure('read', error)
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
const searchTool = defineTool(
|
|
250
|
+
'cortex_search_miniapp',
|
|
251
|
+
'Search text files of an unpacked mini-program (plain text or regular expression, optional glob filter) and return file:line matches with short context. Default 50 results, hard cap 200. Use it to locate endpoints/secrets before reading exact files.',
|
|
252
|
+
{
|
|
253
|
+
appid: { type: 'string', minLength: 1, maxLength: 64 },
|
|
254
|
+
version: { type: 'string', maxLength: 64 },
|
|
255
|
+
query: { type: 'string', minLength: 1, maxLength: 400 },
|
|
256
|
+
regex: { type: 'boolean', description: 'Treat query as a JavaScript regular expression' },
|
|
257
|
+
glob: { type: 'string', maxLength: 300, description: 'Simple glob against the relative path, e.g. "**/*.js"' },
|
|
258
|
+
maxResults: { type: 'integer', minimum: 1, maximum: 200, description: 'Default 50' },
|
|
259
|
+
},
|
|
260
|
+
['appid', 'query'],
|
|
261
|
+
async (args) => {
|
|
262
|
+
try {
|
|
263
|
+
const source = requireSource(getStore(), requireAppid(args), text(args.version).trim())
|
|
264
|
+
if (!source) return { text: `${LOG} search failed: miniapp not found` }
|
|
265
|
+
if (source.status !== 'unpacked') {
|
|
266
|
+
return { text: `${LOG} search failed: ${source.appid}@${source.version || '-'} is not unpacked (status: ${source.status})` }
|
|
267
|
+
}
|
|
268
|
+
const query = text(args.query)
|
|
269
|
+
if (query.length === 0) return { text: `${LOG} search failed: query is required` }
|
|
270
|
+
const result = searchSource(source, {
|
|
271
|
+
query,
|
|
272
|
+
regex: args.regex === true,
|
|
273
|
+
glob: text(args.glob).trim(),
|
|
274
|
+
maxResults: Math.min(Number.isInteger(args.maxResults) && args.maxResults > 0 ? args.maxResults : 50, 200),
|
|
275
|
+
})
|
|
276
|
+
const lines = [
|
|
277
|
+
`${LOG} search "${query}"${args.regex ? ' (regex)' : ''}${args.glob ? ` glob=${args.glob}` : ''}: ${result.matches.length} match(es) in ${result.scanned} file(s)${result.truncated ? ' (truncated)' : ''}`,
|
|
278
|
+
]
|
|
279
|
+
for (const match of result.matches) {
|
|
280
|
+
lines.push(`${match.file}:${match.line}: ${match.text}`)
|
|
281
|
+
}
|
|
282
|
+
if (result.matches.length === 0) lines.push('No match.')
|
|
283
|
+
return { text: lines.join('\n') }
|
|
284
|
+
} catch (error) {
|
|
285
|
+
return failure('search', error)
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
const importTool = defineTool(
|
|
291
|
+
'cortex_import_miniapp_assets',
|
|
292
|
+
'Import the domains, endpoint candidates, hard-coded secrets and candidate test parameters extracted from an unpacked mini-program into the existing Cortex asset library (canonical interfaces/leaked_infos/test_parameters, merge semantics). Secrets are stored locally but never printed. Returns real write counts; requires the Cortex data.db (schema v5).',
|
|
293
|
+
{
|
|
294
|
+
appid: { type: 'string', minLength: 1, maxLength: 64 },
|
|
295
|
+
version: { type: 'string', maxLength: 64 },
|
|
296
|
+
host_port: { type: 'string', maxLength: 4000, description: 'Optional explicit asset host_port; default is the most-used business domain (https preferred)' },
|
|
297
|
+
},
|
|
298
|
+
['appid'],
|
|
299
|
+
async (args) => {
|
|
300
|
+
try {
|
|
301
|
+
const source = requireSource(getStore(), requireAppid(args), text(args.version).trim())
|
|
302
|
+
if (!source) return { text: `${LOG} import failed: miniapp not found` }
|
|
303
|
+
if (source.status !== 'unpacked') {
|
|
304
|
+
return { text: `${LOG} import failed: ${source.appid}@${source.version || '-'} is not unpacked (status: ${source.status})` }
|
|
305
|
+
}
|
|
306
|
+
const result = await importAssetsForSource({
|
|
307
|
+
holder,
|
|
308
|
+
store: getStore(),
|
|
309
|
+
source,
|
|
310
|
+
hostPort: text(args.host_port).trim() || undefined,
|
|
311
|
+
})
|
|
312
|
+
if (!result.ok) {
|
|
313
|
+
return {
|
|
314
|
+
text: [
|
|
315
|
+
`${LOG} import failed: no usable business domain extracted from ${source.appid}`,
|
|
316
|
+
...result.warnings.map((warning) => `warning: ${warning}`),
|
|
317
|
+
].join('\n'),
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const asset = result.assets[0] || {}
|
|
321
|
+
return {
|
|
322
|
+
text: [
|
|
323
|
+
`${LOG} imported ${source.appid}@${source.version || '-'} -> asset ${asset.host_port} (${asset.created ? 'created' : 'merged'}) project=${result.project}`,
|
|
324
|
+
`stored: interfaces=${result.totals.interfaces} leaked_infos=${result.totals.leaks} test_parameters=${result.totals.parameters}`,
|
|
325
|
+
...result.warnings.map((warning) => `warning: ${warning}`),
|
|
326
|
+
].join('\n'),
|
|
327
|
+
}
|
|
328
|
+
} catch (error) {
|
|
329
|
+
return failure('import', error)
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
return [scanTool, listTool, unpackTool, overviewTool, readTool, searchTool, importTool]
|
|
335
|
+
}
|