threadnote 0.7.8 → 0.7.9

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 CHANGED
@@ -342,6 +342,9 @@ This is it! Start working with your agents as usual. The agent will automaticall
342
342
  - `version`: prints the installed Threadnote version, latest npm version, and release notes for newer GitHub releases.
343
343
  - `update`: updates the published Threadnote package, then runs `repair` so shims and MCP config point at the new
344
344
  version. When an update is available, it prints release notes for the full version diff.
345
+ - `manage`: opens the local React web manager for browsing, recalling, reading, editing, archiving, forgetting,
346
+ publishing, consolidating, and diagnosing local/shared memories. It binds to `127.0.0.1` with a per-session token; use
347
+ `--no-open` to print the URL without opening a browser.
345
348
  - `repair`: fixes install/config/shim/manifest/server health issues and rewrites Codex/Claude/Cursor/Copilot MCP configs
346
349
  from the current checkout.
347
350
  - `start`: starts `openviking-server` on `127.0.0.1:1933`.
@@ -377,7 +380,7 @@ This is it! Start working with your agents as usual. The agent will automaticall
377
380
  succeeds.
378
381
  - `forget`: removes a `viking://` URI.
379
382
  - `export-pack` / `import-pack`: moves local context through `.ovpack` files.
380
- - `share init|status|sync|publish|unpublish|list|remove`: opts a curated subset of durable memories into a team git
383
+ - `share init|status|sync|publish|unpublish|list|rename|set-url|remove`: opts a curated subset of durable memories into a team git
381
384
  repo. Threadnote periodically fetches configured share repos and automatically syncs clean incoming changes before
382
385
  agent recall/read; use `share sync` for dirty worktrees, conflicts, explicit pushes, or immediate manual sync.
383
386
  Personal handoffs and preferences stay local. See `docs/share.md` for the full workflow and the publish-time scrubber
@@ -33691,33 +33691,111 @@ async function runCommand(executable, args, options = {}) {
33691
33691
  const child = (0, import_node_child_process.spawn)(executable, args, { cwd: options.cwd });
33692
33692
  const stdoutChunks = [];
33693
33693
  const stderrChunks = [];
33694
+ const maxOutputBytes = options.maxOutputBytes ?? defaultCommandMaxOutputBytes();
33695
+ let stdoutBytes = 0;
33696
+ let stderrBytes = 0;
33697
+ let finished = false;
33698
+ let failureResult;
33699
+ let killTimer;
33700
+ let sentTerminationSignal = false;
33701
+ const timeoutMs = options.timeoutMs ?? defaultCommandTimeoutMs();
33702
+ const finish = (result) => {
33703
+ if (finished) {
33704
+ return;
33705
+ }
33706
+ finished = true;
33707
+ if (killTimer) {
33708
+ clearTimeout(killTimer);
33709
+ }
33710
+ if (result.exitCode !== 0 && options.allowFailure !== true) {
33711
+ rejectPromise(new Error(`${formatShellCommand(executable, args)} failed: ${result.stderr || result.stdout}`));
33712
+ return;
33713
+ }
33714
+ resolvePromise(result);
33715
+ };
33716
+ const failAndKill = (message) => {
33717
+ if (failureResult) {
33718
+ return;
33719
+ }
33720
+ failureResult = { exitCode: 124, stderr: message, stdout: stdoutChunks.join("") };
33721
+ if (!sentTerminationSignal) {
33722
+ sentTerminationSignal = true;
33723
+ child.kill("SIGTERM");
33724
+ }
33725
+ setTimeout(() => {
33726
+ if (!finished) {
33727
+ child.kill("SIGKILL");
33728
+ }
33729
+ }, 1e3).unref?.();
33730
+ };
33731
+ if (timeoutMs > 0) {
33732
+ killTimer = setTimeout(() => {
33733
+ failAndKill(`${formatShellCommand(executable, args)} timed out after ${timeoutMs}ms`);
33734
+ }, timeoutMs);
33735
+ killTimer.unref?.();
33736
+ }
33694
33737
  child.stdout.on("data", (chunk) => {
33695
- stdoutChunks.push(String(chunk));
33738
+ if (failureResult) {
33739
+ return;
33740
+ }
33741
+ const text = String(chunk);
33742
+ stdoutBytes += Buffer.byteLength(text);
33743
+ if (stdoutBytes + stderrBytes > maxOutputBytes) {
33744
+ failAndKill(`${formatShellCommand(executable, args)} exceeded output limit of ${maxOutputBytes} bytes`);
33745
+ return;
33746
+ }
33747
+ stdoutChunks.push(text);
33696
33748
  });
33697
33749
  child.stderr.on("data", (chunk) => {
33698
- stderrChunks.push(String(chunk));
33750
+ if (failureResult) {
33751
+ return;
33752
+ }
33753
+ const text = String(chunk);
33754
+ stderrBytes += Buffer.byteLength(text);
33755
+ if (stdoutBytes + stderrBytes > maxOutputBytes) {
33756
+ failAndKill(`${formatShellCommand(executable, args)} exceeded output limit of ${maxOutputBytes} bytes`);
33757
+ return;
33758
+ }
33759
+ stderrChunks.push(text);
33699
33760
  });
33700
33761
  child.on("error", (err) => {
33762
+ if (finished) {
33763
+ return;
33764
+ }
33765
+ if (killTimer) {
33766
+ clearTimeout(killTimer);
33767
+ }
33701
33768
  if (options.allowFailure === true) {
33702
- resolvePromise({ exitCode: 127, stderr: errorMessage(err), stdout: "" });
33769
+ finish({ exitCode: 127, stderr: errorMessage(err), stdout: "" });
33703
33770
  } else {
33771
+ finished = true;
33704
33772
  rejectPromise(err);
33705
33773
  }
33706
33774
  });
33707
33775
  child.on("close", (code) => {
33708
- const result = {
33776
+ const result = failureResult ?? {
33709
33777
  exitCode: code ?? 1,
33710
33778
  stderr: stderrChunks.join(""),
33711
33779
  stdout: stdoutChunks.join("")
33712
33780
  };
33713
- if (result.exitCode !== 0 && options.allowFailure !== true) {
33714
- rejectPromise(new Error(`${formatShellCommand(executable, args)} failed: ${result.stderr || result.stdout}`));
33715
- return;
33716
- }
33717
- resolvePromise(result);
33781
+ finish(result);
33718
33782
  });
33719
33783
  });
