haansi 0.1.19 → 0.1.24
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/dist/haansi.js +186 -57
- package/package.json +1 -1
package/dist/haansi.js
CHANGED
|
@@ -39,7 +39,7 @@ var require_package = __commonJS({
|
|
|
39
39
|
"package.json"(exports2, module2) {
|
|
40
40
|
module2.exports = {
|
|
41
41
|
name: "haansi",
|
|
42
|
-
version: "0.1.
|
|
42
|
+
version: "0.1.24",
|
|
43
43
|
description: "Haansi CLI - Session collector and MCP server for Claude Code",
|
|
44
44
|
bin: {
|
|
45
45
|
haansi: "./dist/haansi.js"
|
|
@@ -141,7 +141,7 @@ If the browser didn't open, visit this URL:
|
|
|
141
141
|
);
|
|
142
142
|
}
|
|
143
143
|
console.log("Creating CLI token...");
|
|
144
|
-
const tokenRes = await fetch(`${apiUrl}/api/v1/cli-tokens`, {
|
|
144
|
+
const tokenRes = await fetch(`${apiUrl}/api/v1/access/cli-tokens`, {
|
|
145
145
|
method: "POST",
|
|
146
146
|
headers: {
|
|
147
147
|
Authorization: `Bearer ${jwt2}`,
|
|
@@ -206,9 +206,12 @@ function prompt(question) {
|
|
|
206
206
|
});
|
|
207
207
|
}
|
|
208
208
|
async function validateToken(token, apiUrl) {
|
|
209
|
-
const response = await fetch(
|
|
210
|
-
|
|
211
|
-
|
|
209
|
+
const response = await fetch(
|
|
210
|
+
`${apiUrl}/api/v1/memory/solutions/recent?limit=1`,
|
|
211
|
+
{
|
|
212
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
213
|
+
}
|
|
214
|
+
);
|
|
212
215
|
if (!response.ok) {
|
|
213
216
|
const body = await response.text().catch(() => "");
|
|
214
217
|
throw new Error(
|
|
@@ -820,7 +823,11 @@ async function apiGet(path, apiUrl, token) {
|
|
|
820
823
|
}
|
|
821
824
|
async function getUploadedSessions(apiUrl, token) {
|
|
822
825
|
try {
|
|
823
|
-
const data = await apiGet(
|
|
826
|
+
const data = await apiGet(
|
|
827
|
+
"/capture/sessions/uploaded",
|
|
828
|
+
apiUrl,
|
|
829
|
+
token
|
|
830
|
+
);
|
|
824
831
|
const map2 = /* @__PURE__ */ new Map();
|
|
825
832
|
for (const s of data.sessions) {
|
|
826
833
|
map2.set(s.sessionId, {
|
|
@@ -845,7 +852,7 @@ async function uploadSession(sessionId, content, apiUrl, token, gitInfo) {
|
|
|
845
852
|
if (gitInfo?.gitRemoteUrl) headers["x-git-remote-url"] = gitInfo.gitRemoteUrl;
|
|
846
853
|
if (gitInfo?.gitCommit) headers["x-git-commit"] = gitInfo.gitCommit;
|
|
847
854
|
if (gitInfo?.gitBranch) headers["x-git-branch"] = gitInfo.gitBranch;
|
|
848
|
-
const response = await fetch(`${apiUrl}/api/v1/sessions/raw`, {
|
|
855
|
+
const response = await fetch(`${apiUrl}/api/v1/capture/sessions/raw`, {
|
|
849
856
|
method: "POST",
|
|
850
857
|
headers,
|
|
851
858
|
body: new Uint8Array(body)
|
|
@@ -1991,23 +1998,18 @@ var init_collector_daemon = __esm({
|
|
|
1991
1998
|
}
|
|
1992
1999
|
});
|
|
1993
2000
|
|
|
1994
|
-
//
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
});
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
var init_user = __esm({
|
|
2003
|
-
"../../packages/@company/types/src/user.ts"() {
|
|
2004
|
-
"use strict";
|
|
2001
|
+
// ../service-capture/claude-sessions/http-timeout.ts
|
|
2002
|
+
async function fetchWithTimeout(url2, init2 = {}, timeoutMs = 15e3) {
|
|
2003
|
+
const controller = new AbortController();
|
|
2004
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2005
|
+
try {
|
|
2006
|
+
return await fetch(url2, { ...init2, signal: controller.signal });
|
|
2007
|
+
} finally {
|
|
2008
|
+
clearTimeout(timer);
|
|
2005
2009
|
}
|
|
2006
|
-
}
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
var init_integration = __esm({
|
|
2010
|
-
"../../packages/@company/types/src/integration.ts"() {
|
|
2010
|
+
}
|
|
2011
|
+
var init_http_timeout = __esm({
|
|
2012
|
+
"../service-capture/claude-sessions/http-timeout.ts"() {
|
|
2011
2013
|
"use strict";
|
|
2012
2014
|
}
|
|
2013
2015
|
});
|
|
@@ -2040,7 +2042,7 @@ function normalizeComplexity(raw) {
|
|
|
2040
2042
|
if (lower in COMPLEXITIES) return lower;
|
|
2041
2043
|
return "moderate";
|
|
2042
2044
|
}
|
|
2043
|
-
var SESSION_TYPES, SESSION_TYPE_ALIASES, COMPLEXITIES;
|
|
2045
|
+
var SESSION_TYPES, SESSION_TYPE_ALIASES, COMPLEXITIES, ARTIFACT_TYPES, __seenAliases, ALIAS_REVERSE_MAP, ARTIFACT_TYPE_IDS, AUTO_APPROVED_ARTIFACT_TYPES;
|
|
2044
2046
|
var init_taxonomy = __esm({
|
|
2045
2047
|
"../../packages/@company/types/src/taxonomy.ts"() {
|
|
2046
2048
|
"use strict";
|
|
@@ -2098,18 +2100,132 @@ var init_taxonomy = __esm({
|
|
|
2098
2100
|
description: "Involved task with many steps or deep analysis"
|
|
2099
2101
|
}
|
|
2100
2102
|
};
|
|
2103
|
+
ARTIFACT_TYPES = {
|
|
2104
|
+
report_incident: {
|
|
2105
|
+
category: "report",
|
|
2106
|
+
label: "Incident Report",
|
|
2107
|
+
description: "Production-incident record + post-incident writeup: detection, timeline, root cause, resolution, follow-ups.",
|
|
2108
|
+
icon: "AlertTriangle",
|
|
2109
|
+
aliases: ["incident_summary", "incident", "postmortem", "post-mortem"],
|
|
2110
|
+
autoApproved: false,
|
|
2111
|
+
producedBy: [
|
|
2112
|
+
"POST /api/v1/memory/processes/incidents",
|
|
2113
|
+
"POST /api/v1/memory/reports/incident",
|
|
2114
|
+
"POST /api/v1/memory/processes/incidents/:id/postmortem"
|
|
2115
|
+
]
|
|
2116
|
+
},
|
|
2117
|
+
report_decision: {
|
|
2118
|
+
category: "report",
|
|
2119
|
+
label: "Decision Record",
|
|
2120
|
+
description: "Architectural / product decision record with context, options considered, decision, and rationale.",
|
|
2121
|
+
icon: "Scale",
|
|
2122
|
+
aliases: ["decision_record", "decision-record", "adr"],
|
|
2123
|
+
autoApproved: false,
|
|
2124
|
+
producedBy: ["POST /api/v1/memory"]
|
|
2125
|
+
},
|
|
2126
|
+
report_work: {
|
|
2127
|
+
category: "report",
|
|
2128
|
+
label: "Work Report",
|
|
2129
|
+
description: "Periodic or one-off work-progress report \u2014 status updates, weekly digests, project rollups, team-activity summaries.",
|
|
2130
|
+
icon: "CalendarDays",
|
|
2131
|
+
aliases: ["weekly_digest", "weekly-digest", "status_report"],
|
|
2132
|
+
autoApproved: true,
|
|
2133
|
+
producedBy: ["POST /api/v1/memory", "POST /api/v1/memory/reports/work"]
|
|
2134
|
+
},
|
|
2135
|
+
report_sop: {
|
|
2136
|
+
category: "report",
|
|
2137
|
+
label: "Standard Operating Procedure",
|
|
2138
|
+
description: "Reusable step-by-step procedure or runbook. Placeholder \u2014 surface not yet specialized; written via generic POST /memory until a dedicated producer ships.",
|
|
2139
|
+
icon: "FileText",
|
|
2140
|
+
aliases: ["sop", "runbook"],
|
|
2141
|
+
autoApproved: false,
|
|
2142
|
+
producedBy: ["POST /api/v1/memory"]
|
|
2143
|
+
},
|
|
2144
|
+
report_onboarding: {
|
|
2145
|
+
category: "report",
|
|
2146
|
+
label: "Onboarding Packet",
|
|
2147
|
+
description: "Onboarding guide assembled from team knowledge, scoped to a project or role.",
|
|
2148
|
+
icon: "GraduationCap",
|
|
2149
|
+
aliases: ["onboarding_packet", "onboarding-packet"],
|
|
2150
|
+
autoApproved: false,
|
|
2151
|
+
producedBy: ["POST /api/v1/memory/reports/onboarding"]
|
|
2152
|
+
},
|
|
2153
|
+
report_pr: {
|
|
2154
|
+
category: "report",
|
|
2155
|
+
label: "PR Context",
|
|
2156
|
+
description: "Enriched pull-request description with linked Jira/Slack context, recent decisions, and reviewer hints.",
|
|
2157
|
+
icon: "GitPullRequest",
|
|
2158
|
+
aliases: ["pr_context", "pr-context"],
|
|
2159
|
+
autoApproved: true,
|
|
2160
|
+
producedBy: ["POST /api/v1/memory/reports/pr"]
|
|
2161
|
+
},
|
|
2162
|
+
expert: {
|
|
2163
|
+
category: "expert",
|
|
2164
|
+
label: "Expert Profile",
|
|
2165
|
+
description: "Per-person expertise profile across platforms \u2014 areas of focus, recent contributions, identity links.",
|
|
2166
|
+
icon: "Users",
|
|
2167
|
+
aliases: ["expert_profile", "expert-profile"],
|
|
2168
|
+
autoApproved: false,
|
|
2169
|
+
producedBy: ["POST /api/v1/memory/experts"]
|
|
2170
|
+
},
|
|
2171
|
+
solution_mined: {
|
|
2172
|
+
category: "solution",
|
|
2173
|
+
label: "Mined Solution",
|
|
2174
|
+
description: "Problem + solution mined automatically from a developer coding session (the distilled-path pipeline).",
|
|
2175
|
+
icon: "Lightbulb",
|
|
2176
|
+
aliases: ["solution", "mined_solution", "distilled_path"],
|
|
2177
|
+
autoApproved: true,
|
|
2178
|
+
producedBy: ["POST /api/v1/memory/solutions/mined"]
|
|
2179
|
+
},
|
|
2180
|
+
solution_qna: {
|
|
2181
|
+
category: "solution",
|
|
2182
|
+
label: "Q&A Solution",
|
|
2183
|
+
description: "Q&A answer marked verified by a user and distilled into reusable knowledge \u2014 promoted out of a conversation thread.",
|
|
2184
|
+
icon: "ThumbsUp",
|
|
2185
|
+
aliases: ["verified_answer", "verified-answer"],
|
|
2186
|
+
autoApproved: true,
|
|
2187
|
+
producedBy: ["POST /api/v1/memory/recall/messages/:id/feedback"]
|
|
2188
|
+
}
|
|
2189
|
+
};
|
|
2190
|
+
__seenAliases = /* @__PURE__ */ new Set();
|
|
2191
|
+
for (const [id, meta3] of Object.entries(ARTIFACT_TYPES)) {
|
|
2192
|
+
for (const a of meta3.aliases) {
|
|
2193
|
+
if (__seenAliases.has(a)) {
|
|
2194
|
+
throw new Error(
|
|
2195
|
+
`[taxonomy] alias "${a}" declared on multiple artifact types; last seen on "${id}"`
|
|
2196
|
+
);
|
|
2197
|
+
}
|
|
2198
|
+
__seenAliases.add(a);
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
ALIAS_REVERSE_MAP = Object.freeze(
|
|
2202
|
+
Object.fromEntries(
|
|
2203
|
+
Object.entries(ARTIFACT_TYPES).flatMap(
|
|
2204
|
+
([id, meta3]) => meta3.aliases.map((a) => [a, id])
|
|
2205
|
+
)
|
|
2206
|
+
)
|
|
2207
|
+
);
|
|
2208
|
+
ARTIFACT_TYPE_IDS = Object.keys(
|
|
2209
|
+
ARTIFACT_TYPES
|
|
2210
|
+
);
|
|
2211
|
+
AUTO_APPROVED_ARTIFACT_TYPES = ARTIFACT_TYPE_IDS.filter((id) => ARTIFACT_TYPES[id].autoApproved);
|
|
2212
|
+
}
|
|
2213
|
+
});
|
|
2214
|
+
|
|
2215
|
+
// ../../packages/@company/types/src/embedding.ts
|
|
2216
|
+
var init_embedding = __esm({
|
|
2217
|
+
"../../packages/@company/types/src/embedding.ts"() {
|
|
2218
|
+
"use strict";
|
|
2101
2219
|
}
|
|
2102
2220
|
});
|
|
2103
2221
|
|
|
2104
2222
|
// ../../packages/@company/types/src/index.ts
|
|
2105
2223
|
var init_src = __esm({
|
|
2106
2224
|
"../../packages/@company/types/src/index.ts"() {
|
|
2107
|
-
init_knowledge();
|
|
2108
|
-
init_user();
|
|
2109
|
-
init_integration();
|
|
2110
2225
|
init_timeline();
|
|
2111
2226
|
init_artifact();
|
|
2112
2227
|
init_taxonomy();
|
|
2228
|
+
init_embedding();
|
|
2113
2229
|
}
|
|
2114
2230
|
});
|
|
2115
2231
|
|
|
@@ -56839,7 +56955,9 @@ async function apiGet2(path, options) {
|
|
|
56839
56955
|
Authorization: `Bearer ${TOKEN}`,
|
|
56840
56956
|
...options?.extraHeaders
|
|
56841
56957
|
};
|
|
56842
|
-
const response = await
|
|
56958
|
+
const response = await fetchWithTimeout(`${API_URL}/api/v1${path}`, {
|
|
56959
|
+
headers
|
|
56960
|
+
});
|
|
56843
56961
|
if (!response.ok) {
|
|
56844
56962
|
const body = await response.text().catch(() => "");
|
|
56845
56963
|
throw new Error(`API error ${response.status}: ${body.slice(0, 200)}`);
|
|
@@ -56852,7 +56970,7 @@ async function apiPost(path, body, options) {
|
|
|
56852
56970
|
"Content-Type": "application/json",
|
|
56853
56971
|
...options?.extraHeaders
|
|
56854
56972
|
};
|
|
56855
|
-
const response = await
|
|
56973
|
+
const response = await fetchWithTimeout(`${API_URL}/api/v1${path}`, {
|
|
56856
56974
|
method: "POST",
|
|
56857
56975
|
headers,
|
|
56858
56976
|
body: JSON.stringify(body)
|
|
@@ -56863,16 +56981,14 @@ async function apiPost(path, body, options) {
|
|
|
56863
56981
|
}
|
|
56864
56982
|
return response.json();
|
|
56865
56983
|
}
|
|
56866
|
-
|
|
56867
|
-
|
|
56868
|
-
|
|
56869
|
-
|
|
56870
|
-
}
|
|
56871
|
-
try {
|
|
56872
|
-
await apiGet2("/users/me/preferences");
|
|
56873
|
-
} catch {
|
|
56874
|
-
}
|
|
56984
|
+
function warmupInBackground() {
|
|
56985
|
+
void apiGet2("/memory/solutions/recent?limit=1").catch(() => {
|
|
56986
|
+
});
|
|
56987
|
+
void apiGet2("/workspace/users/me/preferences").catch(() => {
|
|
56988
|
+
});
|
|
56875
56989
|
triggerCollectInBackground();
|
|
56990
|
+
}
|
|
56991
|
+
async function main3() {
|
|
56876
56992
|
const transportMode = process.env.MCP_TRANSPORT || "stdio";
|
|
56877
56993
|
if (transportMode === "http") {
|
|
56878
56994
|
const express = await Promise.resolve().then(() => __toESM(require_express2()));
|
|
@@ -56895,6 +57011,7 @@ async function main3() {
|
|
|
56895
57011
|
const transport = new StdioServerTransport();
|
|
56896
57012
|
await mcpServer.connect(transport);
|
|
56897
57013
|
}
|
|
57014
|
+
warmupInBackground();
|
|
56898
57015
|
}
|
|
56899
57016
|
var import_dotenv3, import_path3, import_fs3, import_os2, DEFAULT_API_URL4, CREDENTIALS_FILE4, API_URL, TOKEN, contextSchema, isCollecting, lastCollectTime, COLLECT_COOLDOWN_MS, mcpServer, server;
|
|
56900
57017
|
var init_mcp_server2 = __esm({
|
|
@@ -56905,6 +57022,7 @@ var init_mcp_server2 = __esm({
|
|
|
56905
57022
|
import_fs3 = require("fs");
|
|
56906
57023
|
import_os2 = require("os");
|
|
56907
57024
|
init_collector();
|
|
57025
|
+
init_http_timeout();
|
|
56908
57026
|
init_src();
|
|
56909
57027
|
init_mcp();
|
|
56910
57028
|
init_stdio2();
|
|
@@ -57081,7 +57199,7 @@ var init_mcp_server2 = __esm({
|
|
|
57081
57199
|
extraHeaders["X-Session-Org-Id"] = String(_context.orgId);
|
|
57082
57200
|
}
|
|
57083
57201
|
const data = await apiGet2(
|
|
57084
|
-
`/
|
|
57202
|
+
`/memory/solutions/search?q=${encodeURIComponent(query)}&top_k=${k}`,
|
|
57085
57203
|
{ extraHeaders }
|
|
57086
57204
|
);
|
|
57087
57205
|
if (!data.results || data.results.length === 0) {
|
|
@@ -57113,7 +57231,7 @@ var init_mcp_server2 = __esm({
|
|
|
57113
57231
|
if (_context?.orgId) {
|
|
57114
57232
|
extraHeaders["X-Session-Org-Id"] = String(_context.orgId);
|
|
57115
57233
|
}
|
|
57116
|
-
const data = await apiGet2(`/
|
|
57234
|
+
const data = await apiGet2(`/memory/solutions/recent?limit=${n}`, {
|
|
57117
57235
|
extraHeaders
|
|
57118
57236
|
});
|
|
57119
57237
|
if (!data.results || data.results.length === 0) {
|
|
@@ -57156,7 +57274,7 @@ var init_mcp_server2 = __esm({
|
|
|
57156
57274
|
extraHeaders["X-Session-Org-Id"] = String(_context.orgId);
|
|
57157
57275
|
}
|
|
57158
57276
|
const data = await apiPost(
|
|
57159
|
-
"/
|
|
57277
|
+
"/memory/solutions/mined",
|
|
57160
57278
|
{
|
|
57161
57279
|
problem_description,
|
|
57162
57280
|
solution_summary,
|
|
@@ -57270,7 +57388,7 @@ async function setupMcp(targetArg) {
|
|
|
57270
57388
|
warnings.push(msg);
|
|
57271
57389
|
} else {
|
|
57272
57390
|
const apiUrl = process.env.HAANSI_API_URL ?? DEFAULT_API_URL5;
|
|
57273
|
-
const exchangeUrl = `${apiUrl.replace(/\/+$/, "")}/api/v1/
|
|
57391
|
+
const exchangeUrl = `${apiUrl.replace(/\/+$/, "")}/api/v1/access/exchange/mcp`;
|
|
57274
57392
|
config6.mcpServers["haansi-exchange"] = {
|
|
57275
57393
|
type: "http",
|
|
57276
57394
|
url: exchangeUrl,
|
|
@@ -57423,9 +57541,12 @@ async function config5(args) {
|
|
|
57423
57541
|
const subcommand = args[0];
|
|
57424
57542
|
if (subcommand === "get") {
|
|
57425
57543
|
const key = args[1];
|
|
57426
|
-
const response = await fetch(
|
|
57427
|
-
|
|
57428
|
-
|
|
57544
|
+
const response = await fetch(
|
|
57545
|
+
`${apiUrl}/api/v1/workspace/users/me/preferences`,
|
|
57546
|
+
{
|
|
57547
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
57548
|
+
}
|
|
57549
|
+
);
|
|
57429
57550
|
if (!response.ok) {
|
|
57430
57551
|
const body = await response.text().catch(() => "");
|
|
57431
57552
|
console.error(
|
|
@@ -57467,14 +57588,17 @@ async function config5(args) {
|
|
|
57467
57588
|
process.exit(1);
|
|
57468
57589
|
return;
|
|
57469
57590
|
}
|
|
57470
|
-
const response = await fetch(
|
|
57471
|
-
|
|
57472
|
-
|
|
57473
|
-
|
|
57474
|
-
|
|
57475
|
-
|
|
57476
|
-
|
|
57477
|
-
|
|
57591
|
+
const response = await fetch(
|
|
57592
|
+
`${apiUrl}/api/v1/workspace/users/me/preferences`,
|
|
57593
|
+
{
|
|
57594
|
+
method: "PATCH",
|
|
57595
|
+
headers: {
|
|
57596
|
+
Authorization: `Bearer ${token}`,
|
|
57597
|
+
"Content-Type": "application/json"
|
|
57598
|
+
},
|
|
57599
|
+
body: JSON.stringify({ [key]: value })
|
|
57600
|
+
}
|
|
57601
|
+
);
|
|
57478
57602
|
if (!response.ok) {
|
|
57479
57603
|
const body = await response.text().catch(() => "");
|
|
57480
57604
|
console.error(
|
|
@@ -57557,13 +57681,13 @@ async function list(options) {
|
|
|
57557
57681
|
let data;
|
|
57558
57682
|
if (options.search) {
|
|
57559
57683
|
data = await apiGet3(
|
|
57560
|
-
`/
|
|
57684
|
+
`/memory/solutions/search?q=${encodeURIComponent(options.search)}&top_k=${options.limit}`,
|
|
57561
57685
|
token,
|
|
57562
57686
|
apiUrl
|
|
57563
57687
|
);
|
|
57564
57688
|
} else {
|
|
57565
57689
|
data = await apiGet3(
|
|
57566
|
-
`/
|
|
57690
|
+
`/memory/solutions/recent?limit=${options.limit}`,
|
|
57567
57691
|
token,
|
|
57568
57692
|
apiUrl
|
|
57569
57693
|
);
|
|
@@ -57580,7 +57704,7 @@ ${data.results.length} result(s)`);
|
|
|
57580
57704
|
async function searchKnowledge(options) {
|
|
57581
57705
|
const apiUrl = process.env.HAANSI_API_URL ?? DEFAULT_API_URL7;
|
|
57582
57706
|
const token = resolveToken5();
|
|
57583
|
-
let url2 = `/
|
|
57707
|
+
let url2 = `/memory/solutions/knowledge/search?q=${encodeURIComponent(options.query)}&limit=${options.limit}`;
|
|
57584
57708
|
if (options.artifactType) {
|
|
57585
57709
|
url2 += `&artifact_type=${encodeURIComponent(options.artifactType)}`;
|
|
57586
57710
|
}
|
|
@@ -57605,7 +57729,12 @@ async function saveKnowledge(options) {
|
|
|
57605
57729
|
if (options.sessionType) body.session_type = options.sessionType;
|
|
57606
57730
|
if (options.complexity) body.complexity = options.complexity;
|
|
57607
57731
|
if (options.tags) body.tags = options.tags;
|
|
57608
|
-
const data = await apiPost2(
|
|
57732
|
+
const data = await apiPost2(
|
|
57733
|
+
"/memory/solutions/mined",
|
|
57734
|
+
body,
|
|
57735
|
+
token,
|
|
57736
|
+
apiUrl
|
|
57737
|
+
);
|
|
57609
57738
|
console.log("Knowledge saved successfully.");
|
|
57610
57739
|
console.log(` Problem: ${data.problem_description}`);
|
|
57611
57740
|
console.log(` Solution: ${data.solution_summary}`);
|