@agentskit/harness 0.12.0 → 0.13.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.
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createHash, randomUUID, createPrivateKey, createPublicKey, sign, verify } from 'crypto';
3
- import { readFileSync, mkdirSync, existsSync, unlinkSync, openSync, writeFileSync, closeSync, readdirSync, statSync, writeSync, appendFileSync, mkdtempSync, renameSync, rmSync } from 'fs';
3
+ import { readFileSync, mkdirSync, existsSync, unlinkSync, openSync, writeFileSync, closeSync, readdirSync, statSync, writeSync, appendFileSync, mkdtempSync, renameSync, rmSync, accessSync, constants } from 'fs';
4
4
  import { resolve, dirname, relative, basename, join, extname, isAbsolute, delimiter, sep } from 'path';
5
5
  import { Command } from 'commander';
6
6
  import { execFile, spawn, execFileSync } from 'child_process';
@@ -970,9 +970,9 @@ var createDocBridgeContextProvider = ({ root, indexPath = ".doc-bridge/index.jso
970
970
  const started = Date.now();
971
971
  const ageBudget = maxAgeHours ?? 0;
972
972
  const inspection = ageBudget > 0 ? inspectDocBridgeIndex(root, indexPath, now4()) : null;
973
- if (inspection?.error) throw new Error(`Doc Bridge index is unreadable: ${inspection.error}`);
973
+ if (inspection?.error) fail(`Doc Bridge index is unreadable: ${inspection.error}`, "INVALID_STATE");
974
974
  if (inspection?.ageHours !== null && inspection?.ageHours !== void 0 && inspection.ageHours > ageBudget) {
975
- throw new Error(`Doc Bridge index is ${inspection.ageHours.toFixed(1)}h old; refresh it before resolving context.`);
975
+ fail(`Doc Bridge index is ${inspection.ageHours.toFixed(1)}h old; refresh it before resolving context.`, "STALE");
976
976
  }
977
977
  const document = index(root, indexPath);
978
978
  const contentHash = sourceHash(document);
@@ -993,7 +993,9 @@ var createDocBridgeContextProvider = ({ root, indexPath = ".doc-bridge/index.jso
993
993
  });
994
994
  var executable = (path) => {
995
995
  try {
996
- return statSync(path).isFile();
996
+ if (!statSync(path).isFile()) return false;
997
+ accessSync(path, constants.X_OK);
998
+ return true;
997
999
  } catch {
998
1000
  return false;
999
1001
  }
@@ -1524,7 +1526,7 @@ var validateIteration = (iteration, index2) => {
1524
1526
  if (typeof result !== "object" || result === null || Array.isArray(result)) return fail(`iterations[${index2}].steps[${stepIndex}] must be an object.`, "INVALID_INPUT");
1525
1527
  if (result.step !== IMPROVEMENT_CYCLE_STEPS[stepIndex]) return fail(`iterations[${index2}].steps[${stepIndex}] must be ${IMPROVEMENT_CYCLE_STEPS[stepIndex]}.`, "INVALID_INPUT");
1526
1528
  if (!["passed", "failed", "blocked", "pending"].includes(result.status)) return fail(`iterations[${index2}].steps[${stepIndex}].status is invalid.`, "INVALID_INPUT");
1527
- if (result.status !== "passed" && !nonEmpty(result.reason, `iterations[${index2}].steps[${stepIndex}].reason`)) return fail(`iterations[${index2}].steps[${stepIndex}].reason is required when the step does not pass.`, "INVALID_INPUT");
1529
+ if (result.status !== "passed" && (typeof result.reason !== "string" || !result.reason.trim())) return fail(`iterations[${index2}].steps[${stepIndex}].reason is required when the step does not pass.`, "INVALID_INPUT");
1528
1530
  });
1529
1531
  if (iteration.adjustment !== void 0) nonEmpty(iteration.adjustment, `iterations[${index2}].adjustment`);
1530
1532
  return { ...iteration, metrics: validateMetrics(iteration.metrics, index2) };
@@ -1538,8 +1540,10 @@ var assessImprovementCycle = (input) => {
1538
1540
  const iterations = input.iterations.map(validateIteration);
1539
1541
  iterations.forEach((iteration, index2) => {
1540
1542
  if (iteration.iteration !== index2 + 1) return fail("iterations must be sequential and start at 1.", "INVALID_INPUT");
1541
- if (index2 > 0 && iterations[index2 - 1]?.steps.every((step) => step.status === "passed")) return fail("a completed cycle cannot have later iterations.", "INVALID_INPUT");
1542
- if (index2 < iterations.length - 1 && !iteration.adjustment) return fail(`iterations[${index2}].adjustment is required before repeating.`, "INVALID_INPUT");
1543
+ const isLast = index2 === iterations.length - 1;
1544
+ const iterationComplete = iteration.steps.every((step) => step.status === "passed");
1545
+ if (iterationComplete && !isLast) return fail("a completed cycle cannot have later iterations.", "INVALID_INPUT");
1546
+ if (!isLast && !iteration.adjustment) return fail(`iterations[${index2}].adjustment is required before repeating.`, "INVALID_INPUT");
1543
1547
  });
1544
1548
  const matrix = iterations.map((iteration) => {
1545
1549
  const statuses = Object.fromEntries(iteration.steps.map((step) => [step.step, step.status]));
@@ -2241,7 +2245,17 @@ var MODEL_ROLES = ["orchestrator", "reviewer", "builder", "watcher"];
2241
2245
 
2242
2246
  // src/kernel/pii.ts
2243
2247
  var PATTERNS = [
2244
- { kind: "api-key", regex: /\b(?:sk-[A-Za-z0-9]{16,}|gh[opsu]_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g },
2248
+ // PEM key blocks first: large, unambiguous, and must claim their content before any narrower pattern below
2249
+ // could otherwise match a substring inside the base64 body (unlikely, but claimed-range order matters).
2250
+ { kind: "private-key", regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----/g },
2251
+ // `sk-` body allows `-`/`_` (not just alnum) so a project/scoped key like `sk-proj-...`/`sk-live-...` matches
2252
+ // as one token instead of the hyphen splitting it into a too-short fragment. `github_pat_` (fine-grained PAT)
2253
+ // and `AIza…` (Google API key) are current real-world formats missing from the original list entirely.
2254
+ { kind: "api-key", regex: /\b(?:sk-[A-Za-z0-9_-]{16,}|sk_(?:live|test)_[A-Za-z0-9]{16,}|pk_(?:live|test)_[A-Za-z0-9]{16,}|gh[opsu]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|AIza[A-Za-z0-9_-]{30,}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g },
2255
+ // The AWS *secret* half (as opposed to the `AKIA…` access-key id above) has no recognizable prefix — a bare
2256
+ // 40-char base64-shaped run is too generic to scan for on its own (matches hashes, tokens, arbitrary base64).
2257
+ // Anchoring on the conventional key name it's almost always assigned to/from keeps this pattern high-signal.
2258
+ { kind: "api-key", regex: /\b(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY|SecretAccessKey)\s*[:=]\s*["']?([A-Za-z0-9/+=]{40})["']?/g },
2245
2259
  { kind: "email", regex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g },
2246
2260
  { kind: "credit-card", regex: /\b(?:\d[ -]?){13,16}\b/g },
2247
2261
  { kind: "phone", regex: /\b\+?\d{1,3}?[\s().-]?\(?\d{2,4}\)?[\s.-]?\d{3,4}[\s.-]?\d{4}\b/g }
@@ -2389,7 +2403,9 @@ var exportEvidenceBundle = async ({ configPath, runId, outputPath, privateKeyPat
2389
2403
  `, "utf8");
2390
2404
  return bundle;
2391
2405
  };
2392
- var verifyEvidenceBundle = (path, { trustedKeys = [] } = {}) => {
2406
+ var EVIDENCE_MAX_FILE_BYTES = 25 * 1048576;
2407
+ var EVIDENCE_MAX_TOTAL_BYTES = 200 * 1048576;
2408
+ var verifyEvidenceBundle = (path, { trustedKeys = [], maxFileBytes = EVIDENCE_MAX_FILE_BYTES, maxTotalBytes = EVIDENCE_MAX_TOTAL_BYTES } = {}) => {
2393
2409
  const bundle = parseBundle(path);
2394
2410
  if (bundle.type !== "agentskit-harness-evidence-bundle" || bundle.schemaVersion !== EVIDENCE_BUNDLE_SCHEMA_VERSION || !bundle.runId || !validKeyId(bundle.signerKeyId) || !validDigest(bundle.payloadHash) || bundle.signature?.algorithm !== "ed25519" || bundle.signature.keyId !== bundle.signerKeyId || typeof bundle.signature.publicKeyPem !== "string" || typeof bundle.signature.signatureBase64 !== "string" || !Array.isArray(bundle.files)) fail("Evidence bundle metadata is invalid.", "HARNESS_ERROR");
2395
2411
  if (trustedKeys.length) {
@@ -2399,10 +2415,14 @@ var verifyEvidenceBundle = (path, { trustedKeys = [] } = {}) => {
2399
2415
  if (trusted.publicKeyPem !== bundle.signature.publicKeyPem) fail(`Evidence bundle key does not match trust store: ${bundle.signerKeyId}`, "HARNESS_ERROR");
2400
2416
  }
2401
2417
  const paths = /* @__PURE__ */ new Set();
2418
+ let totalBytes = 0;
2402
2419
  for (const file of bundle.files) {
2403
2420
  if (!file || typeof file.path !== "string" || paths.has(file.path) || !validDigest(file.sha256) || typeof file.contentBase64 !== "string") fail("Evidence bundle file metadata is invalid.", "HARNESS_ERROR");
2404
2421
  paths.add(file.path);
2422
+ if (file.contentBase64.length > Math.ceil(maxFileBytes / 3) * 4) fail(`Evidence bundle file exceeds the maximum allowed size: ${file.path}`, "HARNESS_ERROR");
2405
2423
  const content = Buffer.from(file.contentBase64, "base64");
2424
+ totalBytes += content.length;
2425
+ if (totalBytes > maxTotalBytes) fail("Evidence bundle exceeds the maximum total allowed size.", "HARNESS_ERROR");
2406
2426
  if (sha256(content) !== file.sha256) fail(`Evidence bundle file hash mismatch: ${file.path}`, "HARNESS_ERROR");
2407
2427
  }
2408
2428
  if (!paths.has(`runs/${bundle.runId}/run.json`) || !paths.has(`runs/${bundle.runId}/events.ndjson`)) fail("Evidence bundle is missing the run projection or event log.", "HARNESS_ERROR");
@@ -4100,6 +4120,14 @@ var githubCommentExists = async (runner, input, options2 = {}) => {
4100
4120
  const list2 = await ghJson(runner, ["api", "--paginate", `repos/${input.repo}/issues/${input.number}/comments`, "--jq", "[.[].body]"], options2);
4101
4121
  return Array.isArray(list2) && list2.some((body2) => typeof body2 === "string" && body2.includes(input.marker));
4102
4122
  };
4123
+ var writeJsonAtomic = (path, value) => {
4124
+ const dir = dirname(path);
4125
+ mkdirSync(dir, { recursive: true });
4126
+ const tmp = join(dir, `.${basename(path)}.${process.pid}.${Date.now()}.tmp`);
4127
+ writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
4128
+ `, "utf8");
4129
+ renameSync(tmp, path);
4130
+ };
4103
4131
  var clip = (text6, max) => text6.length <= max ? text6 : `${text6.slice(0, Math.max(0, max - 1))}\u2026`;
4104
4132
  var createFileMemoryKvStore = (dir) => {
4105
4133
  mkdirSync(dir, { recursive: true });
@@ -4309,9 +4337,7 @@ var readStoredContract = (stateDir, identifier) => {
4309
4337
  };
4310
4338
  var writeStoredContract = (stateDir, stored) => {
4311
4339
  const path = contractPath(stateDir, stored.issue);
4312
- mkdirSync(dirname(path), { recursive: true });
4313
- writeFileSync(path, `${JSON.stringify(stored, null, 2)}
4314
- `, "utf8");
4340
+ writeJsonAtomic(path, stored);
4315
4341
  return path;
4316
4342
  };
4317
4343
  var contractIsFresh = (stored, issue, reuseHours, now4, memoryDigest) => stored.issueUpdatedAt === issue.updatedAt && (reuseHours === 0 || now4.getTime() - Date.parse(stored.generatedAt) <= reuseHours * 36e5) && (memoryDigest === void 0 || (stored.memoryDigest ?? hashJson([])) === memoryDigest);
@@ -4745,26 +4771,56 @@ var readDispatchRecord = (stateDir, identifier) => {
4745
4771
  return null;
4746
4772
  }
4747
4773
  };
4748
- var writeJson2 = (path, value) => {
4749
- mkdirSync(dirname(path), { recursive: true });
4750
- writeFileSync(path, `${JSON.stringify(value, null, 2)}
4751
- `, "utf8");
4752
- };
4753
4774
  var writeDispatchRecord = (stateDir, record3) => {
4754
4775
  const path = dispatchRecordPath(stateDir, record3.issue);
4755
- writeJson2(path, record3);
4776
+ writeJsonAtomic(path, record3);
4756
4777
  return path;
4757
4778
  };
4758
4779
  var EVENTS_ROTATE_AT_BYTES = 10 * 1024 * 1024;
4780
+ var EVENTS_LOCK_STALE_MS = 5e3;
4781
+ var EVENTS_LOCK_MAX_ATTEMPTS = 100;
4782
+ var EVENTS_LOCK_RETRY_MS = 10;
4783
+ var acquireEventsLock = (lockFilePath) => {
4784
+ for (let attempt = 0; attempt < EVENTS_LOCK_MAX_ATTEMPTS; attempt += 1) {
4785
+ try {
4786
+ return openSync(lockFilePath, "wx");
4787
+ } catch (error) {
4788
+ if (error.code !== "EEXIST") throw error;
4789
+ try {
4790
+ if (Date.now() - statSync(lockFilePath).mtimeMs > EVENTS_LOCK_STALE_MS) unlinkSync(lockFilePath);
4791
+ } catch {
4792
+ }
4793
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, EVENTS_LOCK_RETRY_MS);
4794
+ }
4795
+ }
4796
+ return null;
4797
+ };
4759
4798
  var appendLoopEvent = (stateDir, event2, bus, now4 = () => /* @__PURE__ */ new Date()) => {
4760
4799
  const path = join(stateDir, "events.ndjson");
4761
4800
  mkdirSync(dirname(path), { recursive: true });
4801
+ const lockFilePath = `${path}.lock`;
4802
+ const lockFd = acquireEventsLock(lockFilePath);
4762
4803
  try {
4763
- if (statSync(path).size > EVENTS_ROTATE_AT_BYTES) renameSync(path, join(stateDir, `events-archive-${now4().getTime()}.ndjson`));
4764
- } catch {
4765
- }
4766
- appendFileSync(path, `${JSON.stringify(event2)}
4804
+ if (lockFd !== null) {
4805
+ try {
4806
+ if (statSync(path).size > EVENTS_ROTATE_AT_BYTES) renameSync(path, join(stateDir, `events-archive-${now4().getTime()}.ndjson`));
4807
+ } catch {
4808
+ }
4809
+ }
4810
+ appendFileSync(path, `${JSON.stringify(event2)}
4767
4811
  `, "utf8");
4812
+ } finally {
4813
+ if (lockFd !== null) {
4814
+ try {
4815
+ closeSync(lockFd);
4816
+ } catch {
4817
+ }
4818
+ try {
4819
+ unlinkSync(lockFilePath);
4820
+ } catch {
4821
+ }
4822
+ }
4823
+ }
4768
4824
  if (bus && typeof event2["type"] === "string") bus.emit(event2);
4769
4825
  };
4770
4826
  var gatherLoopState = async (input) => {
@@ -5060,7 +5116,7 @@ The loop will not retry this issue until you remove the \`${config.resilience.pa
5060
5116
  if (!launched.accepted) notes.push(`${detail.identifier}: terminal ${launched.terminal} did not confirm the brief; deliver will nudge it if it stays idle`);
5061
5117
  ledger.recordDispatch({ lease: claim.lease, idempotencyKey: plan.idempotencyKey, commandDigest: plan.commandDigest });
5062
5118
  const record3 = { issue: detail.identifier, worktreeId: created.id, worktree, branch: actualBranch, terminal: launched.terminal, provider: builder.provider, model: builder.model, contractDigest: stored.digest, leaseKey: claim.lease.key, leaseId: claim.lease.leaseId, dispatchedAt: now4().toISOString(), url: detail.url, briefDigest, skills: skillRefs(pinnedSkills), setup: setupResult, effort: builder.effort, initialRemainingPercent: builder.remainingPercent, worktreePath: created.path };
5063
- writeJson2(dispatchRecordPath(loaded.stateDir, detail.identifier), record3);
5119
+ writeJsonAtomic(dispatchRecordPath(loaded.stateDir, detail.identifier), record3);
5064
5120
  appendLoopEvent(loaded.stateDir, { at: record3.dispatchedAt, type: "worker.dispatched", ...record3, command: builder.tui, briefAccepted: launched.accepted, tuiIdle: launched.idle }, bus);
5065
5121
  await bus.runHook("afterDispatch", { issue: detail.identifier, provider: record3.provider, model: record3.model, branch: record3.branch, worktreeId: record3.worktreeId });
5066
5122
  clearIssueFailures(loaded.stateDir, detail.identifier);
@@ -5177,11 +5233,6 @@ var discoverIntake = async (runner, input, options2 = {}) => {
5177
5233
  // src/loop/deliver.ts
5178
5234
  var message3 = (error) => error instanceof HarnessError ? `${error.code}: ${error.message}` : error instanceof Error ? error.message : String(error);
5179
5235
  var isMissingOrcaWorktree = (error) => message3(error).includes("selector_not_found");
5180
- var writeJson3 = (path, value) => {
5181
- mkdirSync(dirname(path), { recursive: true });
5182
- writeFileSync(path, `${JSON.stringify(value, null, 2)}
5183
- `, "utf8");
5184
- };
5185
5236
  var deliveryStatePath = (stateDir, identifier) => join(stateDir, "issues", identifier, "delivery.json");
5186
5237
  var readDeliveryState = (stateDir, identifier) => {
5187
5238
  const path = deliveryStatePath(stateDir, identifier);
@@ -5208,7 +5259,7 @@ var minutesBetween = (later, earlier) => earlier === null ? Number.POSITIVE_INFI
5208
5259
  var orcaOptions = (config) => ({ bin: config.orca.bin, timeoutMs: config.orca.timeoutMs });
5209
5260
  var linearOptions = (config) => ({ bin: config.orca.bin, workspaceId: config.linear.workspaceId, orca: { timeoutMs: config.orca.timeoutMs } });
5210
5261
  var saveState = (ctx, state) => {
5211
- if (!ctx.dryRun) writeJson3(deliveryStatePath(ctx.loaded.stateDir, state.issue), state);
5262
+ if (!ctx.dryRun) writeJsonAtomic(deliveryStatePath(ctx.loaded.stateDir, state.issue), state);
5212
5263
  };
5213
5264
  var event = (ctx, payload) => {
5214
5265
  if (!ctx.dryRun) appendLoopEvent(ctx.loaded.stateDir, { at: ctx.now().toISOString(), ...payload }, ctx.bus);
@@ -5880,7 +5931,7 @@ var runDeliver = async (input) => {
5880
5931
  const candidates = (await githubOpenPullRequests(input.runner, { repo: config.project.repo, limit: 100 })).filter((item) => item.headRef === record3.branch || item.headRef.endsWith(`/${record3.worktree}`) || item.headRef === record3.worktree);
5881
5932
  if (candidates.length) {
5882
5933
  open = candidates;
5883
- if (!dryRun) writeJson3(dispatchRecordPath(loaded.stateDir, record3.issue), { ...record3, branch: candidates[0].headRef });
5934
+ if (!dryRun) writeJsonAtomic(dispatchRecordPath(loaded.stateDir, record3.issue), { ...record3, branch: candidates[0].headRef });
5884
5935
  notes.push(`${record3.issue}: PR found on branch ${candidates[0].headRef}; dispatch record updated`);
5885
5936
  }
5886
5937
  }
@@ -6489,7 +6540,7 @@ var parseSince = (value, now4) => {
6489
6540
  return new Date(now4.getTime() - amount * unit);
6490
6541
  }
6491
6542
  const parsed = Date.parse(value);
6492
- if (Number.isNaN(parsed)) throw new Error(`Unrecognised --since value: ${value} (use 7d, 12h, 30m or an ISO date)`);
6543
+ if (Number.isNaN(parsed)) fail(`Unrecognised --since value: ${value} (use 7d, 12h, 30m or an ISO date)`, "INVALID_INPUT");
6493
6544
  return new Date(parsed);
6494
6545
  };
6495
6546
  var median2 = (values) => {
@@ -6738,6 +6789,7 @@ var phaseOf = (dispatch, delivery2) => {
6738
6789
  return "in-flight";
6739
6790
  };
6740
6791
  var summarize2 = (phase, delivery2, dispatch) => {
6792
+ if (phase === "idle") return "Not yet dispatched";
6741
6793
  if (phase === "merged") return `Merged PR #${delivery2.prNumber ?? "?"}`;
6742
6794
  if (phase === "held" || phase === "held-incomplete-review") {
6743
6795
  if (delivery2.heldFor) return `Held for a human (self-edit or protected path at ${delivery2.heldFor.slice(0, 7)})`;
@@ -6824,7 +6876,7 @@ var buildDebriefReport = (input) => {
6824
6876
  });
6825
6877
  continue;
6826
6878
  }
6827
- continue;
6879
+ if (!input.issue) continue;
6828
6880
  }
6829
6881
  rows.push(rowFor({ issue, dispatch, delivery: delivery2, intent, repo: config.project.repo, now: now4 }));
6830
6882
  }
@@ -6992,7 +7044,7 @@ var runObservability = async (input) => {
6992
7044
  const events2 = readLoopEvents(loaded.stateDir).filter((event2) => Date.parse(event2.at) >= since.getTime() && Date.parse(event2.at) <= at.getTime());
6993
7045
  const ledger = createDispatchLedger(loaded.stateDir);
6994
7046
  const active = ledger.active();
6995
- const missingDeliveryIssues = active.filter((lease) => !existsSync(deliveryStatePath(loaded.stateDir, lease.issue))).map((lease) => lease.issue);
7047
+ const missingDeliveryIssues = active.filter((lease) => !existsSync(deliveryStatePath(loaded.stateDir, lease.issue)) && !existsSync(dispatchRecordPath(loaded.stateDir, lease.issue))).map((lease) => lease.issue);
6996
7048
  const records = listDispatched(loaded.stateDir);
6997
7049
  const completed = records.map((record3) => ({ record: record3, state: readDeliveryState(loaded.stateDir, record3.issue) })).filter(({ state }) => state.finishedAt && Date.parse(state.finishedAt) >= since.getTime());
6998
7050
  const leadTimes = completed.map(({ record: record3, state }) => state.finishedAt ? (Date.parse(state.finishedAt) - Date.parse(record3.dispatchedAt)) / 6e4 : null).filter((value) => value !== null && Number.isFinite(value)).sort((a, b) => a - b);
@@ -7515,7 +7567,7 @@ benchmark.command("baseline <taskId>").description("Record one controlled baseli
7515
7567
  program.command("clean").description("Remove only configured task-owned temporary artifacts.").action(() => print(cleanTaskArtifacts(options().config)));
7516
7568
  process.on("SIGINT", () => {
7517
7569
  process.stderr.write("Cancelled.\n");
7518
- process.exitCode = 130;
7570
+ process.exit(130);
7519
7571
  });
7520
7572
  try {
7521
7573
  await program.parseAsync(process.argv);