33720
33784
  }
33785
+ function defaultCommandTimeoutMs() {
33786
+ return positiveIntegerFromEnv("THREADNOTE_COMMAND_TIMEOUT_MS") ?? 10 * 60 * 1e3;
33787
+ }
33788
+ function defaultCommandMaxOutputBytes() {
33789
+ return positiveIntegerFromEnv("THREADNOTE_COMMAND_MAX_OUTPUT_BYTES") ?? 5 * 1024 * 1024;
33790
+ }
33791
+ function positiveIntegerFromEnv(name) {
33792
+ const value = process.env[name];
33793
+ if (value === void 0) {
33794
+ return void 0;
33795
+ }
33796
+ const parsed = Number.parseInt(value, 10);
33797
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
33798
+ }
33721
33799
  async function gitValue(args, cwd = getInvocationCwd()) {
33722
33800
  const result = await runCommand("git", args, { allowFailure: true, cwd });
33723
33801
  if (result.exitCode !== 0) {
@@ -34671,6 +34749,58 @@ async function runShareSyncQuiet(config2, state, options) {
34671
34749
  }
34672
34750
  return void 0;
34673
34751
  }
34752
+ async function publishShareGitChange(worktree, relativePath, commitMessage, options = {}) {
34753
+ const dryRun = options.dryRun === true;
34754
+ const push = options.push !== false;
34755
+ const verb = options.verb ?? "add";
34756
+ const git = await requiredExecutable("git");
34757
+ const messages = [];
34758
+ const stageArgs = verb === "rm" ? ["-C", worktree, "rm", relativePath] : ["-C", worktree, "add", "--", relativePath];
34759
+ const stageResult = await runGitCommand(dryRun, git, stageArgs, `git ${verb} failed`);
34760
+ if (stageResult) {
34761
+ messages.push(`git ${verb}: ${stageResult.stdout.trim() || "ok"}`);
34762
+ }
34763
+ if (dryRun) {
34764
+ console.log(`Would run: ${formatShellCommand(git, ["-C", worktree, "commit", "-m", commitMessage])}`);
34765
+ } else {
34766
+ const commitResult = await runCommand(git, ["-C", worktree, "commit", "-m", commitMessage], { allowFailure: true });
34767
+ if (commitResult.exitCode !== 0) {
34768
+ const detail = commitResult.stdout.trim() || commitResult.stderr.trim();
34769
+ if (/nothing to commit|no changes added/i.test(detail)) {
34770
+ messages.push("git commit: nothing to commit (file already in tree)");
34771
+ } else {
34772
+ throw new Error(`git commit failed: ${detail || "unknown error"}`);
34773
+ }
34774
+ } else {
34775
+ messages.push(`git commit: ${commitResult.stdout.trim().split("\n").slice(0, 2).join(" ")}`);
34776
+ }
34777
+ }
34778
+ if (!push) {
34779
+ messages.push("git push skipped (push=false)");
34780
+ return messages;
34781
+ }
34782
+ const pushResult = await runGitCommand(
34783
+ dryRun,
34784
+ git,
34785
+ ["-C", worktree, "push", DEFAULT_GIT_REMOTE_NAME],
34786
+ "git push failed"
34787
+ );
34788
+ if (pushResult) {
34789
+ messages.push(`git push: ${pushResult.stdout.trim() || pushResult.stderr.trim() || "ok"}`);
34790
+ }
34791
+ return messages;
34792
+ }
34793
+ async function runGitCommand(dryRun, git, args, failureLabel) {
34794
+ if (dryRun) {
34795
+ console.log(`Would run: ${formatShellCommand(git, args)}`);
34796
+ return void 0;
34797
+ }
34798
+ const result = await runCommand(git, args, { allowFailure: true });
34799
+ if (result.exitCode !== 0) {
34800
+ throw new Error(`${failureLabel}: ${result.stderr.trim() || result.stdout.trim() || "unknown error"}`);
34801
+ }
34802
+ return result;
34803
+ }
34674
34804
  function normalizeTeamName(input) {
34675
34805
  const candidate = (input ?? "default").trim();
34676
34806
  if (!candidate) {
@@ -34965,47 +35095,6 @@ async function ingestSingleFile(ov, config2, uri, filePath, initialMode, options
34965
35095
  const content = await (0, import_promises3.readFile)(filePath, "utf8");
34966
35096
  await writeMemoryFile(config2, ov, uri, content, initialMode, false, options);
34967
35097
  }
34968
- async function removeWithRollback(config2, ov, sourceUri, rollbackUri, worktree, dryRun, label) {
34969
- try {
34970
- await removeMemoryUri(config2, ov, sourceUri, dryRun);
34971
- } catch (sourceErr) {
34972
- if (dryRun) {
34973
- throw sourceErr;
34974
- }
34975
- console.error(
34976
- `Source removal failed during ${label}; rolling back ${rollbackUri} so the system is back to the pre-${label} state.`
34977
- );
34978
- try {
34979
- await removeMemoryUri(config2, ov, rollbackUri, false);
34980
- } catch (rollbackErr) {
34981
- console.error(
34982
- `Rollback of ${rollbackUri} also failed. Manual cleanup needed via: threadnote forget ${rollbackUri}
34983
- Rollback error: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`
34984
- );
34985
- }
34986
- await bestEffortRemoveWorktreeFile(rollbackUri, worktree, label);
34987
- throw sourceErr;
34988
- }
34989
- }
34990
- async function bestEffortRemoveWorktreeFile(rollbackUri, worktree, label) {
34991
- if (label !== "publish") {
34992
- return;
34993
- }
34994
- const prefix = "viking://";
34995
- if (!rollbackUri.startsWith(prefix)) {
34996
- return;
34997
- }
34998
- const parts = rollbackUri.slice(prefix.length).split("/");
34999
- const sharedIndex = parts.indexOf("shared");
35000
- if (sharedIndex === -1 || sharedIndex + 2 >= parts.length) {
35001
- return;
35002
- }
35003
- const relative2 = parts.slice(sharedIndex + 2).join("/");
35004
- if (!relative2) {
35005
- return;
35006
- }
35007
- await (0, import_promises3.rm)((0, import_node_path3.join)(worktree, relative2), { force: true });
35008
- }
35009
35098
  async function removeMemoryUri(config2, ov, uri, dryRun, options = {}) {
35010
35099
  const args = withIdentity(config2, ["rm", uri]);
35011
35100
  if (dryRun) {
@@ -35167,7 +35256,7 @@ async function main() {
35167
35256
  "During feature work, update durable feature knowledge when valuable implementation details, decisions, interfaces, or gotchas change.",
35168
35257
  "When updating the same active issue, pass project/topic or replaceUri to remember_context so duplicate durable memories or handoffs do not accumulate.",
35169
35258
  "Use compact_context with dryRun=true for scoped memory hygiene when recall surfaces overlapping active memories.",
35170
- "To share a durable memory with teammates, call `share_publish` with its viking:// URI. share_publish is destructive: it scrubs for secrets, moves the memory into the shared subtree, removes the personal copy, and pushes a git commit. Do not publish handoffs, preferences, or anything carrying machine-local paths or in-flight task context.",
35259
+ "To share a durable memory with teammates, call `share_publish` with its viking:// URI. share_publish scrubs for secrets, writes and pushes the shared copy first, then removes the personal copy after the push succeeds. Do not publish handoffs, preferences, or anything carrying machine-local paths or in-flight task context.",
35171
35260
  "Do not store secrets, customer data, raw production logs, or credentials."
35172
35261
  ].join("\n")
35173
35262
  }
@@ -35351,7 +35440,7 @@ function registerTools(server, config2) {
35351
35440
  "share_publish",
35352
35441
  {
35353
35442
  annotations: { readOnlyHint: false, destructiveHint: true },
35354
- description: "Publish a personal memory into a team's shared memories git repo. Reads the memory, strips local-only provenance frontmatter (supersedes:, archived_from:), refuses publish if it matches secret patterns, copies it into the shared subtree, removes the personal original, and commits/pushes. Default team is used unless team is provided. Pass preview=true to return the would-be-published bytes without writing or committing.",
35443
+ description: "Publish a personal memory into a team's shared memories git repo. Reads the memory, strips local-only provenance frontmatter (supersedes:, archived_from:), refuses publish if it matches secret patterns, writes and pushes the shared copy first, then removes the personal original. Default team is used unless team is provided. Pass preview=true to return the would-be-published bytes without writing or committing.",
35355
35444
  inputSchema: {
35356
35445
  message: external_exports.string().optional().describe('Commit message override; defaults to "share: publish <path>"'),
35357
35446
  preview: external_exports.boolean().optional().describe(
@@ -35749,27 +35838,8 @@ function registerCompactTool(server, config2) {
35749
35838
  const ov = await requiredOpenVikingCli2();
35750
35839
  const appliedMessages = [];
35751
35840
  for (const action of plan.keepUpdates) {
35752
- const result = await runOpenVikingWriteWithRetry(
35753
- ov,
35754
- config2,
35755
- action.uri,
35756
- withIdentity2(config2, [
35757
- "write",
35758
- action.uri,
35759
- "--content",
35760
- action.content,
35761
- "--mode",
35762
- "replace",
35763
- "--wait",
35764
- "--timeout",
35765
- "120"
35766
- ])
35767
- );
35841
+ await writeMemoryFile(config2, ov, action.uri, action.content, "replace", false, { quiet: true });
35768
35842
  appliedMessages.push(`Updated kept memory: ${action.uri}`);
35769
- const output = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
35770
- if (output) {
35771
- appliedMessages.push(output);
35772
- }
35773
35843
  }
35774
35844
  for (const action of plan.archives) {
35775
35845
  const archiveResult = await archiveMemoryForCompact(config2, ov, action);
@@ -35942,22 +36012,7 @@ async function writeDurableMemory(config2, params) {
35942
36012
  const finalMetadata = isInPlaceUpdate ? { ...params.metadata, supersedes: void 0 } : candidateMetadata;
35943
36013
  const memory = isInPlaceUpdate ? formatMemoryDocument2("MEMORY", finalMetadata, params.bodyText) : candidateMemory;
35944
36014
  const writeMode = await memoryWriteMode(ov, config2, memoryUri, finalMetadata);
35945
- const result = await runOpenVikingWriteWithRetry(
35946
- ov,
35947
- config2,
35948
- memoryUri,
35949
- withIdentity2(config2, [
35950
- "write",
35951
- memoryUri,
35952
- "--content",
35953
- memory,
35954
- "--mode",
35955
- writeMode,
35956
- "--wait",
35957
- "--timeout",
35958
- "120"
35959
- ])
35960
- );
36015
+ await writeMemoryFile(config2, ov, memoryUri, memory, writeMode, false, { quiet: true });
35961
36016
  const messages = [`Stored memory: ${memoryUri}`];
35962
36017
  if (params.replaceUri && !isInPlaceUpdate) {
35963
36018
  const removedReplacedMemory = await removeVikingResourceWithRetry(ov, config2, params.replaceUri);
@@ -35967,8 +36022,7 @@ async function writeDurableMemory(config2, params) {
35967
36022
  } else if (isInPlaceUpdate) {
35968
36023
  messages.push(`Updated existing memory in place: ${memoryUri}`);
35969
36024
  }
35970
- const text = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
35971
- return { content: [{ type: "text", text: [...messages, text].filter(Boolean).join("\n") }] };
36025
+ return { content: [{ type: "text", text: messages.join("\n") }] };
35972
36026
  } catch (err) {
35973
36027
  return { content: [{ type: "text", text: errorMessage(err) }], isError: true };
35974
36028
  }
@@ -36003,27 +36057,10 @@ async function writeSharedMemoryReplacement(config2, ov, params, targetUri) {
36003
36057
  messages.push(`Redacted ${redaction.count}\xD7 ${redaction.name} before shared update.`);
36004
36058
  }
36005
36059
  messages.push(
36006
- ...await gitPublishWorkflow(resolved.config.worktree, relativePath, `share: update ${relativePath}`, true)
36060
+ ...await publishShareGitChange(resolved.config.worktree, relativePath, `share: update ${relativePath}`)
36007
36061
  );
36008
36062
  return { content: [{ type: "text", text: messages.join("\n") }] };
36009
36063
  }
36010
- async function runOpenVikingWriteWithRetry(ov, config2, memoryUri, args) {
36011
- for (let attempt = 0; attempt < 4; attempt += 1) {
36012
- const result = await runCommand(ov, args, { allowFailure: true });
36013
- if (result.exitCode === 0) {
36014
- return result;
36015
- }
36016
- if (await vikingResourceExists2(ov, config2, memoryUri)) {
36017
- await runCommand(ov, withIdentity2(config2, ["wait", "--timeout", "120"]), { allowFailure: true });
36018
- return { exitCode: 0, stdout: "OpenViking accepted the memory and indexing wait completed.", stderr: "" };
36019
- }
36020
- if (!isResourceBusy(result.stderr, result.stdout) || attempt === 3) {
36021
- throw new Error(`${ov} ${args.join(" ")} failed: ${result.stderr || result.stdout}`);
36022
- }
36023
- await sleep(1e3 * (attempt + 1));
36024
- }
36025
- throw new Error(`${ov} ${args.join(" ")} failed.`);
36026
- }
36027
36064
  async function vikingResourceExists2(ov, config2, uri) {
36028
36065
  const stat2 = await runCommand(ov, withIdentity2(config2, ["stat", uri]), { allowFailure: true });
36029
36066
  return stat2.exitCode === 0;
@@ -36251,17 +36288,27 @@ async function runSharePublishTool(config2, sourceUri, options) {
36251
36288
  }
36252
36289
  await ensureSharedDirectoryChain(config2, ov, targetUri, false);
36253
36290
  await writeMemoryFile(config2, ov, targetUri, content, "create", false);
36291
+ const messages = [`Published ${sourceUri} -> ${targetUri}`];
36292
+ for (const redaction of scrub.redactions) {
36293
+ messages.push(`Redacted ${redaction.count}\xD7 ${redaction.name} before publish.`);
36294
+ }
36295
+ const relativePath = vikingUriToWorktreeRelative(config2, targetUri, resolved.name);
36296
+ const commitMessage = options.message ?? `share: publish ${relativePath}`;
36297
+ const gitMessages = await publishShareGitChange(resolved.config.worktree, relativePath, commitMessage, {
36298
+ push: options.push
36299
+ });
36254
36300
  try {
36255
- await removeWithRollback(config2, ov, sourceUri, targetUri, resolved.config.worktree, false, "publish");
36301
+ await removeMemoryUri(config2, ov, sourceUri, false, { quiet: true });
36256
36302
  } catch (sourceErr) {
36257
36303
  return {
36258
36304
  content: [
36259
36305
  {
36260
36306
  type: "text",
36261
36307
  text: [
36262
- `Refused to leave a half-published state: could not remove ${sourceUri}.`,
36263
- `Rolled back ${targetUri} so the system is back to the pre-publish state.`,
36264
- `Retry the publish once OpenViking's queue settles.`,
36308
+ ...messages,
36309
+ ...gitMessages,
36310
+ `Could not remove the personal source after publish: ${sourceUri}.`,
36311
+ `Retry cleanup later with: threadnote forget ${sourceUri}`,
36265
36312
  sourceErr instanceof Error ? sourceErr.message : String(sourceErr)
36266
36313
  ].join("\n")
36267
36314
  }
@@ -36269,18 +36316,6 @@ async function runSharePublishTool(config2, sourceUri, options) {
36269
36316
  isError: true
36270
36317
  };
36271
36318
  }
36272
- const messages = [`Published ${sourceUri} -> ${targetUri}`];
36273
- for (const redaction of scrub.redactions) {
36274
- messages.push(`Redacted ${redaction.count}\xD7 ${redaction.name} before publish.`);
36275
- }
36276
- const relativePath = vikingUriToWorktreeRelative(config2, targetUri, resolved.name);
36277
- const commitMessage = options.message ?? `share: publish ${relativePath}`;
36278
- const gitMessages = await gitPublishWorkflow(
36279
- resolved.config.worktree,
36280
- relativePath,
36281
- commitMessage,
36282
- options.push !== false
36283
- );
36284
36319
  return {
36285
36320
  content: [{ type: "text", text: [...messages, ...gitMessages].join("\n") }],
36286
36321
  isError: false
@@ -36289,38 +36324,6 @@ async function runSharePublishTool(config2, sourceUri, options) {
36289
36324
  return { content: [{ type: "text", text: errorMessage(err) }], isError: true };
36290
36325
  }
36291
36326
  }
36292
- async function gitPublishWorkflow(worktree, relativePath, commitMessage, push) {
36293
- const messages = [];
36294
- const add = await runCommand("git", ["-C", worktree, "add", relativePath], { allowFailure: true });
36295
- if (add.exitCode !== 0) {
36296
- messages.push(`git add failed: ${add.stderr.trim() || add.stdout.trim()}`);
36297
- return messages;
36298
- }
36299
- const commit = await runCommand("git", ["-C", worktree, "commit", "-m", commitMessage], { allowFailure: true });
36300
- if (commit.exitCode !== 0) {
36301
- const detail = commit.stdout.trim() || commit.stderr.trim();
36302
- if (/nothing to commit|no changes added/i.test(detail)) {
36303
- messages.push("git commit: nothing to commit (file already in tree)");
36304
- } else {
36305
- messages.push(`git commit failed: ${detail}`);
36306
- return messages;
36307
- }
36308
- } else {
36309
- messages.push(`git commit: ${commit.stdout.trim().split("\n").slice(0, 2).join(" ")}`);
36310
- }
36311
- if (!push) {
36312
- messages.push("git push skipped (push=false)");
36313
- return messages;
36314
- }
36315
- const pushResult = await runCommand("git", ["-C", worktree, "push", DEFAULT_GIT_REMOTE_NAME], { allowFailure: true });
36316
- const pushDetail = pushResult.stdout.trim() || pushResult.stderr.trim();
36317
- if (pushResult.exitCode !== 0) {
36318
- messages.push(`git push failed: ${pushDetail}`);
36319
- } else {
36320
- messages.push(`git push: ${pushDetail || "ok"}`);
36321
- }
36322
- return messages;
36323
- }
36324
36327
  function withIdentity2(config2, args) {
36325
36328
  return [...args, "--account", config2.account, "--user", config2.user, "--agent-id", config2.agentId];
36326
36329
  }