pinokiod 7.3.1 → 7.3.3

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.
Files changed (122) hide show
  1. package/kernel/api/github/index.js +444 -0
  2. package/kernel/api/index.js +199 -11
  3. package/kernel/api/process/index.js +124 -44
  4. package/kernel/api/shell_run_template.js +273 -0
  5. package/kernel/api/uri/index.js +51 -0
  6. package/kernel/bin/git.js +9 -10
  7. package/kernel/bin/huggingface.js +1 -1
  8. package/kernel/bin/zip.js +9 -1
  9. package/kernel/connect/providers/github/README.md +5 -4
  10. package/kernel/environment.js +195 -92
  11. package/kernel/git.js +98 -19
  12. package/kernel/gitconfig_template +7 -0
  13. package/kernel/gpu/amd.js +72 -0
  14. package/kernel/gpu/apple.js +8 -0
  15. package/kernel/gpu/common.js +12 -0
  16. package/kernel/gpu/intel.js +47 -0
  17. package/kernel/gpu/nvidia.js +8 -0
  18. package/kernel/index.js +11 -1
  19. package/kernel/managed_skills.js +871 -0
  20. package/kernel/plugin.js +6 -58
  21. package/kernel/plugin_sources.js +316 -0
  22. package/kernel/resource_usage/gpu.js +349 -0
  23. package/kernel/resource_usage/index.js +322 -0
  24. package/kernel/resource_usage/macos_footprint.js +197 -0
  25. package/kernel/resource_usage/preferences.js +92 -0
  26. package/kernel/resource_usage/process_tree.js +303 -0
  27. package/kernel/scripts/git/create +4 -4
  28. package/kernel/scripts/git/fork +7 -8
  29. package/kernel/shell.js +23 -2
  30. package/kernel/shells.js +41 -0
  31. package/kernel/sysinfo.js +62 -9
  32. package/kernel/util.js +60 -0
  33. package/package.json +1 -1
  34. package/server/index.js +984 -156
  35. package/server/lib/app_log_report.js +543 -0
  36. package/server/lib/content_validation.js +55 -33
  37. package/server/lib/launcher_instruction_bootstrap.js +4 -96
  38. package/server/lib/terminal_session_helpers.js +0 -3
  39. package/server/public/common.js +77 -31
  40. package/server/public/create-launcher.js +4 -32
  41. package/server/public/logs.js +1428 -0
  42. package/server/public/nav.js +7 -0
  43. package/server/public/plugin-detail.js +93 -10
  44. package/server/public/privacy_filter_worker.js +391 -0
  45. package/server/public/style.css +1104 -154
  46. package/server/public/task-launcher.js +8 -29
  47. package/server/public/universal-launcher.css +8 -6
  48. package/server/public/universal-launcher.js +3 -27
  49. package/server/routes/apps.js +195 -1
  50. package/server/views/app.ejs +3041 -717
  51. package/server/views/autolaunch.ejs +917 -0
  52. package/server/views/bootstrap.ejs +7 -1
  53. package/server/views/d.ejs +408 -65
  54. package/server/views/editor.ejs +85 -19
  55. package/server/views/index.ejs +661 -111
  56. package/server/views/init/index.ejs +1 -1
  57. package/server/views/install.ejs +1 -1
  58. package/server/views/logs.ejs +164 -86
  59. package/server/views/net.ejs +7 -1
  60. package/server/views/partials/d_terminal_column.ejs +2 -2
  61. package/server/views/partials/d_terminal_options.ejs +0 -8
  62. package/server/views/partials/fs_status.ejs +47 -0
  63. package/server/views/partials/home_action_modal.ejs +86 -0
  64. package/server/views/partials/home_run_menu.ejs +87 -0
  65. package/server/views/partials/main_sidebar.ejs +2 -0
  66. package/server/views/partials/menu.ejs +1 -1
  67. package/server/views/plugin_detail.ejs +19 -4
  68. package/server/views/plugins.ejs +201 -3
  69. package/server/views/pre.ejs +1 -1
  70. package/server/views/pro.ejs +1 -1
  71. package/server/views/shell.ejs +40 -18
  72. package/server/views/skills.ejs +506 -0
  73. package/server/views/terminal.ejs +45 -19
  74. package/spec/INSTRUCTION_SYNC.md +20 -10
  75. package/system/plugin/antigravity-cli/antigravity.png +0 -0
  76. package/system/plugin/antigravity-cli/common.js +155 -0
  77. package/system/plugin/antigravity-cli/install.js +272 -0
  78. package/system/plugin/antigravity-cli/pinokio.js +13 -0
  79. package/system/plugin/antigravity-cli-auto/antigravity.png +0 -0
  80. package/system/plugin/antigravity-cli-auto/pinokio.js +13 -0
  81. package/system/plugin/claude/claude.png +0 -0
  82. package/system/plugin/claude/pinokio.js +47 -0
  83. package/system/plugin/claude-auto/claude.png +0 -0
  84. package/system/plugin/claude-auto/pinokio.js +58 -0
  85. package/system/plugin/claude-desktop/icon.jpeg +0 -0
  86. package/system/plugin/claude-desktop/pinokio.js +23 -0
  87. package/system/plugin/codex/openai.webp +0 -0
  88. package/system/plugin/codex/pinokio.js +42 -0
  89. package/system/plugin/codex-auto/openai.webp +0 -0
  90. package/system/plugin/codex-auto/pinokio.js +49 -0
  91. package/system/plugin/codex-desktop/icon.png +0 -0
  92. package/system/plugin/codex-desktop/pinokio.js +23 -0
  93. package/system/plugin/crush/crush.png +0 -0
  94. package/system/plugin/crush/pinokio.js +15 -0
  95. package/system/plugin/cursor/cursor.jpeg +0 -0
  96. package/system/plugin/cursor/pinokio.js +23 -0
  97. package/system/plugin/qwen/pinokio.js +34 -0
  98. package/system/plugin/qwen/qwen.png +0 -0
  99. package/system/plugin/vscode/pinokio.js +20 -0
  100. package/system/plugin/vscode/vscode.png +0 -0
  101. package/system/plugin/windsurf/pinokio.js +23 -0
  102. package/system/plugin/windsurf/windsurf.png +0 -0
  103. package/test/antigravity-cli-plugin.test.js +185 -0
  104. package/test/app-api.test.js +239 -0
  105. package/test/app-log-report.test.js +67 -0
  106. package/test/environment-cache-preflight.test.js +98 -0
  107. package/test/git-bin.test.js +59 -0
  108. package/test/git-defaults.test.js +97 -0
  109. package/test/github-api.test.js +158 -0
  110. package/test/github-connection.test.js +117 -0
  111. package/test/huggingface-bin.test.js +25 -0
  112. package/test/managed-skills.test.js +351 -0
  113. package/test/plugin-action-functions.test.js +337 -0
  114. package/test/plugin-dev-iframe.test.js +17 -0
  115. package/test/plugin-sources.test.js +203 -0
  116. package/test/privacy-filter-worker-heuristics.test.js +69 -0
  117. package/test/process-wait.test.js +169 -0
  118. package/test/script-api.test.js +97 -0
  119. package/test/shell-api.test.js +134 -0
  120. package/test/shell-run-template.test.js +209 -0
  121. package/test/storage-api.test.js +137 -0
  122. package/test/uri-api.test.js +100 -0
