@pensar/apex 1.7.0 → 1.8.0-canary.2f3da4a2

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 (38) hide show
  1. package/build/{agent-beywhvf3.js → agent-5mbtsdes.js} +7 -7
  2. package/build/agent-scb9w0fv.js +19 -0
  3. package/build/{auth-wvh553ea.js → auth-0afhfypr.js} +4 -4
  4. package/build/{authentication-ec7trwb4.js → authentication-4eqmx3z4.js} +7 -7
  5. package/build/{blackboxAgent-ng2t2p2x.js → blackboxAgent-nb0v0ptg.js} +8 -8
  6. package/build/{blackboxPentest-rwyjy4kq.js → blackboxPentest-n9m7hbn8.js} +12 -12
  7. package/build/{cli-5m0347h3.js → cli-114rvgvj.js} +1 -1
  8. package/build/{cli-x3k26g1t.js → cli-230tpm4a.js} +146 -123
  9. package/build/{cli-06zt0g1a.js → cli-26tcc4x1.js} +2 -2
  10. package/build/{cli-nqx9y9ds.js → cli-629hvmba.js} +1 -1
  11. package/build/{cli-xtqm11qt.js → cli-91tyknkx.js} +1 -1
  12. package/build/{cli-r879p2yz.js → cli-etxprvkj.js} +1 -1
  13. package/build/{cli-vvyq7ace.js → cli-g90c7ajw.js} +1 -1
  14. package/build/{cli-zr7sg2m2.js → cli-jswt85hp.js} +2 -2
  15. package/build/{cli-40ef01tb.js → cli-kc8akm2b.js} +1 -1
  16. package/build/{cli-09prdch1.js → cli-ndp0xr8m.js} +6 -6
  17. package/build/{cli-836bfgxg.js → cli-nhb9w2dk.js} +1 -1
  18. package/build/{cli-gr3zncst.js → cli-nqynaq47.js} +1 -1
  19. package/build/{cli-rc7hyq7e.js → cli-rzrxttza.js} +55 -6
  20. package/build/{cli-q2dty8g4.js → cli-xf5qehnx.js} +1 -1
  21. package/build/cli.js +31 -27
  22. package/build/{fixes-1z283wdz.js → fixes-c8fa4er8.js} +4 -4
  23. package/build/{index-a4ydz3dd.js → index-5035hx9j.js} +7 -7
  24. package/build/{index-py7gtxez.js → index-6bsbwhyw.js} +2 -2
  25. package/build/{index-5a173a2k.js → index-eata65cj.js} +6 -6
  26. package/build/{index-1p5bg26t.js → index-k41ryzgs.js} +4 -4
  27. package/build/{index-j3hw6d4w.js → index-vhcxarqs.js} +58 -48
  28. package/build/{issues-trbzy8n0.js → issues-hgvycr7p.js} +4 -4
  29. package/build/{logs-c88md0h3.js → logs-0560s383.js} +4 -4
  30. package/build/{offesecAgent-ahcz5hcx.js → offesecAgent-vvrhkr8r.js} +7 -7
  31. package/build/{pentest-4932ke3a.js → pentest-kpn26j7f.js} +12 -12
  32. package/build/{pentests-re8dzxt9.js → pentests-s5x17s2j.js} +4 -4
  33. package/build/{projects-dqp4m0ws.js → projects-0qkjmzbw.js} +4 -4
  34. package/build/{targetedPentest-de8a67va.js → targetedPentest-4bj9654f.js} +8 -8
  35. package/build/{threatModel-waz866yk.js → threatModel-pe3svbnc.js} +8 -8
  36. package/build/{uninstall-0bwz7jdn.js → uninstall-tv9t0pgc.js} +1 -1
  37. package/package.json +1 -1
  38. package/build/agent-63cc9rpx.js +0 -19
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  OffensiveSecurityAgent
3
- } from "./cli-x3k26g1t.js";
3
+ } from "./cli-230tpm4a.js";
4
4
  import {
5
5
  __commonJS,
6
6
  __require
@@ -2,7 +2,7 @@ import {
2
2
  CweEntrySchema,
3
3
  ValidatedCweEntrySchema,
4
4
  hasCanonicalName
5
- } from "./cli-x3k26g1t.js";
5
+ } from "./cli-230tpm4a.js";
6
6
  import {
7
7
  exports_external1 as exports_external,
8
8
  init_zod
@@ -71,12 +71,12 @@ import {
71
71
  } from "./cli-0ghkg3w6.js";
72
72
  import {
73
73
  signGatewayRequest
74
- } from "./cli-xtqm11qt.js";
74
+ } from "./cli-91tyknkx.js";
75
75
  import {
76
76
  config,
77
77
  ensureValidToken,
78
78
  getPensarGatewayUrl
79
- } from "./cli-q2dty8g4.js";
79
+ } from "./cli-xf5qehnx.js";
80
80
  import {
81
81
  getModelInfo,
82
82
  init_models
@@ -34884,7 +34884,41 @@ function checkIfRateLimitError(error) {
34884
34884
  return errorMessage.includes("too many tokens") || errorMessage.includes("rate limit") || errorMessage.includes("request rate") || errorMessage.includes("throttl") || errorMessage.includes("overloaded") || errorMessage.includes("too many requests") || errorMessage.includes("please wait") || errorMessage.includes("service unavailable") || errorName.includes("throttl") || errorName.includes("toomanyrequests") || errorName.includes("serviceunavailable") || errorCode === "rate_limit_exceeded" || errorCode === "throttling" || errorCode === "429" || httpStatus === 429 || httpStatus === 529 || httpStatus === 503 || errorString.includes("throttl") || errorString.includes("too many") || errorString.includes("request rate");
34885
34885
  }
34886
34886
  var MAX_RATE_LIMIT_RETRIES = 20;
34887
- function wrapStreamWithErrorHandler(originalStream, messagesContainer, opts, model, silent, rateLimitRetryCount = 0) {
34887
+ var MAX_IDLE_RESUME_RETRIES = 3;
34888
+ var STREAM_IDLE_TIMEOUT_MS = 5 * 60 * 1000;
34889
+
34890
+ class StreamIdleTimeoutError extends Error {
34891
+ constructor(idleMs) {
34892
+ super(`Stream idle for ${Math.round(idleMs / 1000)}s — no chunks received`);
34893
+ this.name = "StreamIdleTimeoutError";
34894
+ }
34895
+ }
34896
+ async function* withIdleTimeout(stream, idleMs) {
34897
+ const iterator = stream[Symbol.asyncIterator]();
34898
+ try {
34899
+ while (true) {
34900
+ let timer;
34901
+ const result = await Promise.race([
34902
+ iterator.next(),
34903
+ new Promise((_, reject) => {
34904
+ timer = setTimeout(() => reject(new StreamIdleTimeoutError(idleMs)), idleMs);
34905
+ if (typeof timer === "object" && "unref" in timer)
34906
+ timer.unref();
34907
+ })
34908
+ ]);
34909
+ clearTimeout(timer);
34910
+ if (result.done)
34911
+ return;
34912
+ yield result.value;
34913
+ }
34914
+ } finally {
34915
+ iterator.return?.()?.catch?.(() => {});
34916
+ }
34917
+ }
34918
+ function isStreamIdleTimeoutError(error) {
34919
+ return error instanceof StreamIdleTimeoutError;
34920
+ }
34921
+ function wrapStreamWithErrorHandler(originalStream, messagesContainer, opts, model, silent, rateLimitRetryCount = 0, idleResumeCount = 0) {
34888
34922
  let wrappedStream = null;
34889
34923
  const handler = {
34890
34924
  get(target, prop) {
@@ -34892,7 +34926,7 @@ function wrapStreamWithErrorHandler(originalStream, messagesContainer, opts, mod
34892
34926
  if (!wrappedStream) {
34893
34927
  wrappedStream = async function* () {
34894
34928
  try {
34895
- for await (const chunk of originalStream.fullStream) {
34929
+ for await (const chunk of withIdleTimeout(originalStream.fullStream, STREAM_IDLE_TIMEOUT_MS)) {
34896
34930
  if (chunk.type === "error") {
34897
34931
  throw chunk.error;
34898
34932
  }
@@ -34901,6 +34935,21 @@ function wrapStreamWithErrorHandler(originalStream, messagesContainer, opts, mod
34901
34935
  } catch (error) {
34902
34936
  const errorMessage = error instanceof Error ? error.message : String(error);
34903
34937
  const isCtxError = checkIfContextLengthError(error);
34938
+ if (!isCtxError && isStreamIdleTimeoutError(error) && idleResumeCount < MAX_IDLE_RESUME_RETRIES && messagesContainer.current.length > 0) {
34939
+ const nextIdleCount = idleResumeCount + 1;
34940
+ if (!silent) {
34941
+ console.warn(`Stream stalled (attempt ${nextIdleCount}/${MAX_IDLE_RESUME_RETRIES}), resuming with ${messagesContainer.current.length} messages: ${errorMessage}`);
34942
+ }
34943
+ const retriedStream = streamResponse({
34944
+ ...opts,
34945
+ messages: messagesContainer.current
34946
+ });
34947
+ const wrappedRetriedStream = wrapStreamWithErrorHandler(retriedStream, messagesContainer, opts, model, silent, rateLimitRetryCount, nextIdleCount);
34948
+ for await (const chunk of wrappedRetriedStream.fullStream) {
34949
+ yield chunk;
34950
+ }
34951
+ return;
34952
+ }
34904
34953
  if (!isCtxError && checkIfRateLimitError(error) && rateLimitRetryCount < MAX_RATE_LIMIT_RETRIES) {
34905
34954
  const nextRetryCount = rateLimitRetryCount + 1;
34906
34955
  const delayMs = Math.min(1000 * nextRetryCount, 30000);
@@ -34912,7 +34961,7 @@ function wrapStreamWithErrorHandler(originalStream, messagesContainer, opts, mod
34912
34961
  ...opts,
34913
34962
  messages: messagesContainer.current.length > 0 ? messagesContainer.current : undefined
34914
34963
  });
34915
- const wrappedRetriedStream = wrapStreamWithErrorHandler(retriedStream, messagesContainer, opts, model, silent, nextRetryCount);
34964
+ const wrappedRetriedStream = wrapStreamWithErrorHandler(retriedStream, messagesContainer, opts, model, silent, nextRetryCount, idleResumeCount);
34916
34965
  for await (const chunk of wrappedRetriedStream.fullStream) {
34917
34966
  yield chunk;
34918
34967
  }
@@ -34940,7 +34989,7 @@ function wrapStreamWithErrorHandler(originalStream, messagesContainer, opts, mod
34940
34989
  ...opts,
34941
34990
  messages: compacted
34942
34991
  });
34943
- const wrappedRetry = wrapStreamWithErrorHandler(retried, messagesContainer, opts, model, silent, rateLimitRetryCount);
34992
+ const wrappedRetry = wrapStreamWithErrorHandler(retried, messagesContainer, opts, model, silent, rateLimitRetryCount, idleResumeCount);
34944
34993
  for await (const chunk of wrappedRetry.fullStream) {
34945
34994
  yield chunk;
34946
34995
  }
@@ -2,7 +2,7 @@ import {
2
2
  get,
3
3
  init,
4
4
  update
5
- } from "./cli-40ef01tb.js";
5
+ } from "./cli-kc8akm2b.js";
6
6
 
7
7
  // src/core/api/constants.ts
8
8
  var PENSAR_API_BASE_URL = "https://api.pensar.dev";
package/build/cli.js CHANGED
@@ -6,15 +6,15 @@ import {
6
6
  import"./cli-3y0dgy56.js";
7
7
  import {
8
8
  init_toolset
9
- } from "./cli-rc7hyq7e.js";
9
+ } from "./cli-rzrxttza.js";
10
10
  import"./cli-0ghkg3w6.js";
11
- import"./cli-xtqm11qt.js";
12
- import"./cli-q2dty8g4.js";
11
+ import"./cli-91tyknkx.js";
12
+ import"./cli-xf5qehnx.js";
13
13
  import"./cli-gpnb45ck.js";
14
- import"./cli-40ef01tb.js";
14
+ import"./cli-kc8akm2b.js";
15
15
  import {
16
16
  package_default
17
- } from "./cli-r879p2yz.js";
17
+ } from "./cli-etxprvkj.js";
18
18
  import {
19
19
  init_models
20
20
  } from "./cli-03z6pswp.js";
@@ -25,7 +25,7 @@ import {
25
25
  // package.json
26
26
  var package_default2 = {
27
27
  name: "@pensar/apex",
28
- version: "1.7.0",
28
+ version: "1.8.0-canary.2f3da4a2",
29
29
  description: "AI-powered penetration testing CLI tool with terminal UI",
30
30
  module: "src/tui/index.tsx",
31
31
  main: "build/cli.js",
@@ -500,7 +500,7 @@ async function resolveCliModel() {
500
500
  const explicit = getArg("--model");
501
501
  if (explicit)
502
502
  return explicit;
503
- const { config: appConfig } = await import("./index-py7gtxez.js");
503
+ const { config: appConfig } = await import("./index-6bsbwhyw.js");
504
504
  const { getDefaultModelForConfig } = await import("./utils-jf52rmrb.js");
505
505
  const pensarConfig = await appConfig.get();
506
506
  const defaultModel = getDefaultModelForConfig(pensarConfig);
@@ -539,6 +539,7 @@ Usage:
539
539
 
540
540
  operator options (-p):
541
541
  -p, --prompt <text|@file> (required) Prompt for the operator agent
542
+ -s, --system <text|@file> Override the default system prompt
542
543
  --target <url> Target URL / domain / IP
543
544
  --model <model> AI model (default: auto-selected from configured provider)
544
545
 
@@ -572,9 +573,9 @@ Global options:
572
573
  async function runPentest() {
573
574
  const { config: config2 } = await import("./main-3d7dfdvs.js").then((m)=>__toESM(m.default,1));
574
575
  config2();
575
- const { runPentestAgent } = await import("./blackboxPentest-rwyjy4kq.js");
576
- const { sessions } = await import("./index-5a173a2k.js");
577
- const { config: appConfig } = await import("./index-py7gtxez.js");
576
+ const { runPentestAgent } = await import("./blackboxPentest-n9m7hbn8.js");
577
+ const { sessions } = await import("./index-eata65cj.js");
578
+ const { config: appConfig } = await import("./index-6bsbwhyw.js");
578
579
  const target = getArgRequired("--target");
579
580
  const cwd = getArg("--cwd");
580
581
  const mode = getArg("--mode");
@@ -638,9 +639,9 @@ Report: ${reportPath}` : ""}`);
638
639
  async function runTargetedPentest() {
639
640
  const { config: config2 } = await import("./main-3d7dfdvs.js").then((m)=>__toESM(m.default,1));
640
641
  config2();
641
- const { runTargetedPentestAgent } = await import("./targetedPentest-de8a67va.js");
642
- const { sessions } = await import("./index-5a173a2k.js");
643
- const { config: appConfig } = await import("./index-py7gtxez.js");
642
+ const { runTargetedPentestAgent } = await import("./targetedPentest-4bj9654f.js");
643
+ const { sessions } = await import("./index-eata65cj.js");
644
+ const { config: appConfig } = await import("./index-6bsbwhyw.js");
644
645
  const target = getArgRequired("--target");
645
646
  const objectives = getAllArgs("--objective");
646
647
  const pensarConfig = await appConfig.get();
@@ -688,8 +689,8 @@ POCs: ${pocsPath}`);
688
689
  async function runThreatModel() {
689
690
  const { config: config2 } = await import("./main-3d7dfdvs.js").then((m)=>__toESM(m.default,1));
690
691
  config2();
691
- const { runThreatModelWorkflow } = await import("./threatModel-waz866yk.js");
692
- const { config: appConfig } = await import("./index-py7gtxez.js");
692
+ const { runThreatModelWorkflow } = await import("./threatModel-pe3svbnc.js");
693
+ const { config: appConfig } = await import("./index-6bsbwhyw.js");
693
694
  const path = await import("path");
694
695
  const pensarConfig = await appConfig.get();
695
696
  const model = await resolveCliModel();
@@ -725,10 +726,10 @@ Threat model written to: ${resolvedPath}`);
725
726
  async function runOperator() {
726
727
  const { config: config2 } = await import("./main-3d7dfdvs.js").then((m)=>__toESM(m.default,1));
727
728
  config2();
728
- const { runOffensiveSecurityAgent } = await import("./offesecAgent-ahcz5hcx.js");
729
- const { sessions, normalizeMessages, getResumeMessages } = await import("./index-5a173a2k.js");
730
- const { ALL_TOOL_NAMES, SKILL_TOOL_NAMES } = await import("./index-a4ydz3dd.js");
731
- const { config: appConfig } = await import("./index-py7gtxez.js");
729
+ const { runOffensiveSecurityAgent } = await import("./offesecAgent-vvrhkr8r.js");
730
+ const { sessions, normalizeMessages, getResumeMessages } = await import("./index-eata65cj.js");
731
+ const { ALL_TOOL_NAMES, SKILL_TOOL_NAMES } = await import("./index-5035hx9j.js");
732
+ const { config: appConfig } = await import("./index-6bsbwhyw.js");
732
733
  const { createInterface } = await import("readline");
733
734
  const { readFileSync: readFileSync2, existsSync } = await import("fs");
734
735
  const path = await import("path");
@@ -739,6 +740,8 @@ async function runOperator() {
739
740
  process.exit(1);
740
741
  }
741
742
  const prompt = resolveFlagValue(promptRaw);
743
+ const systemRaw = getArg("-s") ?? getArg("--system");
744
+ const systemPrompt = systemRaw ? resolveFlagValue(systemRaw) : undefined;
742
745
  const target = getArg("--target");
743
746
  const pensarConfig = await appConfig.get();
744
747
  const model = await resolveCliModel();
@@ -783,6 +786,7 @@ ${sep}
783
786
  for (;; ) {
784
787
  await runOffensiveSecurityAgent({
785
788
  prompt: currentPrompt,
789
+ ...systemPrompt ? { system: systemPrompt } : {},
786
790
  model,
787
791
  target,
788
792
  activeTools: [...ALL_TOOL_NAMES, ...SKILL_TOOL_NAMES],
@@ -837,25 +841,25 @@ if (hasFlag("-p") || command === "--prompt") {
837
841
  await runTargetedPentest();
838
842
  } else if (command === "login" || command === "auth") {
839
843
  process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
840
- await import("./auth-wvh553ea.js");
844
+ await import("./auth-0afhfypr.js");
841
845
  } else if (command === "uninstall") {
842
846
  process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
843
- await import("./uninstall-0bwz7jdn.js");
847
+ await import("./uninstall-tv9t0pgc.js");
844
848
  } else if (command === "projects") {
845
849
  process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
846
- await import("./projects-dqp4m0ws.js");
850
+ await import("./projects-0qkjmzbw.js");
847
851
  } else if (command === "pentests") {
848
852
  process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
849
- await import("./pentests-re8dzxt9.js");
853
+ await import("./pentests-s5x17s2j.js");
850
854
  } else if (command === "issues") {
851
855
  process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
852
- await import("./issues-trbzy8n0.js");
856
+ await import("./issues-hgvycr7p.js");
853
857
  } else if (command === "fixes") {
854
858
  process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
855
- await import("./fixes-1z283wdz.js");
859
+ await import("./fixes-c8fa4er8.js");
856
860
  } else if (command === "logs") {
857
861
  process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
858
- await import("./logs-c88md0h3.js");
862
+ await import("./logs-0560s383.js");
859
863
  } else if (command === "threat-model") {
860
864
  await runThreatModel();
861
865
  } else if (command === "doctor") {
@@ -867,7 +871,7 @@ if (hasFlag("-p") || command === "--prompt") {
867
871
  console.error("All other commands work with Node \u2014 run 'pensar --help'.");
868
872
  process.exit(1);
869
873
  }
870
- await import("./index-j3hw6d4w.js");
874
+ await import("./index-vhcxarqs.js");
871
875
  } else {
872
876
  console.error(`Error: Unknown command '${command}'`);
873
877
  console.error();
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  getFix,
4
4
  listFixes
5
- } from "./cli-vvyq7ace.js";
6
- import"./cli-q2dty8g4.js";
7
- import"./cli-40ef01tb.js";
8
- import"./cli-r879p2yz.js";
5
+ } from "./cli-g90c7ajw.js";
6
+ import"./cli-xf5qehnx.js";
7
+ import"./cli-kc8akm2b.js";
8
+ import"./cli-etxprvkj.js";
9
9
  import"./cli-8rxa073f.js";
10
10
 
11
11
  // src/cli/fixes.ts
@@ -7,17 +7,17 @@ import {
7
7
  SKILL_TOOL_NAMES,
8
8
  StepTraceWriter,
9
9
  createAllTools
10
- } from "./cli-x3k26g1t.js";
10
+ } from "./cli-230tpm4a.js";
11
11
  import"./cli-tp1tqn3k.js";
12
- import"./cli-nqx9y9ds.js";
12
+ import"./cli-629hvmba.js";
13
13
  import"./cli-3y0dgy56.js";
14
- import"./cli-rc7hyq7e.js";
14
+ import"./cli-rzrxttza.js";
15
15
  import"./cli-0ghkg3w6.js";
16
- import"./cli-xtqm11qt.js";
17
- import"./cli-q2dty8g4.js";
16
+ import"./cli-91tyknkx.js";
17
+ import"./cli-xf5qehnx.js";
18
18
  import"./cli-gpnb45ck.js";
19
- import"./cli-40ef01tb.js";
20
- import"./cli-r879p2yz.js";
19
+ import"./cli-kc8akm2b.js";
20
+ import"./cli-etxprvkj.js";
21
21
  import"./cli-03z6pswp.js";
22
22
  import"./cli-8rxa073f.js";
23
23
  export {
@@ -2,8 +2,8 @@ import {
2
2
  get,
3
3
  init,
4
4
  update
5
- } from "./cli-40ef01tb.js";
6
- import"./cli-r879p2yz.js";
5
+ } from "./cli-kc8akm2b.js";
6
+ import"./cli-etxprvkj.js";
7
7
  import"./cli-8rxa073f.js";
8
8
 
9
9
  // src/core/config/index.ts
@@ -13,24 +13,24 @@ import {
13
13
  update,
14
14
  write,
15
15
  writeRaw
16
- } from "./cli-nqx9y9ds.js";
16
+ } from "./cli-629hvmba.js";
17
17
  import {
18
18
  ToolsetStateSchema,
19
19
  exports_toolset,
20
20
  init_toolset,
21
21
  toggleTool
22
- } from "./cli-rc7hyq7e.js";
22
+ } from "./cli-rzrxttza.js";
23
23
  import {
24
24
  init_zod,
25
25
  zod_default
26
26
  } from "./cli-0ghkg3w6.js";
27
- import"./cli-xtqm11qt.js";
28
- import"./cli-q2dty8g4.js";
27
+ import"./cli-91tyknkx.js";
28
+ import"./cli-xf5qehnx.js";
29
29
  import"./cli-gpnb45ck.js";
30
- import"./cli-40ef01tb.js";
30
+ import"./cli-kc8akm2b.js";
31
31
  import {
32
32
  getCurrentVersion
33
- } from "./cli-r879p2yz.js";
33
+ } from "./cli-etxprvkj.js";
34
34
  import"./cli-03z6pswp.js";
35
35
  import {
36
36
  __require,
@@ -9,15 +9,15 @@ import {
9
9
  signGatewayRequest,
10
10
  startDeviceFlow,
11
11
  validateGateway
12
- } from "./cli-xtqm11qt.js";
12
+ } from "./cli-91tyknkx.js";
13
13
  import {
14
14
  ensureValidToken,
15
15
  fetchWorkOSClientId,
16
16
  isTokenExpired,
17
17
  refreshAccessToken
18
- } from "./cli-q2dty8g4.js";
19
- import"./cli-40ef01tb.js";
20
- import"./cli-r879p2yz.js";
18
+ } from "./cli-xf5qehnx.js";
19
+ import"./cli-kc8akm2b.js";
20
+ import"./cli-etxprvkj.js";
21
21
  import"./cli-8rxa073f.js";
22
22
  export {
23
23
  validateGateway,