@swmansion/argent 0.22.1-next.5 → 0.22.1-next.7

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-cmds.mjs CHANGED
@@ -2675,11 +2675,13 @@ async function applyClientFileDirectives(result) {
2675
2675
  var ToolInvocationError = class extends Error {
2676
2676
  errorCode;
2677
2677
  errorKind;
2678
+ issues;
2678
2679
  constructor(message, signal) {
2679
2680
  super(message);
2680
2681
  this.name = "ToolInvocationError";
2681
2682
  this.errorCode = signal?.errorCode;
2682
2683
  this.errorKind = signal?.errorKind;
2684
+ this.issues = signal?.issues;
2683
2685
  }
2684
2686
  };
2685
2687
  function authHeaders2(token) {
@@ -2727,6 +2729,10 @@ async function consumeToolStream(body, onProgress) {
2727
2729
  const { result: data } = await applyClientFileDirectives(final.data);
2728
2730
  return { data, note: final.note };
2729
2731
  }
2732
+ function errorBodyMessage(body) {
2733
+ if (Array.isArray(body.issues) && typeof body.message === "string") return body.message;
2734
+ return body.error ?? body.message;
2735
+ }
2730
2736
  function createToolsClient(options = {}) {
2731
2737
  let cached2 = null;
2732
2738
  async function baseUrl() {
@@ -2781,13 +2787,11 @@ function createToolsClient(options = {}) {
2781
2787
  }
2782
2788
  const json = await res.json().catch(() => ({}));
2783
2789
  if (!res.ok) {
2784
- throw new ToolInvocationError(
2785
- json.error ?? json.message ?? `${res.status} ${res.statusText}`,
2786
- {
2787
- errorCode: json.error_code,
2788
- errorKind: json.error_kind
2789
- }
2790
- );
2790
+ throw new ToolInvocationError(errorBodyMessage(json) ?? `${res.status} ${res.statusText}`, {
2791
+ errorCode: json.error_code,
2792
+ errorKind: json.error_kind,
2793
+ issues: Array.isArray(json.issues) ? json.issues : void 0
2794
+ });
2791
2795
  }
2792
2796
  const { result: data } = await applyClientFileDirectives(json.data);
2793
2797
  return { data, note: json.note };
@@ -3270,8 +3274,8 @@ function describeExpectedValue(def) {
3270
3274
  var CONFIG_SCHEMA = [
3271
3275
  {
3272
3276
  key: "telemetry.enabled",
3273
- description: "Whether anonymous opt-out telemetry is enabled (on by default; environment opt-outs like DO_NOT_TRACK are not reflected here \u2014 `argent telemetry status` shows effective consent).",
3274
- scopes: ["global"],
3277
+ description: "Whether anonymous opt-out telemetry is enabled (on by default; environment opt-outs like DO_NOT_TRACK are not reflected here \u2014 `argent telemetry status` shows effective consent). `false` in either scope wins, so a committed project opt-out holds for every teammate.",
3278
+ scopes: ["project", "global"],
3275
3279
  parse: asBoolean,
3276
3280
  merge: "prioritize-restrictive",
3277
3281
  // Opt-out: consent.ts reads an unstored value as enabled, so the config
@@ -7206,7 +7210,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
7206
7210
  var SESSION_ID2 = randomUUID5();
7207
7211
  function readCliVersion() {
7208
7212
  if (true) {
7209
- return "0.22.1-next.5";
7213
+ return "0.22.1-next.7";
7210
7214
  }
7211
7215
  return "0.0.0";
7212
7216
  }
@@ -7568,32 +7572,30 @@ function resolveHostFingerprintAsync() {
7568
7572
 
7569
7573
  // ../telemetry/src/consent.ts
7570
7574
  import * as fs9 from "node:fs";
7571
- var cache = { current: null };
7575
+ var cache = /* @__PURE__ */ new Map();
7572
7576
  var sessionOverride = null;
7573
- function readConfigOverride() {
7577
+ function readConfigOverrideAt(filePath) {
7578
+ const miss = { mtimeMs: null, fingerprint: null, enabledOverride: null };
7574
7579
  let stats;
7575
7580
  try {
7576
- stats = fs9.lstatSync(configFilePath());
7577
- } catch (err) {
7578
- if (err.code === "ENOENT") {
7579
- cache.current = { mtimeMs: null, fingerprint: null, enabledOverride: null };
7580
- return null;
7581
- }
7582
- cache.current = { mtimeMs: null, fingerprint: null, enabledOverride: null };
7581
+ stats = fs9.lstatSync(filePath);
7582
+ } catch {
7583
+ cache.set(filePath, miss);
7583
7584
  return null;
7584
7585
  }
7585
7586
  if (!stats.isFile()) {
7586
- cache.current = { mtimeMs: null, fingerprint: null, enabledOverride: null };
7587
+ cache.set(filePath, miss);
7587
7588
  return null;
7588
7589
  }
7589
7590
  const fingerprint = `${stats.dev}:${stats.ino}:${stats.size}`;
7590
7591
  const mtimeMs = stats.mtimeMs;
7591
- if (cache.current && cache.current.fingerprint === fingerprint && cache.current.mtimeMs === mtimeMs) {
7592
- return cache.current.enabledOverride;
7592
+ const cached2 = cache.get(filePath);
7593
+ if (cached2 && cached2.fingerprint === fingerprint && cached2.mtimeMs === mtimeMs) {
7594
+ return cached2.enabledOverride;
7593
7595
  }
7594
7596
  let parsedEnabled = null;
7595
7597
  try {
7596
- const raw = fs9.readFileSync(configFilePath(), "utf8");
7598
+ const raw = fs9.readFileSync(filePath, "utf8");
7597
7599
  const json = JSON.parse(raw);
7598
7600
  if (json && typeof json === "object") {
7599
7601
  const t2 = json.telemetry;
@@ -7604,9 +7606,27 @@ function readConfigOverride() {
7604
7606
  }
7605
7607
  } catch {
7606
7608
  }
7607
- cache.current = { mtimeMs, fingerprint, enabledOverride: parsedEnabled };
7609
+ cache.set(filePath, { mtimeMs, fingerprint, enabledOverride: parsedEnabled });
7608
7610
  return parsedEnabled;
7609
7611
  }
7612
+ function readPersistedConsent(cwd) {
7613
+ const globalPath = configFilePath("global");
7614
+ const projectPath = configFilePath("project", { cwd });
7615
+ const global2 = readConfigOverrideAt(globalPath);
7616
+ const project = projectPath === globalPath ? null : readConfigOverrideAt(projectPath);
7617
+ if (project === false && global2 === false) {
7618
+ return { enabled: false, detail: "config.json (project and global)" };
7619
+ }
7620
+ if (project === false) return { enabled: false, detail: "config.json (project)" };
7621
+ if (global2 === false) return { enabled: false, detail: "config.json (global)" };
7622
+ if (project === true || global2 === true) {
7623
+ return {
7624
+ enabled: true,
7625
+ detail: project === true ? "config.json (project)" : "config.json (global)"
7626
+ };
7627
+ }
7628
+ return null;
7629
+ }
7610
7630
  function parseFalsy(value) {
7611
7631
  if (value === void 0) return false;
7612
7632
  const v = value.trim().toLowerCase();
@@ -7616,7 +7636,7 @@ function isDoNotTrackSet(value) {
7616
7636
  if (value === void 0 || value.trim() === "") return false;
7617
7637
  return !parseFalsy(value);
7618
7638
  }
7619
- function getConsentState(env = process.env) {
7639
+ function getConsentState(env = process.env, cwd = process.cwd()) {
7620
7640
  if (isDoNotTrackSet(env.DO_NOT_TRACK)) {
7621
7641
  return {
7622
7642
  enabled: false,
@@ -7633,24 +7653,28 @@ function getConsentState(env = process.env) {
7633
7653
  if (sessionOverride !== null) {
7634
7654
  return { enabled: sessionOverride, source: { source: "session_override" } };
7635
7655
  }
7636
- const persisted = readConfigOverride();
7637
- if (persisted === false) {
7638
- return { enabled: false, source: { source: "config_file", detail: "config.json" } };
7639
- }
7640
- if (persisted === true) {
7641
- return { enabled: true, source: { source: "config_file", detail: "config.json" } };
7656
+ const persisted = readPersistedConsent(cwd);
7657
+ if (persisted !== null) {
7658
+ return {
7659
+ enabled: persisted.enabled,
7660
+ source: { source: "config_file", detail: persisted.detail }
7661
+ };
7642
7662
  }
7643
7663
  return { enabled: true, source: { source: "default" } };
7644
7664
  }
7645
- function isEnabled(env = process.env) {
7646
- return getConsentState(env).enabled;
7665
+ function isEnabled(env = process.env, cwd) {
7666
+ return getConsentState(env, cwd).enabled;
7647
7667
  }
7648
- function writeConsentFlag(enabled) {
7649
- updateConfig((config2) => {
7650
- const telemetryBlock = typeof config2.telemetry === "object" && config2.telemetry ? config2.telemetry : {};
7651
- config2.telemetry = { ...telemetryBlock, enabled };
7652
- });
7653
- cache.current = null;
7668
+ function writeConsentFlag(enabled, scope = "global", options = {}) {
7669
+ updateConfig(
7670
+ (config2) => {
7671
+ const telemetryBlock = typeof config2.telemetry === "object" && config2.telemetry ? config2.telemetry : {};
7672
+ config2.telemetry = { ...telemetryBlock, enabled };
7673
+ },
7674
+ scope,
7675
+ options
7676
+ );
7677
+ cache.clear();
7654
7678
  }
7655
7679
 
7656
7680
  // ../telemetry/src/notice.ts
@@ -7746,13 +7770,13 @@ async function shutdown(timeoutMs = SHORT_FLUSH_TIMEOUT_MS) {
7746
7770
  state = null;
7747
7771
  }
7748
7772
  }
7749
- function markEnabled() {
7750
- writeConsentFlag(true);
7773
+ function markEnabled(scope = "global") {
7774
+ writeConsentFlag(true, scope);
7751
7775
  }
7752
- async function markDisabled() {
7776
+ async function markDisabled(scope = "global") {
7753
7777
  try {
7754
7778
  const client2 = getConstructedClient();
7755
- writeConsentFlag(false);
7779
+ writeConsentFlag(false, scope);
7756
7780
  if (client2) {
7757
7781
  try {
7758
7782
  await raceDrain(client2, SHORT_FLUSH_TIMEOUT_MS);
@@ -8009,7 +8033,11 @@ function findMissingRequired(payload, schema) {
8009
8033
  }
8010
8034
  return names.filter((name) => !Object.hasOwn(payload, name));
8011
8035
  }
8012
- function describeServerValidationFailure(err, payload, schema) {
8036
+ function serverIssueList(err) {
8037
+ const carried = err?.issues;
8038
+ if (Array.isArray(carried)) {
8039
+ return carried.length > 0 && carried.every(isValidationIssue) ? carried : null;
8040
+ }
8013
8041
  const message = err instanceof Error ? err.message : typeof err === "string" ? err : null;
8014
8042
  if (message === null) return null;
8015
8043
  let parsed;
@@ -8019,7 +8047,11 @@ function describeServerValidationFailure(err, payload, schema) {
8019
8047
  return null;
8020
8048
  }
8021
8049
  if (!Array.isArray(parsed) || parsed.length === 0) return null;
8022
- if (!parsed.every(isValidationIssue)) return null;
8050
+ return parsed.every(isValidationIssue) ? parsed : null;
8051
+ }
8052
+ function describeServerValidationFailure(err, payload, schema) {
8053
+ const parsed = serverIssueList(err);
8054
+ if (parsed === null) return null;
8023
8055
  const properties = schema?.properties ?? {};
8024
8056
  const addressesThisTool = (issue) => issue.path.length === 0 || typeof issue.path[0] === "string" && Object.hasOwn(properties, issue.path[0]);
8025
8057
  if (!parsed.every(addressesThisTool)) return null;
@@ -10791,8 +10823,8 @@ Options:
10791
10823
  console.log("Feature flags (project overrides global):");
10792
10824
  const maxName = registryView.reduce((m, f) => Math.max(m, f.name.length), 0);
10793
10825
  for (const f of registryView) {
10794
- const scopeLabel2 = f.scope ? ` (${f.scope})` : "";
10795
- console.log(` ${f.name.padEnd(maxName, " ")} ${colorState(f.enabled)}${scopeLabel2}`);
10826
+ const scopeLabel3 = f.scope ? ` (${f.scope})` : "";
10827
+ console.log(` ${f.name.padEnd(maxName, " ")} ${colorState(f.enabled)}${scopeLabel3}`);
10796
10828
  console.log(` ${" ".repeat(maxName)} ${f.description}`);
10797
10829
  }
10798
10830
  }
@@ -12792,8 +12824,80 @@ async function unlink3(argv) {
12792
12824
 
12793
12825
  // ../argent-cli/src/telemetry.ts
12794
12826
  var import_picocolors5 = __toESM(require_picocolors(), 1);
12827
+
12828
+ // ../argent-cli/src/command-args.ts
12829
+ var UsageError = class extends Error {
12830
+ constructor(message) {
12831
+ super(message);
12832
+ this.name = "UsageError";
12833
+ }
12834
+ };
12835
+ function parseCommandArgs(argv, specs) {
12836
+ const positionals = [];
12837
+ const options = {};
12838
+ for (let i2 = 0; i2 < argv.length; i2++) {
12839
+ const tok = argv[i2];
12840
+ if (tok === "--") {
12841
+ positionals.push(...argv.slice(i2 + 1));
12842
+ break;
12843
+ }
12844
+ if (!tok.startsWith("--")) {
12845
+ positionals.push(tok);
12846
+ continue;
12847
+ }
12848
+ const eq = tok.indexOf("=");
12849
+ const name = eq === -1 ? tok.slice(2) : tok.slice(2, eq);
12850
+ const inlineValue = eq === -1 ? void 0 : tok.slice(eq + 1);
12851
+ const spec = specs[name];
12852
+ if (!spec) throw new UsageError(`Unknown flag "${tok}".`);
12853
+ if (spec.kind === "boolean") {
12854
+ if (inlineValue !== void 0) throw new UsageError(`--${name} does not take a value.`);
12855
+ options[name] = true;
12856
+ continue;
12857
+ }
12858
+ let value = inlineValue;
12859
+ if (value === void 0) {
12860
+ const next = argv[i2 + 1];
12861
+ if (next !== void 0 && !next.startsWith("--")) {
12862
+ value = next;
12863
+ i2 += 1;
12864
+ }
12865
+ }
12866
+ if (value === void 0 || value === "") {
12867
+ throw new UsageError(
12868
+ `--${name} requires a value${spec.choices ? ` (${spec.choices.join("|")})` : ""}.`
12869
+ );
12870
+ }
12871
+ if (spec.choices && !spec.choices.includes(value)) {
12872
+ throw new UsageError(
12873
+ `--${name} must be one of ${spec.choices.map((c2) => `"${c2}"`).join(", ")} (got "${value}").`
12874
+ );
12875
+ }
12876
+ options[name] = value;
12877
+ }
12878
+ return { positionals, options };
12879
+ }
12880
+
12881
+ // ../argent-cli/src/telemetry.ts
12882
+ var SCOPES = ["global", "project"];
12883
+ var TELEMETRY_OPTIONS = {
12884
+ scope: { kind: "value", choices: SCOPES }
12885
+ };
12795
12886
  async function telemetry(args) {
12796
12887
  const sub = args[0];
12888
+ let scope = "global";
12889
+ try {
12890
+ const { positionals, options } = parseCommandArgs(args.slice(1), TELEMETRY_OPTIONS);
12891
+ if (positionals.length > 0) {
12892
+ throw new UsageError(`Unexpected argument "${positionals[0]}".`);
12893
+ }
12894
+ if (options.scope !== void 0) scope = options.scope;
12895
+ } catch (err) {
12896
+ if (!(err instanceof UsageError)) throw err;
12897
+ console.error(`Error: ${err.message}`);
12898
+ printUsage3();
12899
+ process.exit(2);
12900
+ }
12797
12901
  init("cli");
12798
12902
  switch (sub) {
12799
12903
  case void 0:
@@ -12805,10 +12909,10 @@ async function telemetry(args) {
12805
12909
  await shutdown();
12806
12910
  return;
12807
12911
  case "enable":
12808
- await cmdEnable();
12912
+ await cmdEnable(scope);
12809
12913
  return;
12810
12914
  case "disable":
12811
- await cmdDisable();
12915
+ await cmdDisable(scope);
12812
12916
  return;
12813
12917
  case "--help":
12814
12918
  case "-h":
@@ -12823,37 +12927,64 @@ async function telemetry(args) {
12823
12927
  }
12824
12928
  function printUsage3() {
12825
12929
  console.log(`Usage:
12826
- argent telemetry status Show telemetry state and device id
12827
- argent telemetry enable Enable telemetry
12828
- argent telemetry disable Disable telemetry
12930
+ argent telemetry status Show telemetry state and device id
12931
+ argent telemetry enable [--scope global|project] Enable telemetry
12932
+ argent telemetry disable [--scope global|project] Disable telemetry
12933
+
12934
+ The default scope is global (~/.argent/config.json). \`--scope project\` writes
12935
+ <project-root>/.argent/config.json instead \u2014 commit it and telemetry stays off
12936
+ for everyone who clones the repository. \`false\` in either scope wins.
12829
12937
  `);
12830
12938
  }
12939
+ function scopeLabel2(scope) {
12940
+ return scope === "project" ? "project scope" : "global scope";
12941
+ }
12831
12942
  function printStatus() {
12832
12943
  const s = status();
12833
12944
  const idLabel = s.anonIdPrefix ? `${s.anonIdPrefix}...` : s.hasAnonIdOnDisk ? "present" : "not created";
12834
12945
  console.log("telemetry:");
12835
12946
  console.log(` state: ${s.enabled ? "enabled" : "disabled"}`);
12947
+ console.log(` source: ${describeSource(s.source)}`);
12836
12948
  console.log(` device id: ${idLabel}`);
12837
12949
  }
12838
- async function cmdEnable() {
12950
+ function describeSource(source) {
12951
+ switch (source.source) {
12952
+ case "env_do_not_track":
12953
+ case "env_argent_telemetry":
12954
+ return `environment (${source.detail ?? source.source})`;
12955
+ case "config_file":
12956
+ return source.detail ?? "config.json";
12957
+ case "session_override":
12958
+ return "this session";
12959
+ case "default":
12960
+ return "default (no opt-out set)";
12961
+ }
12962
+ }
12963
+ async function cmdEnable(scope) {
12839
12964
  const wasEnabled = isEnabled();
12840
- markEnabled();
12841
- if (wasEnabled) {
12842
- console.log(import_picocolors5.default.dim("Telemetry was already enabled."));
12965
+ markEnabled(scope);
12966
+ const nowEnabled = isEnabled();
12967
+ if (!nowEnabled) {
12968
+ console.log(
12969
+ import_picocolors5.default.yellow(
12970
+ `Telemetry set to enabled at ${scopeLabel2(scope)}, but it stays disabled: ${describeSource(status().source)} still opts out.`
12971
+ )
12972
+ );
12973
+ } else if (wasEnabled) {
12974
+ console.log(import_picocolors5.default.dim(`Telemetry was already enabled (written at ${scopeLabel2(scope)}).`));
12843
12975
  } else {
12844
- console.log(import_picocolors5.default.green("Telemetry enabled."));
12976
+ console.log(import_picocolors5.default.green(`Telemetry enabled (${scopeLabel2(scope)}).`));
12845
12977
  }
12846
12978
  await shutdown();
12847
12979
  }
12848
- async function cmdDisable() {
12980
+ async function cmdDisable(scope) {
12849
12981
  const wasEnabled = isEnabled();
12982
+ await markDisabled(scope);
12850
12983
  if (!wasEnabled) {
12851
- console.log(import_picocolors5.default.dim("Telemetry was already disabled."));
12852
- await shutdown();
12853
- return;
12984
+ console.log(import_picocolors5.default.dim(`Telemetry was already disabled (written at ${scopeLabel2(scope)}).`));
12985
+ } else {
12986
+ console.log(import_picocolors5.default.red(`Telemetry disabled (${scopeLabel2(scope)}).`));
12854
12987
  }
12855
- await markDisabled();
12856
- console.log(import_picocolors5.default.red("Telemetry disabled."));
12857
12988
  await shutdown();
12858
12989
  }
12859
12990
  export {
@@ -16428,7 +16428,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
16428
16428
  var SESSION_ID = randomUUID4();
16429
16429
  function readCliVersion() {
16430
16430
  if (true) {
16431
- return "0.22.1-next.5";
16431
+ return "0.22.1-next.7";
16432
16432
  }
16433
16433
  return "0.0.0";
16434
16434
  }
@@ -16806,32 +16806,30 @@ function resolveHostFingerprintAsync() {
16806
16806
 
16807
16807
  // ../telemetry/src/consent.ts
16808
16808
  import * as fs8 from "node:fs";
16809
- var cache = { current: null };
16809
+ var cache = /* @__PURE__ */ new Map();
16810
16810
  var sessionOverride = null;
16811
- function readConfigOverride() {
16811
+ function readConfigOverrideAt(filePath) {
16812
+ const miss = { mtimeMs: null, fingerprint: null, enabledOverride: null };
16812
16813
  let stats;
16813
16814
  try {
16814
- stats = fs8.lstatSync(configFilePath());
16815
- } catch (err) {
16816
- if (err.code === "ENOENT") {
16817
- cache.current = { mtimeMs: null, fingerprint: null, enabledOverride: null };
16818
- return null;
16819
- }
16820
- cache.current = { mtimeMs: null, fingerprint: null, enabledOverride: null };
16815
+ stats = fs8.lstatSync(filePath);
16816
+ } catch {
16817
+ cache.set(filePath, miss);
16821
16818
  return null;
16822
16819
  }
16823
16820
  if (!stats.isFile()) {
16824
- cache.current = { mtimeMs: null, fingerprint: null, enabledOverride: null };
16821
+ cache.set(filePath, miss);
16825
16822
  return null;
16826
16823
  }
16827
16824
  const fingerprint = `${stats.dev}:${stats.ino}:${stats.size}`;
16828
16825
  const mtimeMs = stats.mtimeMs;
16829
- if (cache.current && cache.current.fingerprint === fingerprint && cache.current.mtimeMs === mtimeMs) {
16830
- return cache.current.enabledOverride;
16826
+ const cached2 = cache.get(filePath);
16827
+ if (cached2 && cached2.fingerprint === fingerprint && cached2.mtimeMs === mtimeMs) {
16828
+ return cached2.enabledOverride;
16831
16829
  }
16832
16830
  let parsedEnabled = null;
16833
16831
  try {
16834
- const raw = fs8.readFileSync(configFilePath(), "utf8");
16832
+ const raw = fs8.readFileSync(filePath, "utf8");
16835
16833
  const json = JSON.parse(raw);
16836
16834
  if (json && typeof json === "object") {
16837
16835
  const t2 = json.telemetry;
@@ -16842,9 +16840,27 @@ function readConfigOverride() {
16842
16840
  }
16843
16841
  } catch {
16844
16842
  }
16845
- cache.current = { mtimeMs, fingerprint, enabledOverride: parsedEnabled };
16843
+ cache.set(filePath, { mtimeMs, fingerprint, enabledOverride: parsedEnabled });
16846
16844
  return parsedEnabled;
16847
16845
  }
16846
+ function readPersistedConsent(cwd) {
16847
+ const globalPath = configFilePath("global");
16848
+ const projectPath = configFilePath("project", { cwd });
16849
+ const global2 = readConfigOverrideAt(globalPath);
16850
+ const project = projectPath === globalPath ? null : readConfigOverrideAt(projectPath);
16851
+ if (project === false && global2 === false) {
16852
+ return { enabled: false, detail: "config.json (project and global)" };
16853
+ }
16854
+ if (project === false) return { enabled: false, detail: "config.json (project)" };
16855
+ if (global2 === false) return { enabled: false, detail: "config.json (global)" };
16856
+ if (project === true || global2 === true) {
16857
+ return {
16858
+ enabled: true,
16859
+ detail: project === true ? "config.json (project)" : "config.json (global)"
16860
+ };
16861
+ }
16862
+ return null;
16863
+ }
16848
16864
  function parseFalsy(value) {
16849
16865
  if (value === void 0) return false;
16850
16866
  const v = value.trim().toLowerCase();
@@ -16854,7 +16870,7 @@ function isDoNotTrackSet(value) {
16854
16870
  if (value === void 0 || value.trim() === "") return false;
16855
16871
  return !parseFalsy(value);
16856
16872
  }
16857
- function getConsentState(env = process.env) {
16873
+ function getConsentState(env = process.env, cwd = process.cwd()) {
16858
16874
  if (isDoNotTrackSet(env.DO_NOT_TRACK)) {
16859
16875
  return {
16860
16876
  enabled: false,
@@ -16871,24 +16887,28 @@ function getConsentState(env = process.env) {
16871
16887
  if (sessionOverride !== null) {
16872
16888
  return { enabled: sessionOverride, source: { source: "session_override" } };
16873
16889
  }
16874
- const persisted = readConfigOverride();
16875
- if (persisted === false) {
16876
- return { enabled: false, source: { source: "config_file", detail: "config.json" } };
16877
- }
16878
- if (persisted === true) {
16879
- return { enabled: true, source: { source: "config_file", detail: "config.json" } };
16890
+ const persisted = readPersistedConsent(cwd);
16891
+ if (persisted !== null) {
16892
+ return {
16893
+ enabled: persisted.enabled,
16894
+ source: { source: "config_file", detail: persisted.detail }
16895
+ };
16880
16896
  }
16881
16897
  return { enabled: true, source: { source: "default" } };
16882
16898
  }
16883
- function isEnabled(env = process.env) {
16884
- return getConsentState(env).enabled;
16899
+ function isEnabled(env = process.env, cwd) {
16900
+ return getConsentState(env, cwd).enabled;
16885
16901
  }
16886
- function writeConsentFlag(enabled) {
16887
- updateConfig((config) => {
16888
- const telemetryBlock = typeof config.telemetry === "object" && config.telemetry ? config.telemetry : {};
16889
- config.telemetry = { ...telemetryBlock, enabled };
16890
- });
16891
- cache.current = null;
16902
+ function writeConsentFlag(enabled, scope = "global", options = {}) {
16903
+ updateConfig(
16904
+ (config) => {
16905
+ const telemetryBlock = typeof config.telemetry === "object" && config.telemetry ? config.telemetry : {};
16906
+ config.telemetry = { ...telemetryBlock, enabled };
16907
+ },
16908
+ scope,
16909
+ options
16910
+ );
16911
+ cache.clear();
16892
16912
  }
16893
16913
  function setSessionConsentOverride(enabled) {
16894
16914
  sessionOverride = enabled;
@@ -22455,6 +22475,18 @@ async function init2(args) {
22455
22475
  }
22456
22476
  tel.installMode = modeFromFlags ?? await promptInstallMode(recordedMode ?? "global");
22457
22477
  track("installation:install_mode_decision", { install_mode: tel.installMode });
22478
+ let wroteProjectTelemetryOptOut = false;
22479
+ if (parsed.noTelemetry && tel.installMode === "local") {
22480
+ try {
22481
+ writeConsentFlag(false, "project", { cwd: initProjectRoot });
22482
+ wroteProjectTelemetryOptOut = true;
22483
+ log.info(
22484
+ `${import_picocolors10.default.bold("Telemetry")} ${import_picocolors10.default.dim("also disabled for this project \u2014")} ${import_picocolors10.default.cyan(".argent/config.json")} ${import_picocolors10.default.dim("(commit it so the opt-out applies to every clone).")}`
22485
+ );
22486
+ } catch (err) {
22487
+ log.warn(`Could not write the project telemetry opt-out: ${err}`);
22488
+ }
22489
+ }
22458
22490
  version = await runInstall({
22459
22491
  installMode: tel.installMode,
22460
22492
  fromTar: parsed.fromTar,
@@ -22582,7 +22614,8 @@ async function init2(args) {
22582
22614
  scope,
22583
22615
  allowlistEnabled: allowlist.enabled,
22584
22616
  skillsMethod,
22585
- copiedRules: copyResults.length > 0
22617
+ copiedRules: copyResults.length > 0,
22618
+ wroteProjectTelemetryOptOut
22586
22619
  });
22587
22620
  note(
22588
22621
  [
@@ -22620,7 +22653,8 @@ function printSummary({
22620
22653
  scope,
22621
22654
  allowlistEnabled,
22622
22655
  skillsMethod,
22623
- copiedRules
22656
+ copiedRules,
22657
+ wroteProjectTelemetryOptOut
22624
22658
  }) {
22625
22659
  const summaryLines = [
22626
22660
  `${import_picocolors10.default.green("Install mode")} ${installMode === "local" ? "local (devDependency)" : "global"}`,
@@ -22638,7 +22672,7 @@ function printSummary({
22638
22672
  `${import_picocolors10.default.bold("Commit")} so your team shares the same setup:`,
22639
22673
  ` ${import_picocolors10.default.cyan("package.json")} + your lockfile`,
22640
22674
  ` the written MCP config (.mcp.json, .cursor/mcp.json, \u2026)`,
22641
- ` ${import_picocolors10.default.cyan(".argent/install.json")}, and the skills/rules/agents files`,
22675
+ ` ${import_picocolors10.default.cyan(".argent/install.json")}${wroteProjectTelemetryOptOut ? ` + ${import_picocolors10.default.cyan(".argent/config.json")}` : ""}, and the skills/rules/agents files`,
22642
22676
  "",
22643
22677
  `Teammates then get argent on ${import_picocolors10.default.cyan("npm install")} \u2014 no global install, no ${import_picocolors10.default.cyan("argent init")}.`,
22644
22678
  import_picocolors10.default.dim(
@@ -16855,6 +16855,12 @@ async function applyClientFileDirectives(result) {
16855
16855
  return { result: rewritten, written };
16856
16856
  }
16857
16857
 
16858
+ // ../argent-tools-client/src/tools-client.ts
16859
+ function errorBodyMessage(body) {
16860
+ if (Array.isArray(body.issues) && typeof body.message === "string") return body.message;
16861
+ return body.error ?? body.message;
16862
+ }
16863
+
16858
16864
  // ../argent-tools-client/src/artifacts.ts
16859
16865
  import { copyFile, mkdir as mkdir4, readFile as readFile4, realpath, rm as rm3, stat as stat3, writeFile as writeFile4 } from "node:fs/promises";
16860
16866
  import { constants as fsConstants } from "node:fs";
@@ -17136,8 +17142,8 @@ function asStringArray(raw) {
17136
17142
  var CONFIG_SCHEMA = [
17137
17143
  {
17138
17144
  key: "telemetry.enabled",
17139
- description: "Whether anonymous opt-out telemetry is enabled (on by default; environment opt-outs like DO_NOT_TRACK are not reflected here \u2014 `argent telemetry status` shows effective consent).",
17140
- scopes: ["global"],
17145
+ description: "Whether anonymous opt-out telemetry is enabled (on by default; environment opt-outs like DO_NOT_TRACK are not reflected here \u2014 `argent telemetry status` shows effective consent). `false` in either scope wins, so a committed project opt-out holds for every teammate.",
17146
+ scopes: ["project", "global"],
17141
17147
  parse: asBoolean,
17142
17148
  merge: "prioritize-restrictive",
17143
17149
  // Opt-out: consent.ts reads an unstored value as enabled, so the config
@@ -18171,32 +18177,30 @@ var DYLIB_TVOS_DIR = path11.join(DYLIB_DIR, "tvos");
18171
18177
 
18172
18178
  // ../telemetry/src/consent.ts
18173
18179
  import * as fs8 from "node:fs";
18174
- var cache = { current: null };
18180
+ var cache = /* @__PURE__ */ new Map();
18175
18181
  var sessionOverride = null;
18176
- function readConfigOverride() {
18182
+ function readConfigOverrideAt(filePath) {
18183
+ const miss = { mtimeMs: null, fingerprint: null, enabledOverride: null };
18177
18184
  let stats;
18178
18185
  try {
18179
- stats = fs8.lstatSync(configFilePath());
18180
- } catch (err) {
18181
- if (err.code === "ENOENT") {
18182
- cache.current = { mtimeMs: null, fingerprint: null, enabledOverride: null };
18183
- return null;
18184
- }
18185
- cache.current = { mtimeMs: null, fingerprint: null, enabledOverride: null };
18186
+ stats = fs8.lstatSync(filePath);
18187
+ } catch {
18188
+ cache.set(filePath, miss);
18186
18189
  return null;
18187
18190
  }
18188
18191
  if (!stats.isFile()) {
18189
- cache.current = { mtimeMs: null, fingerprint: null, enabledOverride: null };
18192
+ cache.set(filePath, miss);
18190
18193
  return null;
18191
18194
  }
18192
18195
  const fingerprint = `${stats.dev}:${stats.ino}:${stats.size}`;
18193
18196
  const mtimeMs = stats.mtimeMs;
18194
- if (cache.current && cache.current.fingerprint === fingerprint && cache.current.mtimeMs === mtimeMs) {
18195
- return cache.current.enabledOverride;
18197
+ const cached2 = cache.get(filePath);
18198
+ if (cached2 && cached2.fingerprint === fingerprint && cached2.mtimeMs === mtimeMs) {
18199
+ return cached2.enabledOverride;
18196
18200
  }
18197
18201
  let parsedEnabled = null;
18198
18202
  try {
18199
- const raw = fs8.readFileSync(configFilePath(), "utf8");
18203
+ const raw = fs8.readFileSync(filePath, "utf8");
18200
18204
  const json = JSON.parse(raw);
18201
18205
  if (json && typeof json === "object") {
18202
18206
  const t = json.telemetry;
@@ -18207,9 +18211,27 @@ function readConfigOverride() {
18207
18211
  }
18208
18212
  } catch {
18209
18213
  }
18210
- cache.current = { mtimeMs, fingerprint, enabledOverride: parsedEnabled };
18214
+ cache.set(filePath, { mtimeMs, fingerprint, enabledOverride: parsedEnabled });
18211
18215
  return parsedEnabled;
18212
18216
  }
18217
+ function readPersistedConsent(cwd) {
18218
+ const globalPath = configFilePath("global");
18219
+ const projectPath = configFilePath("project", { cwd });
18220
+ const global = readConfigOverrideAt(globalPath);
18221
+ const project = projectPath === globalPath ? null : readConfigOverrideAt(projectPath);
18222
+ if (project === false && global === false) {
18223
+ return { enabled: false, detail: "config.json (project and global)" };
18224
+ }
18225
+ if (project === false) return { enabled: false, detail: "config.json (project)" };
18226
+ if (global === false) return { enabled: false, detail: "config.json (global)" };
18227
+ if (project === true || global === true) {
18228
+ return {
18229
+ enabled: true,
18230
+ detail: project === true ? "config.json (project)" : "config.json (global)"
18231
+ };
18232
+ }
18233
+ return null;
18234
+ }
18213
18235
  function parseFalsy(value) {
18214
18236
  if (value === void 0) return false;
18215
18237
  const v = value.trim().toLowerCase();
@@ -18219,7 +18241,7 @@ function isDoNotTrackSet(value) {
18219
18241
  if (value === void 0 || value.trim() === "") return false;
18220
18242
  return !parseFalsy(value);
18221
18243
  }
18222
- function getConsentState(env = process.env) {
18244
+ function getConsentState(env = process.env, cwd = process.cwd()) {
18223
18245
  if (isDoNotTrackSet(env.DO_NOT_TRACK)) {
18224
18246
  return {
18225
18247
  enabled: false,
@@ -18236,17 +18258,17 @@ function getConsentState(env = process.env) {
18236
18258
  if (sessionOverride !== null) {
18237
18259
  return { enabled: sessionOverride, source: { source: "session_override" } };
18238
18260
  }
18239
- const persisted = readConfigOverride();
18240
- if (persisted === false) {
18241
- return { enabled: false, source: { source: "config_file", detail: "config.json" } };
18242
- }
18243
- if (persisted === true) {
18244
- return { enabled: true, source: { source: "config_file", detail: "config.json" } };
18261
+ const persisted = readPersistedConsent(cwd);
18262
+ if (persisted !== null) {
18263
+ return {
18264
+ enabled: persisted.enabled,
18265
+ source: { source: "config_file", detail: persisted.detail }
18266
+ };
18245
18267
  }
18246
18268
  return { enabled: true, source: { source: "default" } };
18247
18269
  }
18248
- function isEnabled(env = process.env) {
18249
- return getConsentState(env).enabled;
18270
+ function isEnabled(env = process.env, cwd) {
18271
+ return getConsentState(env, cwd).enabled;
18250
18272
  }
18251
18273
 
18252
18274
  // ../telemetry/src/notice.ts
@@ -18685,7 +18707,7 @@ async function startMcpServer(options) {
18685
18707
  fetchTimeoutMs: meta2?.longRunning ? null : FETCH_TIMEOUT_MS
18686
18708
  });
18687
18709
  const json = await res.json();
18688
- if (!res.ok) throw new Error(json.error ?? json.message ?? res.statusText);
18710
+ if (!res.ok) throw new Error(errorBodyMessage(json) ?? res.statusText);
18689
18711
  const { result: data } = await applyClientFileDirectives(json.data);
18690
18712
  return { result: data, outputHint: meta2?.outputHint, note: json.note };
18691
18713
  }
@@ -16063,6 +16063,63 @@ function terminatingSignalCause(message) {
16063
16063
  error_kind: "unknown"
16064
16064
  });
16065
16065
  }
16066
+ function valueAtPath(root, path41) {
16067
+ let current = root;
16068
+ for (const key2 of path41) {
16069
+ if (current === null || typeof current !== "object") return void 0;
16070
+ if (!Object.hasOwn(current, key2)) return void 0;
16071
+ current = current[key2];
16072
+ }
16073
+ return current;
16074
+ }
16075
+ function describeParamIssues(error52, params) {
16076
+ const allKeys = params !== null && typeof params === "object" && !Array.isArray(params) ? Object.keys(params) : [];
16077
+ const supplied = allKeys.slice(0, 24);
16078
+ const truncated = allKeys.length > supplied.length;
16079
+ const parts2 = error52.issues.map((issue2) => {
16080
+ const at = issue2.path.length > 0 ? issue2.path.join(".") : "(root)";
16081
+ if (issue2.code === "custom") {
16082
+ return issue2.path.length > 0 ? `\`${at}\`: ${issue2.message}` : issue2.message;
16083
+ }
16084
+ if (valueAtPath(params, issue2.path) === void 0) {
16085
+ const expected = issue2.expected;
16086
+ const kind = typeof expected === "string" ? ` (${expected})` : "";
16087
+ return `\`${at}\` is required${kind} and was not provided`;
16088
+ }
16089
+ if (issue2.code === "unrecognized_keys") {
16090
+ const keys = issue2.keys ?? [];
16091
+ const at2 = issue2.path.length > 0 ? `${issue2.path.join(".")}.` : "";
16092
+ return `unknown parameter${keys.length === 1 ? "" : "s"} ${keys.map((k) => `\`${at2}${k}\``).join(", ")}`;
16093
+ }
16094
+ if (issue2.code === "invalid_union") {
16095
+ const branches = issue2.errors ?? [];
16096
+ const alternatives = [];
16097
+ const seen = /* @__PURE__ */ new Set();
16098
+ let moreAlternatives = false;
16099
+ for (const branch of branches) {
16100
+ for (const inner of branch) {
16101
+ const innerAt = inner.path.length > 0 ? `${at}.${inner.path.join(".")}: ` : "";
16102
+ const text = `${innerAt}${inner.message}`;
16103
+ if (seen.has(text)) continue;
16104
+ if (alternatives.length >= MAX_UNION_ALTERNATIVES) {
16105
+ moreAlternatives = true;
16106
+ break;
16107
+ }
16108
+ seen.add(text);
16109
+ alternatives.push(text);
16110
+ }
16111
+ if (moreAlternatives) break;
16112
+ }
16113
+ if (alternatives.length > 0) {
16114
+ return `\`${at}\`: ${alternatives.join("; or ")}${moreAlternatives ? "; or \u2026" : ""}`;
16115
+ }
16116
+ }
16117
+ return `\`${at}\`: ${issue2.message}`;
16118
+ });
16119
+ const sent = supplied.length > 0 ? ` You sent: ${supplied.map((k) => `\`${k}\``).join(", ")}${truncated ? ", \u2026" : ""}.` : "";
16120
+ const body = parts2.length > 0 ? `${parts2.map((p) => p.replace(/\.$/, "")).join("; ")}.` : "";
16121
+ return `${body}${sent}`.trim() || "invalid parameters";
16122
+ }
16066
16123
  function formatInteractionMessage(format, fallback) {
16067
16124
  try {
16068
16125
  return format() ?? fallback;
@@ -16070,7 +16127,7 @@ function formatInteractionMessage(format, fallback) {
16070
16127
  return fallback;
16071
16128
  }
16072
16129
  }
16073
- var import_node_crypto2, Registry;
16130
+ var import_node_crypto2, Registry, MAX_UNION_ALTERNATIVES;
16074
16131
  var init_registry = __esm({
16075
16132
  "../registry/src/registry.ts"() {
16076
16133
  "use strict";
@@ -16151,7 +16208,15 @@ var init_registry = __esm({
16151
16208
  if (definition.zodSchema) {
16152
16209
  const parsed = definition.zodSchema.safeParse(params ?? {});
16153
16210
  if (!parsed.success) {
16154
- throw new Error(`Invalid params for tool "${id}": ${parsed.error.message}`);
16211
+ throw new FailureError(
16212
+ `Invalid params for tool "${id}": ${describeParamIssues(parsed.error, params)}`,
16213
+ {
16214
+ error_code: FAILURE_CODES.TOOL_INPUT_INVALID,
16215
+ failure_stage: "tool_params_parse",
16216
+ failure_area: "registry",
16217
+ error_kind: "validation"
16218
+ }
16219
+ );
16155
16220
  }
16156
16221
  effectiveParams = parsed.data;
16157
16222
  }
@@ -16388,6 +16453,7 @@ var init_registry = __esm({
16388
16453
  this._transition(node, cause ? "ERROR" /* ERROR */ : "IDLE" /* IDLE */, cause);
16389
16454
  }
16390
16455
  };
16456
+ MAX_UNION_ALTERNATIVES = 12;
16391
16457
  }
16392
16458
  });
16393
16459
 
@@ -92066,8 +92132,8 @@ function asStringArray(raw) {
92066
92132
  var CONFIG_SCHEMA = [
92067
92133
  {
92068
92134
  key: "telemetry.enabled",
92069
- description: "Whether anonymous opt-out telemetry is enabled (on by default; environment opt-outs like DO_NOT_TRACK are not reflected here \u2014 `argent telemetry status` shows effective consent).",
92070
- scopes: ["global"],
92135
+ description: "Whether anonymous opt-out telemetry is enabled (on by default; environment opt-outs like DO_NOT_TRACK are not reflected here \u2014 `argent telemetry status` shows effective consent). `false` in either scope wins, so a committed project opt-out holds for every teammate.",
92136
+ scopes: ["project", "global"],
92071
92137
  parse: asBoolean,
92072
92138
  merge: "prioritize-restrictive",
92073
92139
  // Opt-out: consent.ts reads an unstored value as enabled, so the config
@@ -95668,7 +95734,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
95668
95734
  var SESSION_ID = (0, import_node_crypto3.randomUUID)();
95669
95735
  function readCliVersion() {
95670
95736
  if (true) {
95671
- return "0.22.1-next.5";
95737
+ return "0.22.1-next.7";
95672
95738
  }
95673
95739
  return "0.0.0";
95674
95740
  }
@@ -96135,32 +96201,30 @@ function resolveHostFingerprintAsync() {
96135
96201
 
96136
96202
  // ../telemetry/src/consent.ts
96137
96203
  var fs8 = __toESM(require("node:fs"));
96138
- var cache = { current: null };
96204
+ var cache = /* @__PURE__ */ new Map();
96139
96205
  var sessionOverride = null;
96140
- function readConfigOverride() {
96206
+ function readConfigOverrideAt(filePath) {
96207
+ const miss = { mtimeMs: null, fingerprint: null, enabledOverride: null };
96141
96208
  let stats;
96142
96209
  try {
96143
- stats = fs8.lstatSync(configFilePath());
96144
- } catch (err) {
96145
- if (err.code === "ENOENT") {
96146
- cache.current = { mtimeMs: null, fingerprint: null, enabledOverride: null };
96147
- return null;
96148
- }
96149
- cache.current = { mtimeMs: null, fingerprint: null, enabledOverride: null };
96210
+ stats = fs8.lstatSync(filePath);
96211
+ } catch {
96212
+ cache.set(filePath, miss);
96150
96213
  return null;
96151
96214
  }
96152
96215
  if (!stats.isFile()) {
96153
- cache.current = { mtimeMs: null, fingerprint: null, enabledOverride: null };
96216
+ cache.set(filePath, miss);
96154
96217
  return null;
96155
96218
  }
96156
96219
  const fingerprint = `${stats.dev}:${stats.ino}:${stats.size}`;
96157
96220
  const mtimeMs = stats.mtimeMs;
96158
- if (cache.current && cache.current.fingerprint === fingerprint && cache.current.mtimeMs === mtimeMs) {
96159
- return cache.current.enabledOverride;
96221
+ const cached3 = cache.get(filePath);
96222
+ if (cached3 && cached3.fingerprint === fingerprint && cached3.mtimeMs === mtimeMs) {
96223
+ return cached3.enabledOverride;
96160
96224
  }
96161
96225
  let parsedEnabled = null;
96162
96226
  try {
96163
- const raw = fs8.readFileSync(configFilePath(), "utf8");
96227
+ const raw = fs8.readFileSync(filePath, "utf8");
96164
96228
  const json2 = JSON.parse(raw);
96165
96229
  if (json2 && typeof json2 === "object") {
96166
96230
  const t = json2.telemetry;
@@ -96171,9 +96235,27 @@ function readConfigOverride() {
96171
96235
  }
96172
96236
  } catch {
96173
96237
  }
96174
- cache.current = { mtimeMs, fingerprint, enabledOverride: parsedEnabled };
96238
+ cache.set(filePath, { mtimeMs, fingerprint, enabledOverride: parsedEnabled });
96175
96239
  return parsedEnabled;
96176
96240
  }
96241
+ function readPersistedConsent(cwd) {
96242
+ const globalPath = configFilePath("global");
96243
+ const projectPath = configFilePath("project", { cwd });
96244
+ const global2 = readConfigOverrideAt(globalPath);
96245
+ const project = projectPath === globalPath ? null : readConfigOverrideAt(projectPath);
96246
+ if (project === false && global2 === false) {
96247
+ return { enabled: false, detail: "config.json (project and global)" };
96248
+ }
96249
+ if (project === false) return { enabled: false, detail: "config.json (project)" };
96250
+ if (global2 === false) return { enabled: false, detail: "config.json (global)" };
96251
+ if (project === true || global2 === true) {
96252
+ return {
96253
+ enabled: true,
96254
+ detail: project === true ? "config.json (project)" : "config.json (global)"
96255
+ };
96256
+ }
96257
+ return null;
96258
+ }
96177
96259
  function parseFalsy(value) {
96178
96260
  if (value === void 0) return false;
96179
96261
  const v = value.trim().toLowerCase();
@@ -96183,7 +96265,7 @@ function isDoNotTrackSet(value) {
96183
96265
  if (value === void 0 || value.trim() === "") return false;
96184
96266
  return !parseFalsy(value);
96185
96267
  }
96186
- function getConsentState(env = process.env) {
96268
+ function getConsentState(env = process.env, cwd = process.cwd()) {
96187
96269
  if (isDoNotTrackSet(env.DO_NOT_TRACK)) {
96188
96270
  return {
96189
96271
  enabled: false,
@@ -96200,17 +96282,17 @@ function getConsentState(env = process.env) {
96200
96282
  if (sessionOverride !== null) {
96201
96283
  return { enabled: sessionOverride, source: { source: "session_override" } };
96202
96284
  }
96203
- const persisted = readConfigOverride();
96204
- if (persisted === false) {
96205
- return { enabled: false, source: { source: "config_file", detail: "config.json" } };
96206
- }
96207
- if (persisted === true) {
96208
- return { enabled: true, source: { source: "config_file", detail: "config.json" } };
96285
+ const persisted = readPersistedConsent(cwd);
96286
+ if (persisted !== null) {
96287
+ return {
96288
+ enabled: persisted.enabled,
96289
+ source: { source: "config_file", detail: persisted.detail }
96290
+ };
96209
96291
  }
96210
96292
  return { enabled: true, source: { source: "default" } };
96211
96293
  }
96212
- function isEnabled(env = process.env) {
96213
- return getConsentState(env).enabled;
96294
+ function isEnabled(env = process.env, cwd) {
96295
+ return getConsentState(env, cwd).enabled;
96214
96296
  }
96215
96297
 
96216
96298
  // ../telemetry/src/notice.ts
@@ -113875,14 +113957,23 @@ async function resolveFileInputs(def, body, lookupUpload) {
113875
113957
  };
113876
113958
  const specs = def.fileInputs;
113877
113959
  if (!specs || specs.length === 0 || typeof body !== "object" || body === null) {
113878
- return { args: body ?? {}, fileInputs: void 0, cleanup };
113960
+ return {
113961
+ args: body ?? {},
113962
+ fileInputs: void 0,
113963
+ derivedTargets: [],
113964
+ cleanup
113965
+ };
113879
113966
  }
113880
113967
  const args = { ...body };
113881
113968
  let resolved;
113969
+ const derivedTargets = [];
113882
113970
  try {
113883
113971
  for (const spec of specs) {
113884
113972
  const value = args[spec.target];
113885
113973
  if (!isFileInputWire(value)) continue;
113974
+ if (spec.path !== `\${${spec.target}}` && !derivedTargets.includes(spec.target)) {
113975
+ derivedTargets.push(spec.target);
113976
+ }
113886
113977
  if (spec.unwrapWhenSet !== void 0 && isParamSet(args[spec.unwrapWhenSet])) {
113887
113978
  args[spec.target] = value.path;
113888
113979
  continue;
@@ -113899,7 +113990,7 @@ async function resolveFileInputs(def, body, lookupUpload) {
113899
113990
  await cleanup();
113900
113991
  throw err;
113901
113992
  }
113902
- return { args, fileInputs: resolved, cleanup };
113993
+ return { args, fileInputs: resolved, derivedTargets, cleanup };
113903
113994
  }
113904
113995
 
113905
113996
  // ../tool-server/src/utils/debugger/device-alias.ts
@@ -114190,6 +114281,14 @@ function isToolExposed(def) {
114190
114281
  function findDependencyMissing(err) {
114191
114282
  return findErrorInCauseChain(err, DependencyMissingError);
114192
114283
  }
114284
+ function omitKeys(args, keys) {
114285
+ if (keys.length === 0 || args === null || typeof args !== "object" || Array.isArray(args)) {
114286
+ return args;
114287
+ }
114288
+ const copy = { ...args };
114289
+ for (const key2 of keys) delete copy[key2];
114290
+ return copy;
114291
+ }
114193
114292
  function errorSignalFields(err) {
114194
114293
  const signal = getFailureSignal(err);
114195
114294
  return signal ? { error_code: signal.error_code, error_kind: signal.error_kind } : {};
@@ -114529,6 +114628,7 @@ function createHttpApp(registry2, options) {
114529
114628
  }
114530
114629
  let bodyArgs;
114531
114630
  let resolvedFileInputs;
114631
+ let derivedTargets;
114532
114632
  try {
114533
114633
  const resolved = await resolveFileInputs(def, req.body, (id) => {
114534
114634
  const entry = uploads.get(id);
@@ -114537,6 +114637,7 @@ function createHttpApp(registry2, options) {
114537
114637
  });
114538
114638
  bodyArgs = resolved.args;
114539
114639
  resolvedFileInputs = resolved.fileInputs;
114640
+ derivedTargets = resolved.derivedTargets;
114540
114641
  res.once("close", () => void resolved.cleanup());
114541
114642
  } catch (err) {
114542
114643
  if (err instanceof FileInputError) {
@@ -114560,7 +114661,11 @@ function createHttpApp(registry2, options) {
114560
114661
  req.body,
114561
114662
  { invalid_params: deriveInvalidParams(parseResult.error, declared) }
114562
114663
  );
114563
- res.status(400).json({ error: parseResult.error.message });
114664
+ res.status(400).json({
114665
+ error: parseResult.error.message,
114666
+ message: describeParamIssues(parseResult.error, omitKeys(bodyArgs, derivedTargets)),
114667
+ issues: parseResult.error.issues
114668
+ });
114564
114669
  return;
114565
114670
  }
114566
114671
  parsedData = parseResult.data;
@@ -114701,6 +114806,10 @@ function createHttpApp(registry2, options) {
114701
114806
  res.status(400).json({ error: invalidInputErr.message, ...errorSignalFields(err) });
114702
114807
  return;
114703
114808
  }
114809
+ if (getFailureSignal(err)?.error_code === FAILURE_CODES.TOOL_INPUT_INVALID) {
114810
+ res.status(400).json({ error: formatErrorForAgent(err), ...errorSignalFields(err) });
114811
+ return;
114812
+ }
114704
114813
  const notImplementedErr = findErrorInCauseChain(err, NotImplementedOnPlatformError);
114705
114814
  if (notImplementedErr) {
114706
114815
  res.status(501).json({
@@ -123254,6 +123363,7 @@ init_zod();
123254
123363
 
123255
123364
  // ../tool-server/src/utils/sub-invoke.ts
123256
123365
  var import_node_crypto7 = require("node:crypto");
123366
+ init_src();
123257
123367
  async function invokeSubTool(registry2, ctx, toolId, args) {
123258
123368
  const signal = ctx?.signal;
123259
123369
  const recordChildInvocation = ctx?.recordChildInvocation;
@@ -123272,6 +123382,14 @@ async function invokeSubTool(registry2, ctx, toolId, args) {
123272
123382
  release();
123273
123383
  }
123274
123384
  }
123385
+ function describeNestedParamError(registry2, err, toolId, dispatchedArgs, authoredArgs) {
123386
+ if (getFailureSignal(err)?.error_code !== FAILURE_CODES.TOOL_INPUT_INVALID) return void 0;
123387
+ const zodSchema76 = registry2.getTool(toolId)?.zodSchema;
123388
+ if (!zodSchema76) return void 0;
123389
+ const parsed = zodSchema76.safeParse(dispatchedArgs ?? {});
123390
+ if (parsed.success) return void 0;
123391
+ return `Invalid params for tool "${toolId}": ${describeParamIssues(parsed.error, authoredArgs)}`;
123392
+ }
123275
123393
 
123276
123394
  // ../tool-server/src/tools/await-ui-element/index.ts
123277
123395
  init_zod();
@@ -129750,8 +129868,8 @@ Stops on the first error (or unmet await-ui-element condition) and returns parti
129750
129868
  throw err;
129751
129869
  }
129752
129870
  }
129871
+ const toolArgs = { ...step.args, udid };
129753
129872
  try {
129754
- const toolArgs = { ...step.args, udid };
129755
129873
  const result = await invokeSubTool(registry2, ctx, step.tool, toolArgs);
129756
129874
  if (isUnmetUiWaitResult(step.tool, result)) {
129757
129875
  const note = result.note;
@@ -129763,9 +129881,16 @@ Stops on the first error (or unmet await-ui-element condition) and returns parti
129763
129881
  }
129764
129882
  results.push({ tool: step.tool, result });
129765
129883
  } catch (err) {
129884
+ const reframed = describeNestedParamError(
129885
+ registry2,
129886
+ err,
129887
+ step.tool,
129888
+ toolArgs,
129889
+ step.args ?? {}
129890
+ );
129766
129891
  results.push({
129767
129892
  tool: step.tool,
129768
- error: err instanceof Error ? err.message : String(err)
129893
+ error: reframed ?? (err instanceof Error ? err.message : String(err))
129769
129894
  });
129770
129895
  break;
129771
129896
  }
@@ -145870,7 +145995,9 @@ var zodSchema63 = external_exports.object({
145870
145995
  project_root: external_exports.string().describe(
145871
145996
  "Absolute path to the project root of the flow being recorded \u2014 the same value passed to flow-start-recording. Together with `name` it identifies which recording this step belongs to."
145872
145997
  ),
145873
- command: external_exports.string().describe('MCP tool name (e.g. "gesture-tap", "screenshot", "launch-app")'),
145998
+ command: external_exports.string().describe(
145999
+ 'MCP tool name (e.g. "gesture-tap", "screenshot", "launch-app") \u2014 a TOOL, not a flow directive. A flow-file directive name ("tap", "launch", "run", "type", "await", "assert", "pinch", "echo", "wait", "long-press", "scroll-to", "snapshot", "when") is answered with guidance, and nothing runs or is recorded: most name the tool that records the directive, while "wait", "long-press", "scroll-to", "snapshot" and "when" have no recording tool at all and are answered with what to do instead. A recording tool (flow-add-step, flow-add-echo, flow-start-recording, flow-finish-recording) is refused the same way, each for its own reason \u2014 nesting one would erase this flow at replay, end the take, or write the step twice.'
146000
+ ),
145874
146001
  args: external_exports.string().optional().describe(
145875
146002
  `Tool arguments as a JSON string, e.g. '{"udid": "ABC", "x": 0.5, "y": 0.3}'. Omit for tools with no arguments.`
145876
146003
  ),
@@ -146070,6 +146197,77 @@ async function captureTapSelector(registry2, session, udid, point) {
146070
146197
  };
146071
146198
  }
146072
146199
  }
146200
+ async function activeFlowState(session) {
146201
+ if (session.persist === "host") {
146202
+ try {
146203
+ session.flow = parseFlow(await fs48.readFile(session.filePath, "utf8"));
146204
+ } catch (err) {
146205
+ return {
146206
+ stepCount: session.flow.steps.length,
146207
+ note: `The persisted flow could not be read and parsed (${err instanceof Error ? err.message : String(err)}); the step count is from the last valid in-memory snapshot.`
146208
+ };
146209
+ }
146210
+ }
146211
+ return { stepCount: session.flow.steps.length };
146212
+ }
146213
+ async function recordNothing(session, guidance) {
146214
+ const { stepCount, note } = await activeFlowState(session);
146215
+ return {
146216
+ message: `${guidance} Nothing was executed and no step was recorded.${note ? ` ${note}` : ""}`,
146217
+ toolResult: void 0,
146218
+ stepCount,
146219
+ savedTo: session.filePath
146220
+ };
146221
+ }
146222
+ var DIRECTIVE_COMMAND_HINTS = {
146223
+ tap: { tool: "gesture-tap", rewritten: true },
146224
+ launch: {
146225
+ tool: "restart-app",
146226
+ rewritten: true,
146227
+ rewriteCondition: "when it carries only the bundle id (a call with an extra arg, e.g. an Android `activity`, is kept as a raw `tool: restart-app` step to convert during polish)"
146228
+ },
146229
+ run: {
146230
+ tool: "flow-execute",
146231
+ rewritten: true,
146232
+ rewriteCondition: "when the target resolves as a sibling flow in this recording's folder \u2014 a `name` that does not is kept as a raw `tool: flow-execute` step, and so is every target in a REMOTE recording (`run:` composition is host-resolved, so the host cannot validate the client's siblings); a `flow_path` that is not a sibling is refused outright and records nothing"
146233
+ },
146234
+ type: { tool: "keyboard", rewritten: false },
146235
+ await: { tool: AWAIT_UI_ELEMENT_TOOL_ID, rewritten: false },
146236
+ assert: { tool: AWAIT_UI_ELEMENT_TOOL_ID, rewritten: false },
146237
+ pinch: { tool: "gesture-pinch", rewritten: false }
146238
+ };
146239
+ var NESTED_RECORDER_TOOLS = {
146240
+ "flow-add-echo": "`flow-add-echo` records a step itself, so it must be called DIRECTLY, not through flow-add-step \u2014 nesting it would write the echo AND a `tool: flow-add-echo` step that fails on every replay.",
146241
+ "flow-add-step": "flow-add-step cannot record itself. Pass the MCP tool you want to execute as `command`.",
146242
+ "flow-start-recording": "`flow-start-recording` truncates the flow it names. Recording it as a step would erase this flow at replay; call it directly when you want to start a recording.",
146243
+ "flow-finish-recording": "`flow-finish-recording` ends the recording, so it cannot also be a step in it. Call it directly when the walkthrough is complete."
146244
+ };
146245
+ function isToolNotFound(err, command) {
146246
+ return err instanceof ToolNotFoundError && err.toolId === command;
146247
+ }
146248
+ function directiveCommandHint(command) {
146249
+ if (command === "echo") {
146250
+ return `"echo" is a flow directive, not a tool. Call \`flow-add-echo\` DIRECTLY \u2014 not through flow-add-step, which would run it as a nested tool AND record a \`tool: flow-add-echo\` step that fails on every replay.`;
146251
+ }
146252
+ if (command === "wait") {
146253
+ return `"wait" is a flow directive, not a tool, and there is no tool that records one \u2014 a fixed sleep is not a readiness signal. Record the thing you are actually waiting for with \`${AWAIT_UI_ELEMENT_TOOL_ID}\` instead.`;
146254
+ }
146255
+ if (command === "long-press") {
146256
+ return `"long-press" is a flow directive, not a tool, and no tool records one \u2014 there is no gesture-long-press. Record the rest of the path, then add the \`long-press:\` step by hand during polish and prove it with the replay.`;
146257
+ }
146258
+ if (command === "scroll-to") {
146259
+ return `"scroll-to" is a flow directive, not a tool, and no tool records one \u2014 it SEARCHES, scrolling until the target is visible, which no single recorded gesture reproduces. Record the movement with \`gesture-swipe\` (\`gesture-scroll\` on chromium) if the path needs it, then add the \`scroll-to:\` step by hand during polish and prove it with the replay.`;
146260
+ }
146261
+ if (command === "snapshot") {
146262
+ return `"snapshot" is a flow directive, not a tool, and no tool records one \u2014 it compares the screen against a stored baseline, which \`screenshot-diff\` does not manage. Add the \`snapshot:\` step by hand during polish, then adopt its baseline with a run that sets updateBaselines, and review the PNG before committing it.`;
146263
+ }
146264
+ if (command === "when") {
146265
+ return `"when" is a flow directive, not a tool, and no tool records one \u2014 it GUARDS the steps nested under it, so there is no action of its own to run. Record those steps, then wrap them in the \`when:\` block by hand during polish and prove both branches with the replay.`;
146266
+ }
146267
+ const hint = Object.hasOwn(DIRECTIVE_COMMAND_HINTS, command) ? DIRECTIVE_COMMAND_HINTS[command] : void 0;
146268
+ if (!hint) return void 0;
146269
+ return `"${command}" is a flow directive, not a tool. Record it by calling \`${hint.tool}\` through flow-add-step` + (hint.rewritten ? ` \u2014 the recorder rewrites it into the \`${command}:\` step ${hint.rewriteCondition ?? "for you"}. Where the call is recorded at all, a \`delayMs\` on it opts out of the rewrite: the step is then kept in its raw \`tool: ${hint.tool}\` form (a replay delay has no directive form), so leave \`delayMs\` off if you want the \`${command}:\` step.` : `. It is stored as a raw \`tool: ${hint.tool}\` step; converting it to \`${command}:\` is part of the polish pass.`);
146270
+ }
146073
146271
  var RUN_TARGET_COMMAND = "flow-execute";
146074
146272
  async function rewriteSiblingFlowPath(session, args) {
146075
146273
  const flowPath = args.flow_path;
@@ -146187,12 +146385,12 @@ function createFlowAddStepTool(registry2) {
146187
146385
  // Name the flow: recordings are concurrent, so several of these lines can
146188
146386
  // interleave in one log and "the recorded flow" would not say which.
146189
146387
  startedMsg: ({ params }) => `Adding ${params.command} step to flow ${params.name}`,
146190
- completedMsg: ({ params }) => `Added ${params.command} step to flow ${params.name}`,
146388
+ completedMsg: ({ params, result }) => result.recorded === void 0 ? `Recorded no ${params.command} step in flow ${params.name}` : `Added ${params.command} step to flow ${params.name}`,
146191
146389
  failedMsg: ({ params, failureSignal: failureSignal2 }) => `Failed to add ${params.command} step to flow ${params.name}: ${failureSignal2.error_code}`
146192
146390
  },
146193
146391
  description: `Execute a tool call and record it as a step in the flow named by \`name\` + \`project_root\` (the recording must already be open \u2014 see flow-start-recording). Use when recording a flow and you want to run and capture each action. A coordinate \`gesture-tap\` is recorded as a portable \`tap: { selector }\` step when the tapped element has stable text/identifier (otherwise coordinates are kept with a warning); a \`restart-app\` is recorded as a \`launch\` step (record one FIRST to make the flow a self-contained e2e flow; restart-app has no chromium support, so a chromium flow records as a fragment \u2014 add the \`launch: { chromium: <app path> }\` line to the YAML afterward, deleting the executionPrerequisite line if one was recorded: a flow that starts with a launch must not declare it).
146194
146392
  A recorded \`await-ui-element\` that PASSED is re-probed against the tree the RUNNER resolves \`await:\`/\`assert:\` directives against, which is NOT the tree the live call read; a wait that came back \`{ success: false }\` is not probed at all, and its warning says so; when the condition does not hold there the step is still recorded and \`message\` carries a warning to read before converting \u2014 whether the conversion actually breaks depends on WHY the two disagree, since a screen that moved on between the live wait and the re-probe reads the same way. If that tree could not be read at all, the warning says so instead: the conversion is UNKNOWN, not known-bad. The probe judges the selector exactly as recorded, so write the conversion in the strict map spelling (\`{ visible: { text: Continue } }\`, copying the step's \`selector:\`) \u2014 the bare-string spelling (\`{ visible: Continue }\`) re-parses as a loose selector that resolves identifier-first and falls back to text, which is a different check. \`message\` also warns when the live wait itself came back \`{ success: false }\` \u2014 that tool reports a failed wait by returning rather than throwing, so the step is recorded either way. That warning names the cause, because only one of them judges the condition: a genuine miss will stop the run at replay, while a wait whose tree source was unreadable, or one that was cancelled, observed nothing and leaves the condition UNKNOWN.
146195
- Returns { message, toolResult, stepCount, recorded, savedTo } on success \u2014 \`message\` is \`Step added to "<name>" flow\` plus any warning about what was recorded (read it; a warning never means the step was skipped). If it fails an error is returned and nothing is recorded.
146393
+ Returns { message, toolResult, stepCount, recorded, savedTo } on success \u2014 \`message\` is \`Step added to "<name>" flow\` plus any warning about what was recorded (read it; a warning never means the step was skipped). If it fails an error is returned and nothing is recorded. Two calls SUCCEED while recording nothing, and omit \`recorded\` to say so: a \`command\` naming a recording tool, and one naming a flow-file directive rather than a tool. Both answer with what to do instead \u2014 usually the call to make (the tool that records that directive, or the recording tool called directly), but \`wait\`, \`long-press\`, \`scroll-to\`, \`snapshot\` and \`when\` have no recording tool, so those name no call and say what to record or add by hand in its place. Either way nothing runs at the device and the take is left untouched \u2014 read \`recorded\`, not the status, to know whether a step was appended.
146196
146394
  If a step was recorded by mistake, remove it from the .yaml after \`flow-finish-recording\` rather than during the recording: against a remote client the in-memory copy is authoritative and every write serializes it over your edit, and in host mode a mid-recording edit renumbers the steps, which costs the finish the cross-tree verdicts anchored to them.`,
146197
146395
  // The recorded tool RUNS here, so this call lasts as long as whatever it
146198
146396
  // wraps, and the three it most often wraps declare this too. Without it the
@@ -146204,7 +146402,19 @@ If a step was recorded by mistake, remove it from the .yaml after \`flow-finish-
146204
146402
  services: () => ({}),
146205
146403
  async execute(_services, params, ctx) {
146206
146404
  const session = await requireRecordingSession(params.project_root, params.name);
146207
- const args = params.args ? JSON.parse(params.args) : {};
146405
+ const nested = Object.hasOwn(NESTED_RECORDER_TOOLS, params.command) ? NESTED_RECORDER_TOOLS[params.command] : void 0;
146406
+ if (nested) return recordNothing(session, nested);
146407
+ let args;
146408
+ try {
146409
+ args = params.args ? JSON.parse(params.args) : {};
146410
+ } catch (err) {
146411
+ if (registry2.getTool(params.command) === void 0) {
146412
+ const hint = directiveCommandHint(params.command);
146413
+ if (hint) return recordNothing(session, hint);
146414
+ }
146415
+ throw err;
146416
+ }
146417
+ const authoredArgs = { ...args };
146208
146418
  if (params.command === RUN_TARGET_COMMAND) await rewriteSiblingFlowPath(session, args);
146209
146419
  const isTap = params.command === "gesture-tap" && params.delayMs === void 0 && typeof args.udid === "string" && typeof args.x === "number" && typeof args.y === "number";
146210
146420
  let captured;
@@ -146214,7 +146424,27 @@ If a step was recorded by mistake, remove it from the .yaml after \`flow-finish-
146214
146424
  y: args.y
146215
146425
  });
146216
146426
  }
146217
- const toolResult = await invokeSubTool(registry2, ctx, params.command, args);
146427
+ let toolResult;
146428
+ try {
146429
+ toolResult = await invokeSubTool(registry2, ctx, params.command, args);
146430
+ } catch (err) {
146431
+ const hint = isToolNotFound(err, params.command) ? directiveCommandHint(params.command) : void 0;
146432
+ if (hint) return recordNothing(session, hint);
146433
+ const reframed = describeNestedParamError(
146434
+ registry2,
146435
+ err,
146436
+ params.command,
146437
+ args,
146438
+ authoredArgs
146439
+ );
146440
+ if (reframed === void 0) throw err;
146441
+ throw new FailureError(reframed, {
146442
+ error_code: FAILURE_CODES.TOOL_INPUT_INVALID,
146443
+ failure_stage: "flow_add_step_nested_params",
146444
+ failure_area: "tool_server",
146445
+ error_kind: "validation"
146446
+ });
146447
+ }
146218
146448
  let waitWarning;
146219
146449
  if (params.command === AWAIT_UI_ELEMENT_TOOL_ID) {
146220
146450
  if (isUnmetUiWaitResult(params.command, toolResult)) {
@@ -148756,8 +148986,10 @@ var zodSchema65 = external_exports.object({
148756
148986
  if (params.name === void 0 === (params.flow_path === void 0)) {
148757
148987
  ctx.addIssue({
148758
148988
  code: external_exports.ZodIssueCode.custom,
148759
- message: "Pass exactly one flow source: name or flow_path.",
148760
- path: ["flow_path"]
148989
+ message: params.name !== void 0 ? "Pass exactly one flow source: name or flow_path." : "Pass exactly one flow source: name or flow_path. flow-execute needs the flow's name in `name` \u2014 it resolves <project_root>/.argent/flows/<name>.yaml.",
148990
+ // The ROOT, not `flow_path`: the rule spans both source fields, and a
148991
+ // path would prefix the message with "`flow_path`:".
148992
+ path: []
148761
148993
  });
148762
148994
  }
148763
148995
  });
@@ -149847,7 +150079,8 @@ async function execLeafStep(state3, step, index, scope) {
149847
150079
  }
149848
150080
  return { ...base, status: "pass", tool: step.name, result, outputHint, args };
149849
150081
  } catch (err) {
149850
- return { ...base, status: "error", tool: step.name, reason: errMsg3(err) };
150082
+ const reframed = describeNestedParamError(registry2, err, step.name, args, step.args ?? {});
150083
+ return { ...base, status: "error", tool: step.name, reason: reframed ?? errMsg3(err) };
149851
150084
  }
149852
150085
  }
149853
150086
  default:
@@ -149995,8 +150228,10 @@ var zodSchema66 = external_exports.object({
149995
150228
  if (params.name === void 0 === (params.flow_path === void 0)) {
149996
150229
  ctx.addIssue({
149997
150230
  code: external_exports.ZodIssueCode.custom,
149998
- message: "Pass exactly one flow source: name or flow_path.",
149999
- path: ["flow_path"]
150231
+ message: params.name !== void 0 ? "Pass exactly one flow source: name or flow_path." : "Pass exactly one flow source: name or flow_path. flow-read-prerequisite needs the flow's name in `name` \u2014 it resolves <project_root>/.argent/flows/<name>.yaml.",
150232
+ // The ROOT, matching flow-execute: the rule spans both source fields,
150233
+ // so it must not be anchored on one of them.
150234
+ path: []
150000
150235
  });
150001
150236
  }
150002
150237
  });
@@ -150028,7 +150263,7 @@ Use when you need to check what app/simulator state is required before executing
150028
150263
  source (name or flow_path) you will pass to flow-execute, so the prerequisite you read is the contract of
150029
150264
  the flow that will actually run.
150030
150265
  Fails if the flow file does not exist.
150031
- Address the flow exactly as you will address it in flow-execute: name or flow_path, one and only one; supplying both or neither is rejected.`,
150266
+ Address the flow exactly as you will address it in flow-execute: name or flow_path, one and only one; supplying both or neither is rejected. The name goes in \`name\`, which resolves <project_root>/.argent/flows/<name>.yaml.`,
150032
150267
  zodSchema: zodSchema66,
150033
150268
  fileInputs: fileInputs4,
150034
150269
  services: () => ({}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.22.1-next.5",
3
+ "version": "0.22.1-next.7",
4
4
  "mcpName": "io.github.software-mansion/argent",
5
5
  "description": "MCP server for iOS Simulator and Android Emulator control",
6
6
  "license": "Apache-2.0",