@@ -4,33 +4,13 @@
4
4
  const CATEGORY_ORDER = ["CLI", "IDE"];
5
5
  const TOOL_PREFERENCE_KEY = "pinokio.universalLauncher.tool";
6
6
  const TOOL_VALUE_ALIASES = {
7
- claude: "code/claude",
8
- codex: "code/codex",
9
- gemini: "code/gemini"
7
+ claude: "pinokio/run/plugin/claude",
8
+ codex: "pinokio/run/plugin/codex",
9
+ antigravity: "pinokio/run/plugin/antigravity-cli",
10
+ "antigravity-cli": "pinokio/run/plugin/antigravity-cli",
11
+ "code/claude": "pinokio/run/plugin/claude",
12
+ "code/codex": "pinokio/run/plugin/codex"
10
13
  };
11
- const FALLBACK_TOOLS = [
12
- {
13
- value: "code/claude",
14
- label: "Claude Code",
15
- iconSrc: "/asset/plugin/code/claude/claude.png",
16
- isDefault: true,
17
- category: "CLI"
18
- },
19
- {
20
- value: "code/codex",
21
- label: "OpenAI Codex",
22
- iconSrc: "/asset/plugin/code/codex/openai.webp",
23
- isDefault: false,
24
- category: "CLI"
25
- },
26
- {
27
- value: "code/gemini",
28
- label: "Google Gemini CLI",
29
- iconSrc: "/asset/plugin/code/gemini/gemini.jpeg",
30
- isDefault: false,
31
- category: "CLI"
32
- }
33
- ];
34
14
  const TASK_INSTALL_SHELL_CLIENT = {
35
15
  cols: 120,
36
16
  rows: 32
@@ -440,10 +420,9 @@
440
420
  throw new Error(String(response.status));
441
421
  }
442
422
  const payload = await response.json();
443
- const tools = mapPluginMenuToTools(payload && Array.isArray(payload.menu) ? payload.menu : []);
444
- return tools.length > 0 ? tools : FALLBACK_TOOLS.slice();
423
+ return mapPluginMenuToTools(payload && Array.isArray(payload.menu) ? payload.menu : []);
445
424
  } catch (_) {
446
- return FALLBACK_TOOLS.slice();
425
+ return [];
447
426
  }
448
427
  }
449
428
 
@@ -55,7 +55,7 @@ body.dark {
55
55
  .main-sidebar .btn-tab.quick-actions.universal-quick-actions {
56
56
  display: flex;
57
57
  flex-direction: column;
58
- gap: 6px;
58
+ gap: 4px;
59
59
  align-items: stretch;
60
60
  }
61
61
 
@@ -110,15 +110,15 @@ body.dark .main-sidebar .btn-tab.quick-actions.universal-quick-actions > .univer
110
110
  justify-content: flex-start !important;
111
111
  text-align: left;
112
112
  gap: 3px;
113
- height: 25px;
114
- min-height: 25px;
113
+ height: 30px;
114
+ min-height: 30px;
115
115
  margin: 0;
116
116
  padding: 0 8px;
117
117
  border: 1px solid var(--universal-quick-action-bg) !important;
118
118
  border-radius: 5px !important;
119
119
  background: var(--universal-quick-action-bg) !important;
120
120
  border-color: var(--universal-quick-action-bg) !important;
121
- color: var(--universal-quick-action-text) !important;
121
+ color: #fff !important;
122
122
  font-size: 12px;
123
123
  font-weight: 700;
124
124
  line-height: 1;
@@ -134,7 +134,7 @@ body.dark .main-sidebar .btn-tab.quick-actions.universal-quick-actions > .univer
134
134
  .main-sidebar .btn-tab.quick-actions.universal-quick-actions > .universal-create-menu > summary.btn:active {
135
135
  background: var(--universal-quick-action-bg-hover) !important;
136
136
  border-color: var(--universal-quick-action-bg-hover) !important;
137
- color: var(--universal-quick-action-text) !important;
137
+ color: #fff !important;
138
138
  }
139
139
 
140
140
  .main-sidebar .btn-tab.quick-actions.universal-quick-actions > #explore,
@@ -146,10 +146,12 @@ body.dark .main-sidebar .btn-tab.quick-actions.universal-quick-actions > #explor
146
146
  }
147
147
 
148
148
  .main-sidebar .btn-tab.quick-actions.universal-quick-actions > .btn i,
149
+ .main-sidebar .btn-tab.quick-actions.universal-quick-actions > .btn .quick-action-icon,
149
150
  .main-sidebar .btn-tab.quick-actions.universal-quick-actions > .btn .caption,
150
151
  .main-sidebar .btn-tab.quick-actions.universal-quick-actions > .universal-create-menu > summary.btn i,
152
+ .main-sidebar .btn-tab.quick-actions.universal-quick-actions > .universal-create-menu > summary.btn .quick-action-icon,
151
153
  .main-sidebar .btn-tab.quick-actions.universal-quick-actions > .universal-create-menu > summary.btn .caption {
152
- color: var(--universal-quick-action-text) !important;
154
+ color: #fff !important;
153
155
  }
154
156
 
155
157
  .main-sidebar .universal-create-caret {
@@ -20,29 +20,6 @@
20
20
  SSH_ASKPASS: '',
21
21
  GCM_INTERACTIVE: 'never',
22
22
  };
23
- const FALLBACK_TOOLS = [
24
- {
25
- value: 'code/claude',
26
- label: 'Claude Code',
27
- iconSrc: '/asset/plugin/code/claude/claude.png',
28
- isDefault: true,
29
- category: 'CLI',
30
- },
31
- {
32
- value: 'code/codex',
33
- label: 'OpenAI Codex',
34
- iconSrc: '/asset/plugin/code/codex/openai.webp',
35
- isDefault: false,
36
- category: 'CLI',
37
- },
38
- {
39
- value: 'code/gemini',
40
- label: 'Google Gemini CLI',
41
- iconSrc: '/asset/plugin/code/gemini/gemini.jpeg',
42
- isDefault: false,
43
- category: 'CLI',
44
- },
45
- ];
46
23
  const INTENTS = {
47
24
  create_app: {
48
25
  label: 'Create app',
@@ -267,12 +244,11 @@
267
244
  return res.json();
268
245
  })
269
246
  .then((payload) => {
270
- const tools = mapPluginMenuToTools(payload && Array.isArray(payload.menu) ? payload.menu : []);
271
- return tools.length > 0 ? tools : FALLBACK_TOOLS.slice();
247
+ return mapPluginMenuToTools(payload && Array.isArray(payload.menu) ? payload.menu : []);
272
248
  })
273
249
  .catch((error) => {
274
- console.warn('Falling back to default tools for universal launcher', error);
275
- return FALLBACK_TOOLS.slice();
250
+ console.warn('Failed to load plugins for universal launcher', error);
251
+ return [];
276
252
  })
277
253
  .finally(() => {
278
254
  loadingTools = null;
@@ -6,13 +6,25 @@ const axios = require('axios')
6
6
  const multer = require('multer')
7
7
  const FormData = require('form-data')
8
8
  const sanitize = require('sanitize-filename')
9
+ const AppLogReportService = require('../lib/app_log_report')
9
10
 
10
11
  const DEFAULT_PEER_PORT = 42000
11
12
  const DEFAULT_PEER_TIMEOUT_MS = 2500
12
13
  const DEFAULT_PEER_UPLOAD_TIMEOUT_MS = 30000
14
+ const REGISTRY_DRAFT_IMPORT_FIELD_LIMIT_BYTES = 1024 * 1024
15
+ const REGISTRY_DRAFT_IMPORT_TIMEOUT_MS = 60000
13
16
  const IPV4_HOST_PATTERN = /^(?:\d{1,3}\.){3}\d{1,3}$/
14
17
  const PINOKIO_REF_PROTOCOL = 'pinokio:'
15
18
 
19
+ const draftImportUpload = multer({
20
+ storage: multer.memoryStorage(),
21
+ limits: {
22
+ fieldSize: REGISTRY_DRAFT_IMPORT_FIELD_LIMIT_BYTES,
23
+ fields: 6,
24
+ files: 0
25
+ }
26
+ })
27
+
16
28
  const isQualifiedHost = (value = '') => {
17
29
  return IPV4_HOST_PATTERN.test(String(value || '').trim())
18
30
  }
@@ -57,6 +69,26 @@ const parseQualifiedAppId = (value = '') => {
57
69
  }
58
70
  }
59
71
 
72
+ const normalizeRegistryBase = (value = '') => {
73
+ const fallback = 'https://pinokio.co'
74
+ let url
75
+ try {
76
+ url = new URL(value || fallback)
77
+ } catch (_) {
78
+ throw new Error('Invalid registry URL')
79
+ }
80
+ const host = url.hostname.toLowerCase()
81
+ const allowedHttps = new Set(['pinokio.co', 'www.pinokio.co', 'beta.pinokio.co', 'api.pinokio.co'])
82
+ const allowedLocal = new Set(['localhost', '127.0.0.1', '::1', '[::1]'])
83
+ if (url.protocol === 'https:' && allowedHttps.has(host)) {
84
+ return url.origin
85
+ }
86
+ if (url.protocol === 'http:' && allowedLocal.has(host)) {
87
+ return url.origin
88
+ }
89
+ throw new Error('Registry URL is not allowed')
90
+ }
91
+
60
92
  const isLoopbackHost = (value = '') => {
61
93
  const normalized = String(value || '').trim().toLowerCase()
62
94
  return normalized === '127.0.0.1' || normalized === 'localhost' || normalized === '::1' || normalized === '[::1]'
@@ -135,10 +167,11 @@ const buildPinokioRef = ({ host, port, scope, id }) => {
135
167
  return `pinokio://${normalizedHost}:${normalizedPort}/${encodedPath}`
136
168
  }
137
169
 
138
- module.exports = function registerAppRoutes(app, { registry, preferences, appSearch, appLogs, getTheme }) {
170
+ module.exports = function registerAppRoutes(app, { registry, preferences, appSearch, appLogs, appLogReports, getTheme }) {
139
171
  if (!app || !registry || !preferences || !appSearch || !appLogs) {
140
172
  throw new Error('App routes require app, registry, preferences, appSearch, and appLogs')
141
173
  }
174
+ const appLogReportService = appLogReports || new AppLogReportService({ registry })
142
175
 
143
176
  const router = express.Router()
144
177
  const upload = multer()
@@ -1042,5 +1075,166 @@ module.exports = function registerAppRoutes(app, { registry, preferences, appSea
1042
1075
  })
1043
1076
  }))
1044
1077
 
1078
+ router.get('/apps/logs/:app_id/report', asyncHandler(async (req, res) => {
1079
+ const parsedAppId = parseQualifiedAppId(req.params.app_id)
1080
+ const requestedAppId = parsedAppId.app_id || req.params.app_id
1081
+ const remoteHost = parsedAppId.qualified ? parsedAppId.host : null
1082
+ if (remoteHost && remoteHost !== currentPeerHost()) {
1083
+ try {
1084
+ const params = {}
1085
+ const tail = registry.parseTailCount(req.query.tail, 800)
1086
+ if (Number.isFinite(tail) && tail > 0) {
1087
+ params.tail = String(tail)
1088
+ }
1089
+ if (req.query.redaction === 'none') {
1090
+ params.redaction = 'none'
1091
+ }
1092
+ const response = await axios.get(`http://${remoteHost}:${peerPort()}/apps/logs/${encodeURIComponent(requestedAppId)}/report`, {
1093
+ timeout: DEFAULT_PEER_TIMEOUT_MS,
1094
+ headers: peerRequestHeaders(req),
1095
+ params
1096
+ })
1097
+ const payload = response && response.data && typeof response.data === 'object'
1098
+ ? { ...response.data }
1099
+ : {}
1100
+ payload.app_id = qualifyAppId(requestedAppId, remoteHost)
1101
+ payload.source = buildSource(remoteHost, false)
1102
+ payload.ref = buildPinokioRef({
1103
+ host: remoteHost,
1104
+ port: peerPort(),
1105
+ scope: 'api',
1106
+ id: requestedAppId
1107
+ })
1108
+ res.json(payload)
1109
+ return
1110
+ } catch (error) {
1111
+ if (error && error.response) {
1112
+ res.status(error.response.status).json(error.response.data)
1113
+ return
1114
+ }
1115
+ res.status(502).json({
1116
+ error: 'Peer log report unavailable',
1117
+ app_id: qualifyAppId(requestedAppId, remoteHost),
1118
+ source: buildSource(remoteHost, false)
1119
+ })
1120
+ return
1121
+ }
1122
+ }
1123
+ const appId = registry.normalizeAppId(requestedAppId)
1124
+ if (!appId) {
1125
+ res.status(400).json({ error: 'Invalid app_id' })
1126
+ return
1127
+ }
1128
+ const status = await registry.buildAppStatus(appId, {
1129
+ source: req.$source || null
1130
+ })
1131
+ if (!status) {
1132
+ res.status(404).json({ error: 'App not found', app_id: appId })
1133
+ return
1134
+ }
1135
+ const tail = registry.parseTailCount(req.query.tail, 800)
1136
+ const report = await appLogReportService.buildReport({
1137
+ appId,
1138
+ status,
1139
+ tail,
1140
+ redact: req.query.redaction !== 'none'
1141
+ })
1142
+ if (!report) {
1143
+ res.status(404).json({ error: 'No log report available', app_id: appId })
1144
+ return
1145
+ }
1146
+ res.json({
1147
+ app_id: appId,
1148
+ ref: buildPinokioRef({
1149
+ host: currentPeerHost() || '127.0.0.1',
1150
+ port: peerPort(),
1151
+ scope: 'api',
1152
+ id: appId
1153
+ }),
1154
+ source: buildSource(currentPeerHost(), true),
1155
+ ...report
1156
+ })
1157
+ }))
1158
+
1159
+ router.post('/apps/logs/:app_id/drafts', draftImportUpload.none(), asyncHandler(async (req, res) => {
1160
+ const parsedAppId = parseQualifiedAppId(req.params.app_id)
1161
+ const requestedAppId = parsedAppId.app_id || req.params.app_id
1162
+ const remoteHost = parsedAppId.qualified ? parsedAppId.host : null
1163
+ if (remoteHost && remoteHost !== currentPeerHost()) {
1164
+ res.status(400).json({ error: 'Draft import is available for local workspaces.' })
1165
+ return
1166
+ }
1167
+
1168
+ const appId = registry.normalizeAppId(requestedAppId)
1169
+ if (!appId) {
1170
+ res.status(400).json({ error: 'Invalid app_id' })
1171
+ return
1172
+ }
1173
+ const status = await registry.buildAppStatus(appId, {
1174
+ source: req.$source || null
1175
+ })
1176
+ if (!status) {
1177
+ res.status(404).json({ error: 'App not found', app_id: appId })
1178
+ return
1179
+ }
1180
+
1181
+ const body = req.body || {}
1182
+ const token = typeof body.token === 'string' ? body.token.trim() : ''
1183
+ let registryBase
1184
+ try {
1185
+ registryBase = normalizeRegistryBase(typeof body.registry === 'string' ? body.registry.trim() : '')
1186
+ } catch (error) {
1187
+ res.status(400).json({ error: error && error.message ? error.message : 'Invalid registry URL' })
1188
+ return
1189
+ }
1190
+ const metadataB64 = typeof body.metadata_b64 === 'string' ? body.metadata_b64.trim() : ''
1191
+ if (!token) {
1192
+ res.status(400).json({ error: 'Registry authorization token is required.' })
1193
+ return
1194
+ }
1195
+ if (!metadataB64) {
1196
+ res.status(400).json({ error: 'Draft metadata is required.' })
1197
+ return
1198
+ }
1199
+
1200
+ let metadata
1201
+ try {
1202
+ metadata = JSON.parse(Buffer.from(metadataB64, 'base64').toString('utf8'))
1203
+ } catch (_) {
1204
+ res.status(400).json({ error: 'Draft metadata is invalid.' })
1205
+ return
1206
+ }
1207
+ if (!metadata || typeof metadata !== 'object' || typeof metadata.body !== 'string' || !metadata.body.trim()) {
1208
+ res.status(400).json({ error: 'Draft body is required.' })
1209
+ return
1210
+ }
1211
+
1212
+ const repoUrl = appLogReportService.sanitizeRemoteUrl(metadata.appRepoUrl || metadata.repoUrl || appLogReportService.readGitRemote(status.path))
1213
+ const enrichedMetadata = {
1214
+ ...metadata,
1215
+ appRepoUrl: repoUrl || metadata.appRepoUrl || metadata.repoUrl || undefined,
1216
+ repoUrl: repoUrl || metadata.repoUrl || undefined
1217
+ }
1218
+ const enrichedMetadataB64 = Buffer.from(JSON.stringify(enrichedMetadata), 'utf8').toString('base64')
1219
+ if (Buffer.byteLength(enrichedMetadataB64) > REGISTRY_DRAFT_IMPORT_FIELD_LIMIT_BYTES) {
1220
+ res.status(413).json({ error: 'Draft is too large for registry import.' })
1221
+ return
1222
+ }
1223
+
1224
+ const form = new FormData()
1225
+ form.append('metadata_b64', enrichedMetadataB64)
1226
+ const response = await axios.post(`${registryBase}/registry-bridge/draft-imports`, form, {
1227
+ headers: {
1228
+ ...form.getHeaders(),
1229
+ Authorization: `Bearer ${token}`
1230
+ },
1231
+ timeout: REGISTRY_DRAFT_IMPORT_TIMEOUT_MS,
1232
+ maxBodyLength: Infinity,
1233
+ maxContentLength: Infinity,
1234
+ validateStatus: () => true
1235
+ })
1236
+ res.status(response.status).json(response.data || {})
1237
+ }))
1238
+
1045
1239
  app.use(router)
1046
1240
  }