@amdahl/cli 0.4.0 → 0.4.2

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/amdahl.js CHANGED
@@ -18,9 +18,9 @@ var __commonJS = (cb, mod) => function __require2() {
18
18
  };
19
19
  var __copyProps = (to, from, except, desc) => {
20
20
  if (from && typeof from === "object" || typeof from === "function") {
21
- for (let key of __getOwnPropNames(from))
22
- if (!__hasOwnProp.call(to, key) && key !== except)
23
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ for (let key2 of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key2) && key2 !== except)
23
+ __defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
24
24
  }
25
25
  return to;
26
26
  };
@@ -110,11 +110,11 @@ var require_visit = __commonJS({
110
110
  visit.BREAK = BREAK;
111
111
  visit.SKIP = SKIP;
112
112
  visit.REMOVE = REMOVE;
113
- function visit_(key, node, visitor, path) {
114
- const ctrl = callVisitor(key, node, visitor, path);
113
+ function visit_(key2, node, visitor, path) {
114
+ const ctrl = callVisitor(key2, node, visitor, path);
115
115
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
116
- replaceNode(key, path, ctrl);
117
- return visit_(key, ctrl, visitor, path);
116
+ replaceNode(key2, path, ctrl);
117
+ return visit_(key2, ctrl, visitor, path);
118
118
  }
119
119
  if (typeof ctrl !== "symbol") {
120
120
  if (identity.isCollection(node)) {
@@ -158,11 +158,11 @@ var require_visit = __commonJS({
158
158
  visitAsync.BREAK = BREAK;
159
159
  visitAsync.SKIP = SKIP;
160
160
  visitAsync.REMOVE = REMOVE;
161
- async function visitAsync_(key, node, visitor, path) {
162
- const ctrl = await callVisitor(key, node, visitor, path);
161
+ async function visitAsync_(key2, node, visitor, path) {
162
+ const ctrl = await callVisitor(key2, node, visitor, path);
163
163
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
164
- replaceNode(key, path, ctrl);
165
- return visitAsync_(key, ctrl, visitor, path);
164
+ replaceNode(key2, path, ctrl);
165
+ return visitAsync_(key2, ctrl, visitor, path);
166
166
  }
167
167
  if (typeof ctrl !== "symbol") {
168
168
  if (identity.isCollection(node)) {
@@ -212,27 +212,27 @@ var require_visit = __commonJS({
212
212
  }
213
213
  return visitor;
214
214
  }
215
- function callVisitor(key, node, visitor, path) {
215
+ function callVisitor(key2, node, visitor, path) {
216
216
  if (typeof visitor === "function")
217
- return visitor(key, node, path);
217
+ return visitor(key2, node, path);
218
218
  if (identity.isMap(node))
219
- return visitor.Map?.(key, node, path);
219
+ return visitor.Map?.(key2, node, path);
220
220
  if (identity.isSeq(node))
221
- return visitor.Seq?.(key, node, path);
221
+ return visitor.Seq?.(key2, node, path);
222
222
  if (identity.isPair(node))
223
- return visitor.Pair?.(key, node, path);
223
+ return visitor.Pair?.(key2, node, path);
224
224
  if (identity.isScalar(node))
225
- return visitor.Scalar?.(key, node, path);
225
+ return visitor.Scalar?.(key2, node, path);
226
226
  if (identity.isAlias(node))
227
- return visitor.Alias?.(key, node, path);
227
+ return visitor.Alias?.(key2, node, path);
228
228
  return void 0;
229
229
  }
230
- function replaceNode(key, path, node) {
230
+ function replaceNode(key2, path, node) {
231
231
  const parent = path[path.length - 1];
232
232
  if (identity.isCollection(parent)) {
233
- parent.items[key] = node;
233
+ parent.items[key2] = node;
234
234
  } else if (identity.isPair(parent)) {
235
- if (key === "key")
235
+ if (key2 === "key")
236
236
  parent.key = node;
237
237
  else
238
238
  parent.value = node;
@@ -307,8 +307,8 @@ var require_directives = __commonJS({
307
307
  this.atNextDocument = false;
308
308
  }
309
309
  const parts = line.trim().split(/[ \t]+/);
310
- const name = parts.shift();
311
- switch (name) {
310
+ const name2 = parts.shift();
311
+ switch (name2) {
312
312
  case "%TAG": {
313
313
  if (parts.length !== 2) {
314
314
  onError(0, "%TAG directive should contain exactly two parts");
@@ -336,7 +336,7 @@ var require_directives = __commonJS({
336
336
  }
337
337
  }
338
338
  default:
339
- onError(0, `Unknown directive ${name}`, true);
339
+ onError(0, `Unknown directive ${name2}`, true);
340
340
  return false;
341
341
  }
342
342
  }
@@ -445,9 +445,9 @@ var require_anchors = __commonJS({
445
445
  }
446
446
  function findNewAnchor(prefix2, exclude) {
447
447
  for (let i = 1; true; ++i) {
448
- const name = `${prefix2}${i}`;
449
- if (!exclude.has(name))
450
- return name;
448
+ const name2 = `${prefix2}${i}`;
449
+ if (!exclude.has(name2))
450
+ return name2;
451
451
  }
452
452
  }
453
453
  function createNodeAnchors(doc, prefix2) {
@@ -493,7 +493,7 @@ var require_anchors = __commonJS({
493
493
  var require_applyReviver = __commonJS({
494
494
  "node_modules/.pnpm/yaml@2.9.1/node_modules/yaml/dist/doc/applyReviver.js"(exports) {
495
495
  "use strict";
496
- function applyReviver(reviver, obj, key, val) {
496
+ function applyReviver(reviver, obj, key2, val) {
497
497
  if (val && typeof val === "object") {
498
498
  if (Array.isArray(val)) {
499
499
  for (let i = 0, len = val.length; i < len; ++i) {
@@ -533,7 +533,7 @@ var require_applyReviver = __commonJS({
533
533
  }
534
534
  }
535
535
  }
536
- return reviver.call(obj, key, val);
536
+ return reviver.call(obj, key2, val);
537
537
  }
538
538
  exports.applyReviver = applyReviver;
539
539
  }
@@ -896,14 +896,14 @@ var require_Collection = __commonJS({
896
896
  if (isEmptyPath(path))
897
897
  this.add(value);
898
898
  else {
899
- const [key, ...rest] = path;
900
- const node = this.get(key, true);
899
+ const [key2, ...rest] = path;
900
+ const node = this.get(key2, true);
901
901
  if (identity.isCollection(node))
902
902
  node.addIn(rest, value);
903
903
  else if (node === void 0 && this.schema)
904
- this.set(key, collectionFromPath(this.schema, rest, value));
904
+ this.set(key2, collectionFromPath(this.schema, rest, value));
905
905
  else
906
- throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
906
+ throw new Error(`Expected YAML collection at ${key2}. Remaining path: ${rest}`);
907
907
  }
908
908
  }
909
909
  /**
@@ -911,14 +911,14 @@ var require_Collection = __commonJS({
911
911
  * @returns `true` if the item was found and removed.
912
912
  */
913
913
  deleteIn(path) {
914
- const [key, ...rest] = path;
914
+ const [key2, ...rest] = path;
915
915
  if (rest.length === 0)
916
- return this.delete(key);
917
- const node = this.get(key, true);
916
+ return this.delete(key2);
917
+ const node = this.get(key2, true);
918
918
  if (identity.isCollection(node))
919
919
  return node.deleteIn(rest);
920
920
  else
921
- throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
921
+ throw new Error(`Expected YAML collection at ${key2}. Remaining path: ${rest}`);
922
922
  }
923
923
  /**
924
924
  * Returns item at `key`, or `undefined` if not found. By default unwraps
@@ -926,8 +926,8 @@ var require_Collection = __commonJS({
926
926
  * `true` (collections are always returned intact).
927
927
  */
928
928
  getIn(path, keepScalar) {
929
- const [key, ...rest] = path;
930
- const node = this.get(key, true);
929
+ const [key2, ...rest] = path;
930
+ const node = this.get(key2, true);
931
931
  if (rest.length === 0)
932
932
  return !keepScalar && identity.isScalar(node) ? node.value : node;
933
933
  else
@@ -945,10 +945,10 @@ var require_Collection = __commonJS({
945
945
  * Checks if the collection includes a value with the key `key`.
946
946
  */
947
947
  hasIn(path) {
948
- const [key, ...rest] = path;
948
+ const [key2, ...rest] = path;
949
949
  if (rest.length === 0)
950
- return this.has(key);
951
- const node = this.get(key, true);
950
+ return this.has(key2);
951
+ const node = this.get(key2, true);
952
952
  return identity.isCollection(node) ? node.hasIn(rest) : false;
953
953
  }
954
954
  /**
@@ -956,17 +956,17 @@ var require_Collection = __commonJS({
956
956
  * boolean to add/remove the item from the set.
957
957
  */
958
958
  setIn(path, value) {
959
- const [key, ...rest] = path;
959
+ const [key2, ...rest] = path;
960
960
  if (rest.length === 0) {
961
- this.set(key, value);
961
+ this.set(key2, value);
962
962
  } else {
963
- const node = this.get(key, true);
963
+ const node = this.get(key2, true);
964
964
  if (identity.isCollection(node))
965
965
  node.setIn(rest, value);
966
966
  else if (node === void 0 && this.schema)
967
- this.set(key, collectionFromPath(this.schema, rest, value));
967
+ this.set(key2, collectionFromPath(this.schema, rest, value));
968
968
  else
969
- throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
969
+ throw new Error(`Expected YAML collection at ${key2}. Remaining path: ${rest}`);
970
970
  }
971
971
  }
972
972
  };
@@ -1484,8 +1484,8 @@ var require_stringify = __commonJS({
1484
1484
  tagObj = tags.find((t) => t.nodeClass && obj instanceof t.nodeClass);
1485
1485
  }
1486
1486
  if (!tagObj) {
1487
- const name = obj?.constructor?.name ?? (obj === null ? "null" : typeof obj);
1488
- throw new Error(`Tag not resolved for ${name} value`);
1487
+ const name2 = obj?.constructor?.name ?? (obj === null ? "null" : typeof obj);
1488
+ throw new Error(`Tag not resolved for ${name2} value`);
1489
1489
  }
1490
1490
  return tagObj;
1491
1491
  }
@@ -1544,19 +1544,19 @@ var require_stringifyPair = __commonJS({
1544
1544
  var Scalar = require_Scalar();
1545
1545
  var stringify = require_stringify();
1546
1546
  var stringifyComment = require_stringifyComment();
1547
- function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
1547
+ function stringifyPair({ key: key2, value }, ctx, onComment, onChompKeep) {
1548
1548
  const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx;
1549
- let keyComment = identity.isNode(key) && key.comment || null;
1549
+ let keyComment = identity.isNode(key2) && key2.comment || null;
1550
1550
  if (simpleKeys) {
1551
1551
  if (keyComment) {
1552
1552
  throw new Error("With simple keys, key nodes cannot have comments");
1553
1553
  }
1554
- if (identity.isCollection(key) || !identity.isNode(key) && typeof key === "object") {
1554
+ if (identity.isCollection(key2) || !identity.isNode(key2) && typeof key2 === "object") {
1555
1555
  const msg = "With simple keys, collection cannot be used as a key value";
1556
1556
  throw new Error(msg);
1557
1557
  }
1558
1558
  }
1559
- let explicitKey = !simpleKeys && (!key || keyComment && value == null && !ctx.inFlow || identity.isCollection(key) || (identity.isScalar(key) ? key.type === Scalar.Scalar.BLOCK_FOLDED || key.type === Scalar.Scalar.BLOCK_LITERAL : typeof key === "object"));
1559
+ let explicitKey = !simpleKeys && (!key2 || keyComment && value == null && !ctx.inFlow || identity.isCollection(key2) || (identity.isScalar(key2) ? key2.type === Scalar.Scalar.BLOCK_FOLDED || key2.type === Scalar.Scalar.BLOCK_LITERAL : typeof key2 === "object"));
1560
1560
  ctx = Object.assign({}, ctx, {
1561
1561
  allNullValues: false,
1562
1562
  implicitKey: !explicitKey && (simpleKeys || !allNullValues),
@@ -1564,7 +1564,7 @@ var require_stringifyPair = __commonJS({
1564
1564
  });
1565
1565
  let keyCommentDone = false;
1566
1566
  let chompKeep = false;
1567
- let str2 = stringify.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
1567
+ let str2 = stringify.stringify(key2, ctx, () => keyCommentDone = true, () => chompKeep = true);
1568
1568
  if (!explicitKey && !ctx.inFlow && str2.length > 1024) {
1569
1569
  if (simpleKeys)
1570
1570
  throw new Error("With simple keys, single line scalar must not span more than 1024 characters");
@@ -1708,7 +1708,7 @@ var require_merge = __commonJS({
1708
1708
  }),
1709
1709
  stringify: () => MERGE_KEY
1710
1710
  };
1711
- var isMergeKey = (ctx, key) => (merge.identify(key) || identity.isScalar(key) && (!key.type || key.type === Scalar.Scalar.PLAIN) && merge.identify(key.value)) && ctx?.doc.schema.tags.some((tag) => tag.tag === merge.tag && tag.default);
1711
+ var isMergeKey = (ctx, key2) => (merge.identify(key2) || identity.isScalar(key2) && (!key2.type || key2.type === Scalar.Scalar.PLAIN) && merge.identify(key2.value)) && ctx?.doc.schema.tags.some((tag) => tag.tag === merge.tag && tag.default);
1712
1712
  function addMergeToJSMap(ctx, map, value) {
1713
1713
  const source = resolveAliasValue(ctx, value);
1714
1714
  if (identity.isSeq(source))
@@ -1725,14 +1725,14 @@ var require_merge = __commonJS({
1725
1725
  if (!identity.isMap(source))
1726
1726
  throw new Error("Merge sources must be maps or map aliases");
1727
1727
  const srcMap = source.toJSON(null, ctx, Map);
1728
- for (const [key, value2] of srcMap) {
1728
+ for (const [key2, value2] of srcMap) {
1729
1729
  if (map instanceof Map) {
1730
- if (!map.has(key))
1731
- map.set(key, value2);
1730
+ if (!map.has(key2))
1731
+ map.set(key2, value2);
1732
1732
  } else if (map instanceof Set) {
1733
- map.add(key);
1734
- } else if (!Object.prototype.hasOwnProperty.call(map, key)) {
1735
- Object.defineProperty(map, key, {
1733
+ map.add(key2);
1734
+ } else if (!Object.prototype.hasOwnProperty.call(map, key2)) {
1735
+ Object.defineProperty(map, key2, {
1736
1736
  value: value2,
1737
1737
  writable: true,
1738
1738
  enumerable: true,
@@ -1760,19 +1760,19 @@ var require_addPairToJSMap = __commonJS({
1760
1760
  var stringify = require_stringify();
1761
1761
  var identity = require_identity();
1762
1762
  var toJS = require_toJS();
1763
- function addPairToJSMap(ctx, map, { key, value }) {
1764
- if (identity.isNode(key) && key.addToJSMap)
1765
- key.addToJSMap(ctx, map, value);
1766
- else if (merge.isMergeKey(ctx, key))
1763
+ function addPairToJSMap(ctx, map, { key: key2, value }) {
1764
+ if (identity.isNode(key2) && key2.addToJSMap)
1765
+ key2.addToJSMap(ctx, map, value);
1766
+ else if (merge.isMergeKey(ctx, key2))
1767
1767
  merge.addMergeToJSMap(ctx, map, value);
1768
1768
  else {
1769
- const jsKey = toJS.toJS(key, "", ctx);
1769
+ const jsKey = toJS.toJS(key2, "", ctx);
1770
1770
  if (map instanceof Map) {
1771
1771
  map.set(jsKey, toJS.toJS(value, jsKey, ctx));
1772
1772
  } else if (map instanceof Set) {
1773
1773
  map.add(jsKey);
1774
1774
  } else {
1775
- const stringKey = stringifyKey(key, jsKey, ctx);
1775
+ const stringKey = stringifyKey(key2, jsKey, ctx);
1776
1776
  const jsValue = toJS.toJS(value, stringKey, ctx);
1777
1777
  if (stringKey in map)
1778
1778
  Object.defineProperty(map, stringKey, {
@@ -1787,19 +1787,19 @@ var require_addPairToJSMap = __commonJS({
1787
1787
  }
1788
1788
  return map;
1789
1789
  }
1790
- function stringifyKey(key, jsKey, ctx) {
1790
+ function stringifyKey(key2, jsKey, ctx) {
1791
1791
  if (jsKey === null)
1792
1792
  return "";
1793
1793
  if (typeof jsKey !== "object")
1794
1794
  return String(jsKey);
1795
- if (identity.isNode(key) && ctx?.doc) {
1795
+ if (identity.isNode(key2) && ctx?.doc) {
1796
1796
  const strCtx = stringify.createStringifyContext(ctx.doc, {});
1797
1797
  strCtx.anchors = /* @__PURE__ */ new Set();
1798
1798
  for (const node of ctx.anchors.keys())
1799
1799
  strCtx.anchors.add(node.anchor);
1800
1800
  strCtx.inFlow = true;
1801
1801
  strCtx.inStringifyKey = true;
1802
- const strKey = key.toString(strCtx);
1802
+ const strKey = key2.toString(strCtx);
1803
1803
  if (!ctx.mapKeyWarned) {
1804
1804
  let jsonStr = JSON.stringify(strKey);
1805
1805
  if (jsonStr.length > 40)
@@ -1823,24 +1823,24 @@ var require_Pair = __commonJS({
1823
1823
  var stringifyPair = require_stringifyPair();
1824
1824
  var addPairToJSMap = require_addPairToJSMap();
1825
1825
  var identity = require_identity();
1826
- function createPair(key, value, ctx) {
1827
- const k = createNode.createNode(key, void 0, ctx);
1826
+ function createPair(key2, value, ctx) {
1827
+ const k = createNode.createNode(key2, void 0, ctx);
1828
1828
  const v = createNode.createNode(value, void 0, ctx);
1829
1829
  return new Pair(k, v);
1830
1830
  }
1831
1831
  var Pair = class _Pair {
1832
- constructor(key, value = null) {
1832
+ constructor(key2, value = null) {
1833
1833
  Object.defineProperty(this, identity.NODE_TYPE, { value: identity.PAIR });
1834
- this.key = key;
1834
+ this.key = key2;
1835
1835
  this.value = value;
1836
1836
  }
1837
1837
  clone(schema) {
1838
- let { key, value } = this;
1839
- if (identity.isNode(key))
1840
- key = key.clone(schema);
1838
+ let { key: key2, value } = this;
1839
+ if (identity.isNode(key2))
1840
+ key2 = key2.clone(schema);
1841
1841
  if (identity.isNode(value))
1842
1842
  value = value.clone(schema);
1843
- return new _Pair(key, value);
1843
+ return new _Pair(key2, value);
1844
1844
  }
1845
1845
  toJSON(_, ctx) {
1846
1846
  const pair = ctx?.mapAsMap ? /* @__PURE__ */ new Map() : {};
@@ -2016,11 +2016,11 @@ var require_YAMLMap = __commonJS({
2016
2016
  var identity = require_identity();
2017
2017
  var Pair = require_Pair();
2018
2018
  var Scalar = require_Scalar();
2019
- function findPair(items, key) {
2020
- const k = identity.isScalar(key) ? key.value : key;
2019
+ function findPair(items, key2) {
2020
+ const k = identity.isScalar(key2) ? key2.value : key2;
2021
2021
  for (const it of items) {
2022
2022
  if (identity.isPair(it)) {
2023
- if (it.key === key || it.key === k)
2023
+ if (it.key === key2 || it.key === k)
2024
2024
  return it;
2025
2025
  if (identity.isScalar(it.key) && it.key.value === k)
2026
2026
  return it;
@@ -2043,20 +2043,20 @@ var require_YAMLMap = __commonJS({
2043
2043
  static from(schema, obj, ctx) {
2044
2044
  const { keepUndefined, replacer } = ctx;
2045
2045
  const map = new this(schema);
2046
- const add = (key, value) => {
2046
+ const add = (key2, value) => {
2047
2047
  if (typeof replacer === "function")
2048
- value = replacer.call(obj, key, value);
2049
- else if (Array.isArray(replacer) && !replacer.includes(key))
2048
+ value = replacer.call(obj, key2, value);
2049
+ else if (Array.isArray(replacer) && !replacer.includes(key2))
2050
2050
  return;
2051
2051
  if (value !== void 0 || keepUndefined)
2052
- map.items.push(Pair.createPair(key, value, ctx));
2052
+ map.items.push(Pair.createPair(key2, value, ctx));
2053
2053
  };
2054
2054
  if (obj instanceof Map) {
2055
- for (const [key, value] of obj)
2056
- add(key, value);
2055
+ for (const [key2, value] of obj)
2056
+ add(key2, value);
2057
2057
  } else if (obj && typeof obj === "object") {
2058
- for (const key of Object.keys(obj))
2059
- add(key, obj[key]);
2058
+ for (const key2 of Object.keys(obj))
2059
+ add(key2, obj[key2]);
2060
2060
  }
2061
2061
  if (typeof schema.sortMapEntries === "function") {
2062
2062
  map.items.sort(schema.sortMapEntries);
@@ -2096,23 +2096,23 @@ var require_YAMLMap = __commonJS({
2096
2096
  this.items.push(_pair);
2097
2097
  }
2098
2098
  }
2099
- delete(key) {
2100
- const it = findPair(this.items, key);
2099
+ delete(key2) {
2100
+ const it = findPair(this.items, key2);
2101
2101
  if (!it)
2102
2102
  return false;
2103
2103
  const del = this.items.splice(this.items.indexOf(it), 1);
2104
2104
  return del.length > 0;
2105
2105
  }
2106
- get(key, keepScalar) {
2107
- const it = findPair(this.items, key);
2106
+ get(key2, keepScalar) {
2107
+ const it = findPair(this.items, key2);
2108
2108
  const node = it?.value;
2109
2109
  return (!keepScalar && identity.isScalar(node) ? node.value : node) ?? void 0;
2110
2110
  }
2111
- has(key) {
2112
- return !!findPair(this.items, key);
2111
+ has(key2) {
2112
+ return !!findPair(this.items, key2);
2113
2113
  }
2114
- set(key, value) {
2115
- this.add(new Pair.Pair(key, value), true);
2114
+ set(key2, value) {
2115
+ this.add(new Pair.Pair(key2, value), true);
2116
2116
  }
2117
2117
  /**
2118
2118
  * @param ctx - Conversion context, originally set in Document#toJS()
@@ -2201,15 +2201,15 @@ var require_YAMLSeq = __commonJS({
2201
2201
  *
2202
2202
  * @returns `true` if the item was found and removed.
2203
2203
  */
2204
- delete(key) {
2205
- const idx = asItemIndex(key);
2204
+ delete(key2) {
2205
+ const idx = asItemIndex(key2);
2206
2206
  if (typeof idx !== "number")
2207
2207
  return false;
2208
2208
  const del = this.items.splice(idx, 1);
2209
2209
  return del.length > 0;
2210
2210
  }
2211
- get(key, keepScalar) {
2212
- const idx = asItemIndex(key);
2211
+ get(key2, keepScalar) {
2212
+ const idx = asItemIndex(key2);
2213
2213
  if (typeof idx !== "number")
2214
2214
  return void 0;
2215
2215
  const it = this.items[idx];
@@ -2221,8 +2221,8 @@ var require_YAMLSeq = __commonJS({
2221
2221
  * `key` must contain a representation of an integer for this to succeed.
2222
2222
  * It may be wrapped in a `Scalar`.
2223
2223
  */
2224
- has(key) {
2225
- const idx = asItemIndex(key);
2224
+ has(key2) {
2225
+ const idx = asItemIndex(key2);
2226
2226
  return typeof idx === "number" && idx < this.items.length;
2227
2227
  }
2228
2228
  /**
@@ -2232,10 +2232,10 @@ var require_YAMLSeq = __commonJS({
2232
2232
  * If `key` does not contain a representation of an integer, this will throw.
2233
2233
  * It may be wrapped in a `Scalar`.
2234
2234
  */
2235
- set(key, value) {
2236
- const idx = asItemIndex(key);
2235
+ set(key2, value) {
2236
+ const idx = asItemIndex(key2);
2237
2237
  if (typeof idx !== "number")
2238
- throw new Error(`Expected a valid index, not ${key}.`);
2238
+ throw new Error(`Expected a valid index, not ${key2}.`);
2239
2239
  const prev = this.items[idx];
2240
2240
  if (identity.isScalar(prev) && Scalar.isScalarValue(value))
2241
2241
  prev.value = value;
@@ -2269,8 +2269,8 @@ var require_YAMLSeq = __commonJS({
2269
2269
  let i = 0;
2270
2270
  for (let it of obj) {
2271
2271
  if (typeof replacer === "function") {
2272
- const key = obj instanceof Set ? it : String(i++);
2273
- it = replacer.call(obj, key, it);
2272
+ const key2 = obj instanceof Set ? it : String(i++);
2273
+ it = replacer.call(obj, key2, it);
2274
2274
  }
2275
2275
  seq.items.push(createNode.createNode(it, void 0, ctx));
2276
2276
  }
@@ -2278,8 +2278,8 @@ var require_YAMLSeq = __commonJS({
2278
2278
  return seq;
2279
2279
  }
2280
2280
  };
2281
- function asItemIndex(key) {
2282
- let idx = identity.isScalar(key) ? key.value : key;
2281
+ function asItemIndex(key2) {
2282
+ let idx = identity.isScalar(key2) ? key2.value : key2;
2283
2283
  if (idx && typeof idx === "string")
2284
2284
  idx = Number(idx);
2285
2285
  return typeof idx === "number" && Number.isInteger(idx) && idx >= 0 ? idx : null;
@@ -2693,25 +2693,25 @@ ${cn.comment}` : item.comment;
2693
2693
  for (let it of iterable) {
2694
2694
  if (typeof replacer === "function")
2695
2695
  it = replacer.call(iterable, String(i++), it);
2696
- let key, value;
2696
+ let key2, value;
2697
2697
  if (Array.isArray(it)) {
2698
2698
  if (it.length === 2) {
2699
- key = it[0];
2699
+ key2 = it[0];
2700
2700
  value = it[1];
2701
2701
  } else
2702
2702
  throw new TypeError(`Expected [key, value] tuple: ${it}`);
2703
2703
  } else if (it && it instanceof Object) {
2704
2704
  const keys = Object.keys(it);
2705
2705
  if (keys.length === 1) {
2706
- key = keys[0];
2707
- value = it[key];
2706
+ key2 = keys[0];
2707
+ value = it[key2];
2708
2708
  } else {
2709
2709
  throw new TypeError(`Expected tuple with one key, not ${keys.length} keys`);
2710
2710
  }
2711
2711
  } else {
2712
- key = it;
2712
+ key2 = it;
2713
2713
  }
2714
- pairs2.items.push(Pair.createPair(key, value, ctx));
2714
+ pairs2.items.push(Pair.createPair(key2, value, ctx));
2715
2715
  }
2716
2716
  return pairs2;
2717
2717
  }
@@ -2758,16 +2758,16 @@ var require_omap = __commonJS({
2758
2758
  if (ctx?.onCreate)
2759
2759
  ctx.onCreate(map);
2760
2760
  for (const pair of this.items) {
2761
- let key, value;
2761
+ let key2, value;
2762
2762
  if (identity.isPair(pair)) {
2763
- key = toJS.toJS(pair.key, "", ctx);
2764
- value = toJS.toJS(pair.value, key, ctx);
2763
+ key2 = toJS.toJS(pair.key, "", ctx);
2764
+ value = toJS.toJS(pair.value, key2, ctx);
2765
2765
  } else {
2766
- key = toJS.toJS(pair, "", ctx);
2766
+ key2 = toJS.toJS(pair, "", ctx);
2767
2767
  }
2768
- if (map.has(key))
2768
+ if (map.has(key2))
2769
2769
  throw new Error("Ordered maps must not include duplicate keys");
2770
- map.set(key, value);
2770
+ map.set(key2, value);
2771
2771
  }
2772
2772
  return map;
2773
2773
  }
@@ -2788,12 +2788,12 @@ var require_omap = __commonJS({
2788
2788
  resolve(seq, onError) {
2789
2789
  const pairs$1 = pairs.resolvePairs(seq, onError);
2790
2790
  const seenKeys = [];
2791
- for (const { key } of pairs$1.items) {
2792
- if (identity.isScalar(key)) {
2793
- if (seenKeys.includes(key.value)) {
2794
- onError(`Ordered maps must not include duplicate keys: ${key.value}`);
2791
+ for (const { key: key2 } of pairs$1.items) {
2792
+ if (identity.isScalar(key2)) {
2793
+ if (seenKeys.includes(key2.value)) {
2794
+ onError(`Ordered maps must not include duplicate keys: ${key2.value}`);
2795
2795
  } else {
2796
- seenKeys.push(key.value);
2796
+ seenKeys.push(key2.value);
2797
2797
  }
2798
2798
  }
2799
2799
  }
@@ -2978,14 +2978,14 @@ var require_set = __commonJS({
2978
2978
  super(schema);
2979
2979
  this.tag = _YAMLSet.tag;
2980
2980
  }
2981
- add(key) {
2981
+ add(key2) {
2982
2982
  let pair;
2983
- if (identity.isPair(key))
2984
- pair = key;
2985
- else if (key && typeof key === "object" && "key" in key && "value" in key && key.value === null)
2986
- pair = new Pair.Pair(key.key, null);
2983
+ if (identity.isPair(key2))
2984
+ pair = key2;
2985
+ else if (key2 && typeof key2 === "object" && "key" in key2 && "value" in key2 && key2.value === null)
2986
+ pair = new Pair.Pair(key2.key, null);
2987
2987
  else
2988
- pair = new Pair.Pair(key, null);
2988
+ pair = new Pair.Pair(key2, null);
2989
2989
  const prev = YAMLMap.findPair(this.items, pair.key);
2990
2990
  if (!prev)
2991
2991
  this.items.push(pair);
@@ -2994,18 +2994,18 @@ var require_set = __commonJS({
2994
2994
  * If `keepPair` is `true`, returns the Pair matching `key`.
2995
2995
  * Otherwise, returns the value of that Pair's key.
2996
2996
  */
2997
- get(key, keepPair) {
2998
- const pair = YAMLMap.findPair(this.items, key);
2997
+ get(key2, keepPair) {
2998
+ const pair = YAMLMap.findPair(this.items, key2);
2999
2999
  return !keepPair && identity.isPair(pair) ? identity.isScalar(pair.key) ? pair.key.value : pair.key : pair;
3000
3000
  }
3001
- set(key, value) {
3001
+ set(key2, value) {
3002
3002
  if (typeof value !== "boolean")
3003
3003
  throw new Error(`Expected boolean value for set(key, value) in a YAML set, not ${typeof value}`);
3004
- const prev = YAMLMap.findPair(this.items, key);
3004
+ const prev = YAMLMap.findPair(this.items, key2);
3005
3005
  if (prev && !value) {
3006
3006
  this.items.splice(this.items.indexOf(prev), 1);
3007
3007
  } else if (!prev && value) {
3008
- this.items.push(new Pair.Pair(key));
3008
+ this.items.push(new Pair.Pair(key2));
3009
3009
  }
3010
3010
  }
3011
3011
  toJSON(_, ctx) {
@@ -3252,7 +3252,7 @@ var require_tags = __commonJS({
3252
3252
  if (Array.isArray(customTags))
3253
3253
  tags = [];
3254
3254
  else {
3255
- const keys = Array.from(schemas.keys()).filter((key) => key !== "yaml11").map((key) => JSON.stringify(key)).join(", ");
3255
+ const keys = Array.from(schemas.keys()).filter((key2) => key2 !== "yaml11").map((key2) => JSON.stringify(key2)).join(", ");
3256
3256
  throw new Error(`Unknown schema "${schemaName}"; use one of ${keys} or define customTags array`);
3257
3257
  }
3258
3258
  }
@@ -3268,7 +3268,7 @@ var require_tags = __commonJS({
3268
3268
  const tagObj = typeof tag === "string" ? tagsByName[tag] : tag;
3269
3269
  if (!tagObj) {
3270
3270
  const tagName = JSON.stringify(tag);
3271
- const keys = Object.keys(tagsByName).map((key) => JSON.stringify(key)).join(", ");
3271
+ const keys = Object.keys(tagsByName).map((key2) => JSON.stringify(key2)).join(", ");
3272
3272
  throw new Error(`Unknown custom tag ${tagName}; use one of ${keys}`);
3273
3273
  }
3274
3274
  if (!tags2.includes(tagObj))
@@ -3484,11 +3484,11 @@ var require_Document = __commonJS({
3484
3484
  * `name` will be used as a prefix for a new unique anchor.
3485
3485
  * If `name` is undefined, the generated anchor will use 'a' as a prefix.
3486
3486
  */
3487
- createAlias(node, name) {
3487
+ createAlias(node, name2) {
3488
3488
  if (!node.anchor) {
3489
3489
  const prev = anchors.anchorNames(this);
3490
3490
  node.anchor = // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
3491
- !name || prev.has(name) ? anchors.findNewAnchor(name || "a", prev) : name;
3491
+ !name2 || prev.has(name2) ? anchors.findNewAnchor(name2 || "a", prev) : name2;
3492
3492
  }
3493
3493
  return new Alias.Alias(node.anchor);
3494
3494
  }
@@ -3532,8 +3532,8 @@ var require_Document = __commonJS({
3532
3532
  * Convert a key and a value into a `Pair` using the current schema,
3533
3533
  * recursively wrapping all values as `Scalar` or `Collection` nodes.
3534
3534
  */
3535
- createPair(key, value, options = {}) {
3536
- const k = this.createNode(key, null, options);
3535
+ createPair(key2, value, options = {}) {
3536
+ const k = this.createNode(key2, null, options);
3537
3537
  const v = this.createNode(value, null, options);
3538
3538
  return new Pair.Pair(k, v);
3539
3539
  }
@@ -3541,8 +3541,8 @@ var require_Document = __commonJS({
3541
3541
  * Removes a value from the document.
3542
3542
  * @returns `true` if the item was found and removed.
3543
3543
  */
3544
- delete(key) {
3545
- return assertCollection(this.contents) ? this.contents.delete(key) : false;
3544
+ delete(key2) {
3545
+ return assertCollection(this.contents) ? this.contents.delete(key2) : false;
3546
3546
  }
3547
3547
  /**
3548
3548
  * Removes a value from the document.
@@ -3562,8 +3562,8 @@ var require_Document = __commonJS({
3562
3562
  * scalar values from their surrounding node; to disable set `keepScalar` to
3563
3563
  * `true` (collections are always returned intact).
3564
3564
  */
3565
- get(key, keepScalar) {
3566
- return identity.isCollection(this.contents) ? this.contents.get(key, keepScalar) : void 0;
3565
+ get(key2, keepScalar) {
3566
+ return identity.isCollection(this.contents) ? this.contents.get(key2, keepScalar) : void 0;
3567
3567
  }
3568
3568
  /**
3569
3569
  * Returns item at `path`, or `undefined` if not found. By default unwraps
@@ -3578,8 +3578,8 @@ var require_Document = __commonJS({
3578
3578
  /**
3579
3579
  * Checks if the document includes a value with the key `key`.
3580
3580
  */
3581
- has(key) {
3582
- return identity.isCollection(this.contents) ? this.contents.has(key) : false;
3581
+ has(key2) {
3582
+ return identity.isCollection(this.contents) ? this.contents.has(key2) : false;
3583
3583
  }
3584
3584
  /**
3585
3585
  * Checks if the document includes a value at `path`.
@@ -3593,11 +3593,11 @@ var require_Document = __commonJS({
3593
3593
  * Sets a value in this document. For `!!set`, `value` needs to be a
3594
3594
  * boolean to add/remove the item from the set.
3595
3595
  */
3596
- set(key, value) {
3596
+ set(key2, value) {
3597
3597
  if (this.contents == null) {
3598
- this.contents = Collection.collectionFromPath(this.schema, [key], value);
3598
+ this.contents = Collection.collectionFromPath(this.schema, [key2], value);
3599
3599
  } else if (assertCollection(this.contents)) {
3600
- this.contents.set(key, value);
3600
+ this.contents.set(key2, value);
3601
3601
  }
3602
3602
  }
3603
3603
  /**
@@ -3707,9 +3707,9 @@ var require_errors = __commonJS({
3707
3707
  "node_modules/.pnpm/yaml@2.9.1/node_modules/yaml/dist/errors.js"(exports) {
3708
3708
  "use strict";
3709
3709
  var YAMLError = class extends Error {
3710
- constructor(name, pos, code, message2) {
3710
+ constructor(name2, pos, code, message2) {
3711
3711
  super();
3712
- this.name = name;
3712
+ this.name = name2;
3713
3713
  this.code = code;
3714
3714
  this.message = message2;
3715
3715
  this.pos = pos;
@@ -3905,24 +3905,24 @@ var require_resolve_props = __commonJS({
3905
3905
  var require_util_contains_newline = __commonJS({
3906
3906
  "node_modules/.pnpm/yaml@2.9.1/node_modules/yaml/dist/compose/util-contains-newline.js"(exports) {
3907
3907
  "use strict";
3908
- function containsNewline(key) {
3909
- if (!key)
3908
+ function containsNewline(key2) {
3909
+ if (!key2)
3910
3910
  return null;
3911
- switch (key.type) {
3911
+ switch (key2.type) {
3912
3912
  case "alias":
3913
3913
  case "scalar":
3914
3914
  case "double-quoted-scalar":
3915
3915
  case "single-quoted-scalar":
3916
- if (key.source.includes("\n"))
3916
+ if (key2.source.includes("\n"))
3917
3917
  return true;
3918
- if (key.end) {
3919
- for (const st of key.end)
3918
+ if (key2.end) {
3919
+ for (const st of key2.end)
3920
3920
  if (st.type === "newline")
3921
3921
  return true;
3922
3922
  }
3923
3923
  return false;
3924
3924
  case "flow-collection":
3925
- for (const it of key.items) {
3925
+ for (const it of key2.items) {
3926
3926
  for (const st of it.start)
3927
3927
  if (st.type === "newline")
3928
3928
  return true;
@@ -3996,10 +3996,10 @@ var require_resolve_block_map = __commonJS({
3996
3996
  let offset = bm.offset;
3997
3997
  let commentEnd = null;
3998
3998
  for (const collItem of bm.items) {
3999
- const { start, key, sep: sep2, value } = collItem;
3999
+ const { start, key: key2, sep: sep2, value } = collItem;
4000
4000
  const keyProps = resolveProps.resolveProps(start, {
4001
4001
  indicator: "explicit-key-ind",
4002
- next: key ?? sep2?.[0],
4002
+ next: key2 ?? sep2?.[0],
4003
4003
  offset,
4004
4004
  onError,
4005
4005
  parentIndent: bm.indent,
@@ -4007,10 +4007,10 @@ var require_resolve_block_map = __commonJS({
4007
4007
  });
4008
4008
  const implicitKey = !keyProps.found;
4009
4009
  if (implicitKey) {
4010
- if (key) {
4011
- if (key.type === "block-seq")
4010
+ if (key2) {
4011
+ if (key2.type === "block-seq")
4012
4012
  onError(offset, "BLOCK_AS_IMPLICIT_KEY", "A block sequence may not be used as an implicit map key");
4013
- else if ("indent" in key && key.indent !== bm.indent)
4013
+ else if ("indent" in key2 && key2.indent !== bm.indent)
4014
4014
  onError(offset, "BAD_INDENT", startColMsg);
4015
4015
  }
4016
4016
  if (!keyProps.anchor && !keyProps.tag && !sep2) {
@@ -4023,17 +4023,17 @@ var require_resolve_block_map = __commonJS({
4023
4023
  }
4024
4024
  continue;
4025
4025
  }
4026
- if (keyProps.newlineAfterProp || utilContainsNewline.containsNewline(key)) {
4027
- onError(key ?? start[start.length - 1], "MULTILINE_IMPLICIT_KEY", "Implicit keys need to be on a single line");
4026
+ if (keyProps.newlineAfterProp || utilContainsNewline.containsNewline(key2)) {
4027
+ onError(key2 ?? start[start.length - 1], "MULTILINE_IMPLICIT_KEY", "Implicit keys need to be on a single line");
4028
4028
  }
4029
4029
  } else if (keyProps.found?.indent !== bm.indent) {
4030
4030
  onError(offset, "BAD_INDENT", startColMsg);
4031
4031
  }
4032
4032
  ctx.atKey = true;
4033
4033
  const keyStart = keyProps.end;
4034
- const keyNode = key ? composeNode(ctx, key, keyProps, onError) : composeEmptyNode(ctx, keyStart, start, null, keyProps, onError);
4034
+ const keyNode = key2 ? composeNode(ctx, key2, keyProps, onError) : composeEmptyNode(ctx, keyStart, start, null, keyProps, onError);
4035
4035
  if (ctx.schema.compat)
4036
- utilFlowIndentCheck.flowIndentCheck(bm.indent, key, onError);
4036
+ utilFlowIndentCheck.flowIndentCheck(bm.indent, key2, onError);
4037
4037
  ctx.atKey = false;
4038
4038
  if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
4039
4039
  onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
@@ -4043,7 +4043,7 @@ var require_resolve_block_map = __commonJS({
4043
4043
  offset: keyNode.range[2],
4044
4044
  onError,
4045
4045
  parentIndent: bm.indent,
4046
- startOnNewline: !key || key.type === "block-scalar"
4046
+ startOnNewline: !key2 || key2.type === "block-scalar"
4047
4047
  });
4048
4048
  offset = valueProps.end;
4049
4049
  if (valueProps.found) {
@@ -4207,11 +4207,11 @@ var require_resolve_flow_collection = __commonJS({
4207
4207
  let offset = fc.offset + fc.start.source.length;
4208
4208
  for (let i = 0; i < fc.items.length; ++i) {
4209
4209
  const collItem = fc.items[i];
4210
- const { start, key, sep: sep2, value } = collItem;
4210
+ const { start, key: key2, sep: sep2, value } = collItem;
4211
4211
  const props = resolveProps.resolveProps(start, {
4212
4212
  flow: fcName,
4213
4213
  indicator: "explicit-key-ind",
4214
- next: key ?? sep2?.[0],
4214
+ next: key2 ?? sep2?.[0],
4215
4215
  offset,
4216
4216
  onError,
4217
4217
  parentIndent: fc.indent,
@@ -4232,9 +4232,9 @@ var require_resolve_flow_collection = __commonJS({
4232
4232
  offset = props.end;
4233
4233
  continue;
4234
4234
  }
4235
- if (!isMap && ctx.options.strict && utilContainsNewline.containsNewline(key))
4235
+ if (!isMap && ctx.options.strict && utilContainsNewline.containsNewline(key2))
4236
4236
  onError(
4237
- key,
4237
+ key2,
4238
4238
  // checked by containsNewline()
4239
4239
  "MULTILINE_IMPLICIT_KEY",
4240
4240
  "Implicit keys of flow sequence pairs need to be on a single line"
@@ -4281,8 +4281,8 @@ var require_resolve_flow_collection = __commonJS({
4281
4281
  } else {
4282
4282
  ctx.atKey = true;
4283
4283
  const keyStart = props.end;
4284
- const keyNode = key ? composeNode(ctx, key, props, onError) : composeEmptyNode(ctx, keyStart, start, null, props, onError);
4285
- if (isBlock(key))
4284
+ const keyNode = key2 ? composeNode(ctx, key2, props, onError) : composeEmptyNode(ctx, keyStart, start, null, props, onError);
4285
+ if (isBlock(key2))
4286
4286
  onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
4287
4287
  ctx.atKey = false;
4288
4288
  const valueProps = resolveProps.resolveProps(sep2 ?? [], {
@@ -4349,8 +4349,8 @@ var require_resolve_flow_collection = __commonJS({
4349
4349
  if (ce?.source === expectedEnd)
4350
4350
  cePos = ce.offset + ce.source.length;
4351
4351
  else {
4352
- const name = fcName[0].toUpperCase() + fcName.substring(1);
4353
- const msg = atRoot ? `${name} must end with a ${expectedEnd}` : `${name} in block collection must be sufficiently indented and end with a ${expectedEnd}`;
4352
+ const name2 = fcName[0].toUpperCase() + fcName.substring(1);
4353
+ const msg = atRoot ? `${name2} must end with a ${expectedEnd}` : `${name2} in block collection must be sufficiently indented and end with a ${expectedEnd}`;
4354
4354
  onError(offset, atRoot ? "MISSING_CHAR" : "BAD_INDENT", msg);
4355
4355
  if (ce && ce.source.length !== 1)
4356
4356
  ee.unshift(ce);
@@ -5432,9 +5432,9 @@ var require_cst_scalar = __commonJS({
5432
5432
  ];
5433
5433
  if (!addEndtoBlockProps(props, "end" in token ? token.end : void 0))
5434
5434
  props.push({ type: "newline", offset: -1, indent, source: "\n" });
5435
- for (const key of Object.keys(token))
5436
- if (key !== "type" && key !== "offset")
5437
- delete token[key];
5435
+ for (const key2 of Object.keys(token))
5436
+ if (key2 !== "type" && key2 !== "offset")
5437
+ delete token[key2];
5438
5438
  Object.assign(token, { type: "block-scalar", indent, props, source: body });
5439
5439
  }
5440
5440
  }
@@ -5482,9 +5482,9 @@ var require_cst_scalar = __commonJS({
5482
5482
  default: {
5483
5483
  const indent = "indent" in token ? token.indent : -1;
5484
5484
  const end = "end" in token && Array.isArray(token.end) ? token.end.filter((st) => st.type === "space" || st.type === "comment" || st.type === "newline") : [];
5485
- for (const key of Object.keys(token))
5486
- if (key !== "type" && key !== "offset")
5487
- delete token[key];
5485
+ for (const key2 of Object.keys(token))
5486
+ if (key2 !== "type" && key2 !== "offset")
5487
+ delete token[key2];
5488
5488
  Object.assign(token, { type, indent, source, end });
5489
5489
  }
5490
5490
  }
@@ -5539,12 +5539,12 @@ var require_cst_stringify = __commonJS({
5539
5539
  }
5540
5540
  }
5541
5541
  }
5542
- function stringifyItem({ start, key, sep: sep2, value }) {
5542
+ function stringifyItem({ start, key: key2, sep: sep2, value }) {
5543
5543
  let res = "";
5544
5544
  for (const st of start)
5545
5545
  res += st.source;
5546
- if (key)
5547
- res += stringifyToken(key);
5546
+ if (key2)
5547
+ res += stringifyToken(key2);
5548
5548
  if (sep2)
5549
5549
  for (const st of sep2)
5550
5550
  res += st.source;
@@ -6876,7 +6876,7 @@ var require_parser = __commonJS({
6876
6876
  });
6877
6877
  } else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
6878
6878
  const start2 = getFirstKeyStartProps(it.start);
6879
- const key = it.key;
6879
+ const key2 = it.key;
6880
6880
  const sep2 = it.sep;
6881
6881
  sep2.push(this.sourceToken);
6882
6882
  delete it.key;
@@ -6885,7 +6885,7 @@ var require_parser = __commonJS({
6885
6885
  type: "block-map",
6886
6886
  offset: this.offset,
6887
6887
  indent: this.indent,
6888
- items: [{ start: start2, key, sep: sep2 }]
6888
+ items: [{ start: start2, key: key2, sep: sep2 }]
6889
6889
  });
6890
6890
  } else if (start.length > 0) {
6891
6891
  it.sep = it.sep.concat(start, this.sourceToken);
@@ -7396,9 +7396,9 @@ function runMenu(input, output, head, rows, onKey) {
7396
7396
  let headLines = 0;
7397
7397
  let drawn = 0;
7398
7398
  const frame = () => {
7399
- const cols = width();
7400
- const lines = [...rows(), ...fitHint(hint, cols)];
7401
- drawn = lines.reduce((n, line) => n + screenLines(line, cols), 0);
7399
+ const cols2 = width();
7400
+ const lines = [...rows(), ...fitHint(hint, cols2)];
7401
+ drawn = lines.reduce((n, line) => n + screenLines(line, cols2), 0);
7402
7402
  return lines.map((line) => `${CLEAR_LINE}\r${line}
7403
7403
  `).join("");
7404
7404
  };
@@ -7426,12 +7426,12 @@ ${SHOW_CURSOR}`);
7426
7426
  reject(outcome.error);
7427
7427
  }
7428
7428
  };
7429
- const onKeypress = (text3, key) => {
7430
- const name = key?.name ?? text3;
7431
- if (key?.ctrl && (name === "c" || name === "d") || name === "escape" || name === "q" && !key?.ctrl) {
7429
+ const onKeypress = (text3, key2) => {
7430
+ const name2 = key2?.name ?? text3;
7431
+ if (key2?.ctrl && (name2 === "c" || name2 === "d") || name2 === "escape" || name2 === "q" && !key2?.ctrl) {
7432
7432
  return finish({ error: new PromptCancelled() });
7433
7433
  }
7434
- const result = onKey(text3, name);
7434
+ const result = onKey(text3, name2);
7435
7435
  if (result === "redraw") output.write(`\r${up(drawn)}${CLEAR_DOWN}${frame()}`);
7436
7436
  else if (result) finish(result);
7437
7437
  };
@@ -7440,9 +7440,9 @@ ${SHOW_CURSOR}`);
7440
7440
  try {
7441
7441
  emitKeypressEvents(input);
7442
7442
  input.setRawMode?.(true);
7443
- const cols = width();
7444
- const top = [question, ...note ? note.split("\n").flatMap((line) => wrapWords(line, cols)) : []];
7445
- headLines = top.reduce((n, line) => n + screenLines(line, cols), 0);
7443
+ const cols2 = width();
7444
+ const top = [question, ...note ? note.split("\n").flatMap((line) => wrapWords(line, cols2)) : []];
7445
+ headLines = top.reduce((n, line) => n + screenLines(line, cols2), 0);
7446
7446
  output.write(`${HIDE_CURSOR}${top.map((line) => `${line}
7447
7447
  `).join("")}${frame()}`);
7448
7448
  input.on("keypress", onKeypress);
@@ -7477,9 +7477,9 @@ function wrapWords(text3, columns) {
7477
7477
  function marker(options) {
7478
7478
  return options.color ? options.trueColor ? "\x1B[1;38;2;253;75;3m" : "\x1B[1;38;5;202m" : "\x1B[1m";
7479
7479
  }
7480
- function move(current, count2, text3, name) {
7481
- if (name === "up" || name === "k") return (current - 1 + count2) % count2;
7482
- if (name === "down" || name === "j") return (current + 1) % count2;
7480
+ function move(current, count2, text3, name2) {
7481
+ if (name2 === "up" || name2 === "k") return (current - 1 + count2) % count2;
7482
+ if (name2 === "down" || name2 === "j") return (current + 1) % count2;
7483
7483
  if (text3 && /^[1-9]$/.test(text3) && Number(text3) <= count2) return Number(text3) - 1;
7484
7484
  return null;
7485
7485
  }
@@ -7487,9 +7487,9 @@ function selectMenu(input, output, question, choices, options = {}) {
7487
7487
  let current = Math.min(Math.max(options.initial ?? 0, 0), choices.length - 1);
7488
7488
  const on = marker(options);
7489
7489
  const rows = () => choices.map((c, i) => i === current ? `${on}> ${c}${OFF}` : ` ${c}`);
7490
- return runMenu(input, output, { question, hint: HINT, columns: options.columns }, rows, (text3, name) => {
7491
- if (name === "return" || name === "enter") return { pick: current, summary: choices[current] ?? "" };
7492
- const next = move(current, choices.length, text3, name);
7490
+ return runMenu(input, output, { question, hint: HINT, columns: options.columns }, rows, (text3, name2) => {
7491
+ if (name2 === "return" || name2 === "enter") return { pick: current, summary: choices[current] ?? "" };
7492
+ const next = move(current, choices.length, text3, name2);
7493
7493
  if (next === null || next === current) return null;
7494
7494
  current = next;
7495
7495
  return "redraw";
@@ -7503,22 +7503,22 @@ function multiSelectMenu(input, output, question, choices, options = {}) {
7503
7503
  const pad2 = Math.max(...choices.map((c) => c.length));
7504
7504
  const label = (i) => {
7505
7505
  const tag = options.tags?.[i];
7506
- const name = choices[i] ?? "";
7507
- if (!tag) return name;
7508
- const cols = options.columns?.() ?? Infinity;
7506
+ const name2 = choices[i] ?? "";
7507
+ if (!tag) return name2;
7508
+ const cols2 = options.columns?.() ?? Infinity;
7509
7509
  const grey = options.color ? `${GREY}${tag}${GREY_OFF}` : tag;
7510
- if (6 + pad2 + 3 + tag.length <= cols) return `${name.padEnd(pad2)} ${grey}`;
7511
- if (6 + name.length + 1 + tag.length <= cols) return `${name} ${grey}`;
7512
- return name;
7510
+ if (6 + pad2 + 3 + tag.length <= cols2) return `${name2.padEnd(pad2)} ${grey}`;
7511
+ if (6 + name2.length + 1 + tag.length <= cols2) return `${name2} ${grey}`;
7512
+ return name2;
7513
7513
  };
7514
7514
  const rows = () => choices.map((_, i) => `${i === current ? pointer : " "} [${ticked[i] ? "x" : " "}] ${label(i)}`);
7515
7515
  const head = { question, note: options.note, hint: options.hint ?? MULTI_HINT, columns: options.columns };
7516
- return runMenu(input, output, head, rows, (text3, name) => {
7517
- if (name === "return" || name === "enter") {
7516
+ return runMenu(input, output, head, rows, (text3, name2) => {
7517
+ if (name2 === "return" || name2 === "enter") {
7518
7518
  const picks = choices.flatMap((_, i) => ticked[i] ? [i] : []);
7519
7519
  return { pick: picks, summary: picks.map((i) => choices[i]).join(", ") || "none" };
7520
7520
  }
7521
- if (name === "space") {
7521
+ if (name2 === "space") {
7522
7522
  ticked[current] = !ticked[current];
7523
7523
  return "redraw";
7524
7524
  }
@@ -7527,7 +7527,7 @@ function multiSelectMenu(input, output, question, choices, options = {}) {
7527
7527
  ticked[current] = !ticked[current];
7528
7528
  return "redraw";
7529
7529
  }
7530
- const next = move(current, choices.length, text3, name);
7530
+ const next = move(current, choices.length, text3, name2);
7531
7531
  if (next === null || next === current) return null;
7532
7532
  current = next;
7533
7533
  return "redraw";
@@ -7553,9 +7553,9 @@ function listenForEnter(input, onEnter, onCancel) {
7553
7553
  stop();
7554
7554
  onCancel();
7555
7555
  };
7556
- const onKeypress = (_text, key) => {
7557
- if (key?.ctrl && key.name === "c") cancel();
7558
- else if (key?.name === "return" || key?.name === "enter") onEnter();
7556
+ const onKeypress = (_text, key2) => {
7557
+ if (key2?.ctrl && key2.name === "c") cancel();
7558
+ else if (key2?.name === "return" || key2?.name === "enter") onEnter();
7559
7559
  };
7560
7560
  try {
7561
7561
  emitKeypressEvents(input);
@@ -7593,10 +7593,10 @@ function readPaste(input, output, question) {
7593
7593
  else reject(outcome.error);
7594
7594
  };
7595
7595
  const done = () => finish({ text: lines.join("\n").trim() });
7596
- const onKeypress = (_text, key) => {
7597
- if (key?.name === "paste-start") pasting = true;
7598
- else if (key?.name === "paste-end") pasting = false;
7599
- else if (settle && key?.name !== "return" && key?.name !== "enter") {
7596
+ const onKeypress = (_text, key2) => {
7597
+ if (key2?.name === "paste-start") pasting = true;
7598
+ else if (key2?.name === "paste-end") pasting = false;
7599
+ else if (settle && key2?.name !== "return" && key2?.name !== "enter") {
7600
7600
  clearTimeout(settle);
7601
7601
  settle = void 0;
7602
7602
  }
@@ -7868,7 +7868,8 @@ var COMMAND_OPTIONS = {
7868
7868
  list: { type: "string" },
7869
7869
  batch: { type: "string" },
7870
7870
  "no-wait": { type: "boolean" },
7871
- force: { type: "boolean" }
7871
+ force: { type: "boolean" },
7872
+ for: { type: "string" }
7872
7873
  },
7873
7874
  search: { research: { type: "boolean" }, limit: { type: "string" } },
7874
7875
  connections: { all: { type: "boolean" } },
@@ -7921,6 +7922,13 @@ var ALIASES = {
7921
7922
  "auth status": "whoami"
7922
7923
  };
7923
7924
  var TWO_WORD = /* @__PURE__ */ new Set(["auth", "keys", "workspace", "skills"]);
7925
+ function helpWords(argv) {
7926
+ try {
7927
+ return parseArgs({ args: argv, options: allOptions(), allowPositionals: true, strict: false }).positionals;
7928
+ } catch {
7929
+ return [];
7930
+ }
7931
+ }
7924
7932
  function allOptions() {
7925
7933
  const merged = { ...GLOBAL_OPTIONS };
7926
7934
  for (const spec of Object.values(COMMAND_OPTIONS)) Object.assign(merged, spec);
@@ -7939,7 +7947,7 @@ function parseCommandLine(argv) {
7939
7947
  return { command: null, args: [], flags: strictParse(argv, { ...GLOBAL_OPTIONS, ...SETUP_OPTIONS }).values };
7940
7948
  }
7941
7949
  let words = 1;
7942
- let name = first2;
7950
+ let name2 = first2;
7943
7951
  if (TWO_WORD.has(first2)) {
7944
7952
  const second = positionals[1];
7945
7953
  if (first2 === "skills" && second !== "add") {
@@ -7950,14 +7958,14 @@ function parseCommandLine(argv) {
7950
7958
  );
7951
7959
  }
7952
7960
  if (second === void 0) throw usageError(`\`amdahl ${first2}\` needs a subcommand.`);
7953
- name = `${first2} ${second}`;
7961
+ name2 = `${first2} ${second}`;
7954
7962
  words = 2;
7955
7963
  }
7956
- name = ALIASES[name] ?? name;
7957
- const spec = COMMAND_OPTIONS[name];
7964
+ name2 = ALIASES[name2] ?? name2;
7965
+ const spec = COMMAND_OPTIONS[name2];
7958
7966
  if (!spec) throw usageError(`Unknown command \`${positionals.slice(0, words).join(" ")}\`.`, `Run \`${amdahlCommand("--help")}\` to see the commands.`);
7959
7967
  const strict = strictParse(argv, { ...GLOBAL_OPTIONS, ...spec });
7960
- return { command: name, args: strict.positionals.slice(words), flags: strict.values };
7968
+ return { command: name2, args: strict.positionals.slice(words), flags: strict.values };
7961
7969
  }
7962
7970
  function strictParse(argv, options) {
7963
7971
  try {
@@ -7999,6 +8007,10 @@ What you can do:
7999
8007
  amdahl optimize --list drafts.csv
8000
8008
  Rewrite a whole list in one batch; results go
8001
8009
  to drafts.optimized.csv
8010
+ amdahl optimize --list drafts.csv --for lemlist
8011
+ Also write drafts-lemlist.csv, a file your
8012
+ sequencer imports, and say how (Instantly,
8013
+ Smartlead, lemlist, HeyReach, Apollo and more)
8002
8014
  amdahl search "<question>" Ask about your customers, calls and deals
8003
8015
  amdahl connect hubspot Add a CRM or call recorder in the console
8004
8016
  (amdahl connections lists what is connected)
@@ -8022,12 +8034,23 @@ Commands:
8022
8034
  optimize [<file>... | -] [--channel email|linkedin]
8023
8035
  [--evidence off|workspace] [--on-unsupported flag|remove]
8024
8036
  [--tries] [--context <file.json>]
8037
+ --json prints {ok, file, result}: the message is .result.message
8038
+ and the run id is .result.run_id.
8025
8039
  optimize --list <file.csv|file.jsonl> [--no-wait] [--batch <id>] [--force]
8026
- [the optimize flags above, for every message]
8027
- A CSV has a message column, and may have item_id, channel,
8028
- evidence, on_unsupported, facts and rules. --batch <id> picks up
8029
- a batch queued earlier, with the same list. --force replaces an
8030
- existing results file.
8040
+ [--for <tool>] [the optimize flags above, for every message]
8041
+ A CSV needs a message column, and may have item_id, channel,
8042
+ evidence, on_unsupported, facts and rules. Any other column is
8043
+ copied to the results as it is. --batch <id> picks up a batch
8044
+ queued earlier, with the same list. --force replaces an existing
8045
+ results file. In the results CSV, a cell that starts with =, +, -
8046
+ or @ (a phone number, say) gets a ' in front: it is no formula.
8047
+ --for <tool> a CSV list only: also write the messages Amdahl wrote
8048
+ as a CSV that tool imports (<list>-<tool>.csv), and say
8049
+ how to import it. Tools: instantly, smartlead, lemlist,
8050
+ heyreach, reply_io, apollo, outreach, salesloft,
8051
+ hubspot, lgm, woodpecker, other (any tool that imports
8052
+ a CSV). lemlist, reply_io and other take both channels,
8053
+ so add --channel email|linkedin.
8031
8054
  search "<question>" [--research] [--limit <n>]
8032
8055
  --research ask for a research job: a deeper, cited answer (needs
8033
8056
  a Customer agent key, amdahl keys create --preset
@@ -8041,12 +8064,19 @@ Commands:
8041
8064
  What happened to a message
8042
8065
  sent <run-id> --provider <provider> --channel <channel>
8043
8066
  --sent-as returned|edited [--sent-at <time>]
8044
- (--provider-send-id <id> | --recipient <email|url> |
8045
- --recipient-key-hash <sha256>)
8067
+ [--provider-send-id <id>]
8068
+ [--recipient <email|url> | --recipient-key-hash <sha256>]
8046
8069
  [--provider-campaign-id <id>] [--sequence-step <n>]
8047
8070
  [--provider-variant-id <id>] [--provider-lead-id <id>]
8048
8071
  [--thread-id <id>] [--sent-text <file> | -]
8049
- One send per command. It needs one of the three in brackets.
8072
+ One send per command. Give --provider-send-id, a recipient, or
8073
+ both (a recipient is --recipient or --recipient-key-hash, not both).
8074
+ --provider: lemlist, instantly, outreach, salesloft, hubspot,
8075
+ salesforce, gmail, heyreach, apollo, linkedin_manual,
8076
+ other (any sender not listed)
8077
+ --channel: email, linkedin (= linkedin_message), linkedin_invite,
8078
+ linkedin_message, linkedin_inmail
8079
+ --sent-as edited needs --sent-text.
8050
8080
  Report that a message went out
8051
8081
  keys create --name <name> [--preset read-only|agent|internal|admin]
8052
8082
  [--expires 30d|90d|365d] [--no-browser]
@@ -8068,6 +8098,7 @@ Commands:
8068
8098
  (Cancel or Install anyway); -y, --json or no terminal
8069
8099
  stops (exit 9)
8070
8100
  help Show this help
8101
+ Run \`amdahl <command> --help\` for one command's usage.
8071
8102
 
8072
8103
  Global options:
8073
8104
  --profile <name> Use a saved profile (default: AMDAHL_PROFILE, then the
@@ -8085,7 +8116,7 @@ Global options:
8085
8116
  `;
8086
8117
 
8087
8118
  // src/version.ts
8088
- var VERSION = "0.4.0";
8119
+ var VERSION = "0.4.2";
8089
8120
 
8090
8121
  // src/api.ts
8091
8122
  var CLIENT_HEADER = {
@@ -8093,18 +8124,20 @@ var CLIENT_HEADER = {
8093
8124
  "User-Agent": `amdahl-cli/${VERSION}`
8094
8125
  };
8095
8126
  var AmdahlApiError = class extends Error {
8096
- constructor(message2, statusCode, path, responseBody, errorCode) {
8127
+ constructor(message2, statusCode, path, responseBody, errorCode, credentialSource) {
8097
8128
  super(message2);
8098
8129
  this.statusCode = statusCode;
8099
8130
  this.path = path;
8100
8131
  this.responseBody = responseBody;
8101
8132
  this.errorCode = errorCode;
8133
+ this.credentialSource = credentialSource;
8102
8134
  this.name = "AmdahlApiError";
8103
8135
  }
8104
8136
  statusCode;
8105
8137
  path;
8106
8138
  responseBody;
8107
8139
  errorCode;
8140
+ credentialSource;
8108
8141
  };
8109
8142
  function extractToolFailure(json) {
8110
8143
  if (!json || typeof json !== "object" || Array.isArray(json)) return null;
@@ -8159,24 +8192,24 @@ function parseErrorBody(errorBody, statusCode) {
8159
8192
  }
8160
8193
  var PLATFORM = "/api/platform/v1";
8161
8194
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8162
- function assertUuid(name, value) {
8195
+ function assertUuid(name2, value) {
8163
8196
  if (typeof value !== "string" || value.length === 0) {
8164
8197
  throw new Error(
8165
- `mcpKeys client: ${name} must be a non-empty string (got ${typeof value === "string" ? "empty string" : String(value)})`
8198
+ `mcpKeys client: ${name2} must be a non-empty string (got ${typeof value === "string" ? "empty string" : String(value)})`
8166
8199
  );
8167
8200
  }
8168
8201
  const lowered = value.toLowerCase();
8169
8202
  if (lowered === "undefined" || lowered === "null") {
8170
8203
  throw new Error(
8171
- `mcpKeys client: ${name} is the literal string '${value}'. Fix the caller rather than relaxing this guard.`
8204
+ `mcpKeys client: ${name2} is the literal string '${value}'. Fix the caller rather than relaxing this guard.`
8172
8205
  );
8173
8206
  }
8174
8207
  if (!UUID_RE.test(value)) {
8175
- throw new Error(`mcpKeys client: ${name} must be a UUID (got '${value}')`);
8208
+ throw new Error(`mcpKeys client: ${name2} must be a UUID (got '${value}')`);
8176
8209
  }
8177
8210
  }
8178
8211
  function createAmdahlClient(config) {
8179
- const { baseUrl, token } = config;
8212
+ const { baseUrl, token, source } = config;
8180
8213
  async function request(path, options = {}) {
8181
8214
  const { method = "GET", body, headers = {} } = options;
8182
8215
  const allHeaders = { ...headers, ...CLIENT_HEADER, Authorization: `Bearer ${token}` };
@@ -8189,12 +8222,12 @@ function createAmdahlClient(config) {
8189
8222
  if (!response.ok) {
8190
8223
  const errorBody = await response.text().catch(() => "");
8191
8224
  const { message: message2, code } = parseErrorBody(errorBody, response.status);
8192
- throw new AmdahlApiError(message2, response.status, path, errorBody, code);
8225
+ throw new AmdahlApiError(message2, response.status, path, errorBody, code, source);
8193
8226
  }
8194
8227
  const json = await response.json();
8195
8228
  const toolFailure = extractToolFailure(json);
8196
8229
  if (toolFailure) {
8197
- throw new AmdahlApiError(toolFailure.message, response.status, path, JSON.stringify(json), toolFailure.code);
8230
+ throw new AmdahlApiError(toolFailure.message, response.status, path, JSON.stringify(json), toolFailure.code, source);
8198
8231
  }
8199
8232
  if (json && typeof json === "object" && !Array.isArray(json) && "data" in json && Object.keys(json).length === 1) {
8200
8233
  return json.data;
@@ -8804,7 +8837,7 @@ async function profileCredential(ctx) {
8804
8837
  return { source: "profile", token: refreshed.access_token, profile: profileName };
8805
8838
  }
8806
8839
  function apiClient(ctx, credential) {
8807
- return createAmdahlClient({ baseUrl: ctx.apiUrl, token: credential.token });
8840
+ return createAmdahlClient({ baseUrl: ctx.apiUrl, token: credential.token, source: credential.source });
8808
8841
  }
8809
8842
  function errorDetails(body) {
8810
8843
  if (!body) return {};
@@ -8816,6 +8849,28 @@ function errorDetails(body) {
8816
8849
  return {};
8817
8850
  }
8818
8851
  }
8852
+ var CALL_VERBS = [
8853
+ ["/messages/optimize/batch", "optimize a list"],
8854
+ ["/messages/optimize/runs/", "record feedback"],
8855
+ ["/messages/optimize", "optimize"],
8856
+ ["/messages/report_send", "report sends"],
8857
+ ["/research/", "read research"],
8858
+ ["/search/query", "search"],
8859
+ ["/connections", "read connections"],
8860
+ ["/setup/status", "read the setup status"]
8861
+ ];
8862
+ function plainMessage(err) {
8863
+ if (err.statusCode === 401 && err.credentialSource) {
8864
+ if (err.credentialSource === "profile") return "Your sign-in is no longer valid.";
8865
+ const noun = err.credentialSource === "env:AMDAHL_ACCESS_TOKEN" ? "token" : "key";
8866
+ return `This ${noun} isn't valid (revoked or expired).`;
8867
+ }
8868
+ if (err.errorCode === "missing_scope" || /^Missing required scope/i.test(err.message)) {
8869
+ const verb = CALL_VERBS.find(([prefix2]) => err.path.includes(prefix2))?.[1] ?? "do this";
8870
+ return `This credential cannot ${verb}. Use a Customer agent key, or run \`${amdahlCommand("login")}\` again.`;
8871
+ }
8872
+ return null;
8873
+ }
8819
8874
  function toCliError(err) {
8820
8875
  if (err instanceof CliError) return err;
8821
8876
  if (err instanceof PromptCancelled) return new CliError("cancelled", "Stopped. Nothing changed.", EXIT.cancelled);
@@ -8824,7 +8879,7 @@ function toCliError(err) {
8824
8879
  const details = errorDetails(err.responseBody);
8825
8880
  const retry = details.retry_after_seconds;
8826
8881
  const hint = exit === EXIT.unauthenticated ? `Run \`${amdahlCommand("login")}\`, or check AMDAHL_KEY.` : typeof retry === "number" ? `Try again in ${retry} seconds.` : void 0;
8827
- return new CliError(err.errorCode ?? `http_${err.statusCode}`, err.message, exit, hint);
8882
+ return new CliError(err.errorCode ?? `http_${err.statusCode}`, plainMessage(err) ?? err.message, exit, hint);
8828
8883
  }
8829
8884
  const message2 = err instanceof Error ? err.message : String(err);
8830
8885
  return new CliError("network_error", `Could not reach the Amdahl API: ${message2}`, EXIT.network, NETWORK_HINT);
@@ -8971,9 +9026,9 @@ async function browserSignIn(ctx) {
8971
9026
  const client = apiClient(ctx, { source: "profile", token: secrets.access_token });
8972
9027
  const me = await call(() => client.cli.whoami());
8973
9028
  const ws = me.workspace ?? { id: me.business_id, name: me.business_id, slug: me.business_id };
8974
- const name = explicitProfile ?? (PROFILE_NAME.test(ws.slug) ? ws.slug : "default");
8975
- await ctx.secrets().set(keychainAccount(ctx.apiUrl, name), secrets);
8976
- ctx.config.profiles[name] = {
9029
+ const name2 = explicitProfile ?? (PROFILE_NAME.test(ws.slug) ? ws.slug : "default");
9030
+ await ctx.secrets().set(keychainAccount(ctx.apiUrl, name2), secrets);
9031
+ ctx.config.profiles[name2] = {
8977
9032
  api_url: ctx.apiUrl,
8978
9033
  user_id: me.user_id,
8979
9034
  email: me.email ?? null,
@@ -8981,11 +9036,11 @@ async function browserSignIn(ctx) {
8981
9036
  scopes: me.scopes,
8982
9037
  created_at: new Date(io.now()).toISOString()
8983
9038
  };
8984
- ctx.config.default_profile = name;
9039
+ ctx.config.default_profile = name2;
8985
9040
  ctx.saveConfig();
8986
- ctx.profileName = name;
8987
- ctx.profile = ctx.config.profiles[name] ?? null;
8988
- return { profile: name, me, workspace: ws };
9041
+ ctx.profileName = name2;
9042
+ ctx.profile = ctx.config.profiles[name2] ?? null;
9043
+ return { profile: name2, me, workspace: ws };
8989
9044
  } finally {
8990
9045
  stopKeys();
8991
9046
  loopback.close();
@@ -9024,10 +9079,10 @@ async function logout(ctx) {
9024
9079
  }
9025
9080
  const notRevoked = [];
9026
9081
  const store = ctx.secrets();
9027
- for (const name of names) {
9028
- const profile = config.profiles[name];
9082
+ for (const name2 of names) {
9083
+ const profile = config.profiles[name2];
9029
9084
  if (!profile) continue;
9030
- const account = keychainAccount(profile.api_url, name);
9085
+ const account = keychainAccount(profile.api_url, name2);
9031
9086
  const secrets = await store.get(account);
9032
9087
  if (secrets) {
9033
9088
  let ok = false;
@@ -9039,10 +9094,10 @@ async function logout(ctx) {
9039
9094
  } catch {
9040
9095
  ok = false;
9041
9096
  }
9042
- if (!ok) notRevoked.push(name);
9097
+ if (!ok) notRevoked.push(name2);
9043
9098
  }
9044
9099
  await store.delete(account);
9045
- delete config.profiles[name];
9100
+ delete config.profiles[name2];
9046
9101
  }
9047
9102
  if (config.default_profile && !config.profiles[config.default_profile]) {
9048
9103
  config.default_profile = Object.keys(config.profiles)[0] ?? null;
@@ -9100,7 +9155,7 @@ async function authToken(ctx) {
9100
9155
  // src/ci.ts
9101
9156
  var isSet = (value) => Boolean(value) && value !== "false" && value !== "0";
9102
9157
  function ciVariable(env) {
9103
- const named = ["CI", "JENKINS_URL", "TF_BUILD", "CODEBUILD_BUILD_ID", "TEAMCITY_VERSION", "GITHUB_ACTIONS", "GITLAB_CI", "BUILDKITE", "CIRCLECI"].find((name) => isSet(env[name]));
9158
+ const named = ["CI", "JENKINS_URL", "TF_BUILD", "CODEBUILD_BUILD_ID", "TEAMCITY_VERSION", "GITHUB_ACTIONS", "GITLAB_CI", "BUILDKITE", "CIRCLECI"].find((name2) => isSet(env[name2]));
9104
9159
  if (named) return named;
9105
9160
  return isSet(env.BUILD_NUMBER) && isSet(env.JENKINS_HOME) ? "BUILD_NUMBER with JENKINS_HOME" : null;
9106
9161
  }
@@ -9113,10 +9168,10 @@ import { dirname as dirname2, join as join4, relative, sep } from "path";
9113
9168
  import { randomBytes as randomBytes2 } from "crypto";
9114
9169
  import { chmodSync as chmodSync3, lstatSync, mkdirSync as mkdirSync2, mkdtempSync, readdirSync, readFileSync as readFileSync3, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
9115
9170
  import { dirname, join as join3 } from "path";
9116
- function skillDirs(targets, name) {
9171
+ function skillDirs(targets, name2) {
9117
9172
  const dirs = [];
9118
- if (targets.claude) dirs.push(join3(targets.base, ".claude", "skills", name));
9119
- if (targets.agents) dirs.push(join3(targets.base, ".agents", "skills", name));
9173
+ if (targets.claude) dirs.push(join3(targets.base, ".claude", "skills", name2));
9174
+ if (targets.agents) dirs.push(join3(targets.base, ".agents", "skills", name2));
9120
9175
  return dirs;
9121
9176
  }
9122
9177
  var message = (err) => err instanceof Error ? err.message : String(err);
@@ -9210,24 +9265,24 @@ function planInstall(dirs, payload) {
9210
9265
  return { path, action: current === null ? "wrote" : current.equals(payload.body) ? "unchanged" : "replaced" };
9211
9266
  });
9212
9267
  }
9213
- function refuseClashes(plan, name, force) {
9268
+ function refuseClashes(plan, name2, force) {
9214
9269
  const clashes = plan.filter((p) => p.action === "replaced").map((p) => p.path);
9215
9270
  if (clashes.length > 0 && !force) {
9216
9271
  throw new CliError(
9217
9272
  "skill_exists",
9218
- `A different ${name} skill is already at ${clashes.join(" and ")}.`,
9273
+ `A different ${name2} skill is already at ${clashes.join(" and ")}.`,
9219
9274
  EXIT.general,
9220
9275
  "Nothing was written. Run again with --force to replace it."
9221
9276
  );
9222
9277
  }
9223
9278
  }
9224
- function stage(target, name, archive, staged) {
9279
+ function stage(target, name2, archive, staged) {
9225
9280
  const s = { target, temp: "" };
9226
9281
  staged.push(s);
9227
9282
  refuseLinks(target);
9228
9283
  s.createdParent = mkdirSync2(dirname(target), { recursive: true }) ?? void 0;
9229
9284
  refuseLinks(target);
9230
- s.temp = mkdtempSync(join3(dirname(target), `.${name}.amdahl-tmp-`));
9285
+ s.temp = mkdtempSync(join3(dirname(target), `.${name2}.amdahl-tmp-`));
9231
9286
  chmodSync3(s.temp, 493);
9232
9287
  for (const dir of archive.dirs) mkdirSync2(join3(s.temp, dir));
9233
9288
  for (const file of archive.files) writeFileSync2(join3(s.temp, file.path), file.bytes, { flag: "wx", mode: file.mode });
@@ -9247,14 +9302,14 @@ function rollBack(staged) {
9247
9302
  if (s.createdParent) rmSync2(s.createdParent, { recursive: true, force: true });
9248
9303
  }
9249
9304
  }
9250
- function writeFolders(plan, name, archive) {
9305
+ function writeFolders(plan, name2, archive) {
9251
9306
  const staged = [];
9252
9307
  let current = "";
9253
9308
  try {
9254
9309
  for (const p of plan) {
9255
9310
  if (p.action === "unchanged") continue;
9256
9311
  current = p.path;
9257
- stage(p.path, name, archive, staged);
9312
+ stage(p.path, name2, archive, staged);
9258
9313
  }
9259
9314
  for (const s of staged) {
9260
9315
  current = s.target;
@@ -9291,8 +9346,8 @@ function writeFiles(plan, body) {
9291
9346
  written.push(p.path);
9292
9347
  }
9293
9348
  }
9294
- function writeInstall(io, plan, name, payload) {
9295
- io.uninterruptible(() => payload.type === "archive" ? writeFolders(plan, name, payload.archive) : writeFiles(plan, payload.body));
9349
+ function writeInstall(io, plan, name2, payload) {
9350
+ io.uninterruptible(() => payload.type === "archive" ? writeFolders(plan, name2, payload.archive) : writeFiles(plan, payload.body));
9296
9351
  }
9297
9352
 
9298
9353
  // src/commands/skills-guide.ts
@@ -9436,6 +9491,14 @@ function day(iso) {
9436
9491
  const ms = Date.parse(iso);
9437
9492
  return Number.isFinite(ms) ? new Date(ms).toISOString().slice(0, 10) : "";
9438
9493
  }
9494
+ function hasFactsOrRules(context) {
9495
+ const c = record(context);
9496
+ const some = (list2) => Array.isArray(list2) && list2.some((entry) => typeof entry === "string" ? entry.trim() !== "" : !!record(entry));
9497
+ return !!c && (some(c.facts) || some(c.rules));
9498
+ }
9499
+ function truthfulSummary(summary, context) {
9500
+ return /facts and rules/i.test(summary) && !hasFactsOrRules(context) ? "" : summary;
9501
+ }
9439
9502
  function count(n, one, many = `${one}s`) {
9440
9503
  return `${n} ${n === 1 ? one : many}`;
9441
9504
  }
@@ -9515,8 +9578,8 @@ function catalogEntries(body) {
9515
9578
  }));
9516
9579
  }
9517
9580
  function findConnector(entries, wanted) {
9518
- const key = wanted.toLowerCase().replace(/[\s-]+/g, "_");
9519
- return entries.find((e) => e.type.toLowerCase() === key || e.name.toLowerCase().replace(/[\s-]+/g, "_") === key);
9581
+ const key2 = wanted.toLowerCase().replace(/[\s-]+/g, "_");
9582
+ return entries.find((e) => e.type.toLowerCase() === key2 || e.name.toLowerCase().replace(/[\s-]+/g, "_") === key2);
9520
9583
  }
9521
9584
  async function connect(ctx, args) {
9522
9585
  if (args.length > 1) throw usageError("Name one connector at a time.", `Usage: ${amdahlCommand("connect [<connector>] [--no-browser]")}`);
@@ -9536,7 +9599,10 @@ async function connect(ctx, args) {
9536
9599
  }
9537
9600
  }
9538
9601
  const slug = await workspaceSlug(ctx, credential);
9539
- const url = connectUrl(consoleUrlFor(ctx.io, ctx.apiUrl), slug, entry?.type);
9602
+ const consoleUrl = consoleUrlFor(ctx.io, ctx.apiUrl);
9603
+ const url = connectUrl(consoleUrl, slug, entry?.type);
9604
+ const existing = entry ? await existingConnection(client, entry.type) : null;
9605
+ if (entry && existing) return alreadyConnected(ctx, entry, existing, url, connectionUrl(consoleUrl, slug, field(existing.id)));
9540
9606
  const opened = await openOrPrint(ctx, url);
9541
9607
  if (ctx.out.json) {
9542
9608
  ctx.out.result({ ok: true, url, opened, connector: entry?.type ?? null });
@@ -9550,15 +9616,42 @@ async function connect(ctx, args) {
9550
9616
  ctx.out.result({}, url);
9551
9617
  return EXIT.ok;
9552
9618
  }
9619
+ async function existingConnection(client, type) {
9620
+ try {
9621
+ const body = await client.request(`${PLATFORM2}/connections`);
9622
+ const same = records(Array.isArray(body) ? body : record(body)?.connections).filter((c) => field(c.connector_type) === type || field(record(c.connector)?.type) === type);
9623
+ return same.find((c) => FINE.has(healthOf(c))) ?? same[0] ?? null;
9624
+ } catch {
9625
+ return null;
9626
+ }
9627
+ }
9628
+ function alreadyConnected(ctx, entry, existing, addUrl, pageUrl) {
9629
+ const health = healthOf(existing);
9630
+ const healthy = FINE.has(health);
9631
+ const synced = day(existing.last_synced_at);
9632
+ if (ctx.out.json) {
9633
+ ctx.out.result({ ok: true, url: addUrl, opened: false, connector: entry.type, connected: { id: field(existing.id), health, healthy, url: pageUrl } });
9634
+ return EXIT.ok;
9635
+ }
9636
+ if (healthy) {
9637
+ ctx.out.info(`${entry.name} is already connected and healthy${synced ? ` (last sync ${synced})` : ""}.`);
9638
+ ctx.out.info("To add another one, open this link:");
9639
+ ctx.out.result({}, addUrl);
9640
+ return EXIT.ok;
9641
+ }
9642
+ ctx.out.info(`${entry.name} is already connected, but its health is "${HEALTH[health] ?? health}".`);
9643
+ ctx.out.info(`${health === "needs_reauth" ? "Sign in to it again" : "Fix it"}: ${pageUrl}`);
9644
+ return EXIT.ok;
9645
+ }
9553
9646
  function connectionRow(c) {
9554
9647
  const connector = record(c.connector);
9555
9648
  const type = field(connector?.name) || field(c.connector_type);
9556
9649
  const label = field(c.name);
9557
- const name = label && label !== type ? `${type} (${label})` : type || label;
9650
+ const name2 = label && label !== type ? `${type} (${label})` : type || label;
9558
9651
  const health = healthOf(c);
9559
9652
  const synced = day(c.last_synced_at);
9560
9653
  return {
9561
- name: clip(name, 32),
9654
+ name: clip(name2, 32),
9562
9655
  kind: field(connector?.category),
9563
9656
  health: HEALTH[health] ?? health,
9564
9657
  synced: synced ? `synced ${synced}` : "never synced"
@@ -9580,7 +9673,7 @@ ${connectLine()}`);
9580
9673
  return EXIT.ok;
9581
9674
  }
9582
9675
  const rows = list2.map(connectionRow);
9583
- const width = (key) => Math.max(...rows.map((r) => [...r[key]].length));
9676
+ const width = (key2) => Math.max(...rows.map((r) => [...r[key2]].length));
9584
9677
  const [w1, w2, w3] = [width("name"), width("kind"), width("health")];
9585
9678
  const pad2 = (s, w) => s + " ".repeat(Math.max(0, w - [...s].length));
9586
9679
  const lines = rows.map((r) => `${pad2(r.name, w1)} ${pad2(r.kind, w2)} ${pad2(r.health, w3)} ${r.synced}`.trimEnd());
@@ -9602,13 +9695,27 @@ async function fixLine(ctx, credential, attention, slug) {
9602
9695
  return `Fix them: ${connectionsUrl(consoleUrl, ws)}`;
9603
9696
  }
9604
9697
 
9605
- // src/commands/feedback.ts
9698
+ // src/files.ts
9606
9699
  import { readFileSync as readFileSync4 } from "fs";
9700
+ function readTextFile(path) {
9701
+ try {
9702
+ return readFileSync4(path, "utf8");
9703
+ } catch (err) {
9704
+ const code = err?.code;
9705
+ if (code === "ENOENT") throw usageError(`Can't find ${path}.`);
9706
+ if (code === "EISDIR") throw usageError(`${path} is a folder, not a file.`);
9707
+ if (code === "EACCES" || code === "EPERM") throw usageError(`Can't read ${path}: permission denied.`);
9708
+ throw usageError(`Can't read ${path}: ${err instanceof Error ? err.message : String(err)}`);
9709
+ }
9710
+ }
9711
+
9712
+ // src/commands/feedback.ts
9607
9713
  var PLATFORM3 = "/api/platform/v1";
9608
9714
  var FEEDBACK_OUTCOMES = ["used", "used_with_edits", "not_used", "unsure"];
9609
9715
  var FEEDBACK_EVIDENCE = ["did_it_myself", "reported_to_me", "inferred"];
9610
9716
  var SEND_PROVIDERS = ["lemlist", "instantly", "outreach", "salesloft", "hubspot", "salesforce", "gmail", "heyreach", "apollo", "linkedin_manual", "other"];
9611
9717
  var SEND_CHANNELS = ["email", "linkedin_invite", "linkedin_message", "linkedin_inmail"];
9718
+ var CHANNEL_ALIASES = { linkedin: "linkedin_message" };
9612
9719
  var SENT_AS = ["returned", "edited"];
9613
9720
  var MAX_MESSAGE_CHARS = 1e4;
9614
9721
  var MAX_COMMENT_CHARS = 2e3;
@@ -9633,11 +9740,7 @@ function text(flag, value, max) {
9633
9740
  return value;
9634
9741
  }
9635
9742
  async function readText(ctx, path) {
9636
- try {
9637
- return path === "-" ? await ctx.io.readStdin() : readFileSync4(path, "utf8");
9638
- } catch (err) {
9639
- throw usageError(`Could not read --sent-text ${path}: ${err instanceof Error ? err.message : String(err)}`);
9640
- }
9743
+ return path === "-" ? await ctx.io.readStdin() : readTextFile(path);
9641
9744
  }
9642
9745
  async function feedback(ctx, args) {
9643
9746
  const usage = `Usage: ${amdahlCommand("feedback <run-id> --evidence did_it_myself [--outcome used] [--sent-text <file>] [--comment <text>]")}`;
@@ -9675,34 +9778,41 @@ var PROVIDER_IDS = {
9675
9778
  "provider-lead-id": ["provider_lead_id", 256],
9676
9779
  "thread-id": ["thread_id", 512]
9677
9780
  };
9678
- var SEND_KEYS = ["--provider-send-id", "--recipient", "--recipient-key-hash"];
9679
9781
  async function sent(ctx, args) {
9680
9782
  const { flags } = ctx;
9681
9783
  const usage = `Usage: ${amdahlCommand("sent <run-id> --provider <provider> --channel <channel> --sent-as returned|edited --provider-send-id <id>")}`;
9682
9784
  const [rawId, ...rest] = args;
9683
9785
  if (!rawId) throw usageError("Give the run id of the message that was sent.", usage);
9786
+ if (rest.length > 0 && rest.every((r) => r.toLowerCase() === rawId.toLowerCase())) {
9787
+ throw usageError(`${rawId} is there twice.`, "Give each run id once. The command reports one send.");
9788
+ }
9684
9789
  if (rest.length > 0) {
9685
9790
  throw usageError("Report one send at a time: give one run id.", "Each send needs its own --provider-send-id or --recipient, so run it once for each. The API takes up to 100 at once (POST /messages/report_send).");
9686
9791
  }
9687
9792
  const id = runId(rawId, usage);
9688
- const provider = oneOf("provider", str(flags.provider), SEND_PROVIDERS);
9689
- const channel = oneOf("channel", str(flags.channel), SEND_CHANNELS);
9793
+ const rawProvider = str(flags.provider);
9794
+ if (rawProvider !== void 0 && !SEND_PROVIDERS.includes(rawProvider)) {
9795
+ throw usageError(`--provider must be one of: ${SEND_PROVIDERS.join(", ")}.`, `"${clip(rawProvider, 40)}" is not on the list. For a sender that is not, use --provider other.`);
9796
+ }
9797
+ const provider = rawProvider;
9798
+ const rawChannel = str(flags.channel);
9799
+ const channel = rawChannel === void 0 ? void 0 : CHANNEL_ALIASES[rawChannel] ?? oneOf("channel", rawChannel, [...SEND_CHANNELS, ...Object.keys(CHANNEL_ALIASES)]);
9690
9800
  const sentAs = oneOf("sent-as", str(flags["sent-as"]), SENT_AS);
9691
9801
  if (!provider) throw usageError(`Say where it went out with --provider: ${SEND_PROVIDERS.join(", ")}.`, usage);
9692
- if (!channel) throw usageError(`Say what was sent with --channel: ${SEND_CHANNELS.join(", ")}.`, usage);
9802
+ if (!channel) throw usageError(`Say what was sent with --channel: ${[...SEND_CHANNELS, ...Object.keys(CHANNEL_ALIASES)].join(", ")}.`, usage);
9693
9803
  if (!sentAs) throw usageError("Say whether it was sent as Amdahl returned it with --sent-as: returned, edited.", usage);
9694
9804
  const sentAt = str(flags["sent-at"]) ?? new Date(ctx.io.now()).toISOString();
9695
9805
  if (!TIME_WITH_ZONE.test(sentAt) || !Number.isFinite(Date.parse(sentAt))) {
9696
9806
  throw usageError("--sent-at must be a time with a time zone, for example 2026-11-12T09:30:00Z.");
9697
9807
  }
9698
9808
  const item = { run_id: id, provider, channel, sent_as: sentAs, sent_at: sentAt };
9699
- for (const [flag, [key, max]] of Object.entries(PROVIDER_IDS)) {
9809
+ for (const [flag, [key2, max]] of Object.entries(PROVIDER_IDS)) {
9700
9810
  const value = text(flag, str(flags[flag]), max);
9701
9811
  if (value === void 0) continue;
9702
9812
  if (EMAIL_WHOLE_VALUE.test(value.trim()) || LINKEDIN_PROFILE.test(value.trim())) {
9703
9813
  throw usageError(`--${flag} is the provider's own id, not an email address or a LinkedIn profile URL.`, "Amdahl stores a person only as a hash: give the address with --recipient.");
9704
9814
  }
9705
- item[key] = value;
9815
+ item[key2] = value;
9706
9816
  }
9707
9817
  const sendId = typeof item.provider_send_id === "string" ? item.provider_send_id : void 0;
9708
9818
  if (provider === "gmail" && sendId !== void 0 && !BRACKETED_MESSAGE_ID.test(sendId.trim())) {
@@ -9726,8 +9836,14 @@ async function sent(ctx, args) {
9726
9836
  }
9727
9837
  const sentPath = str(flags["sent-text"]);
9728
9838
  if (sentPath !== void 0) item.sent_text = text("sent-text", await readText(ctx, sentPath), MAX_MESSAGE_CHARS);
9839
+ if (sentAs === "edited" && sentPath === void 0) {
9840
+ throw usageError("--sent-as edited needs --sent-text <file>: the message as you sent it.", "Amdahl keeps only a hash of the text. Use - to read it from standard input.");
9841
+ }
9842
+ if (sentAs === "edited" && !(typeof item.sent_text === "string" && item.sent_text.trim())) {
9843
+ throw usageError(`${sentPath === "-" ? "Standard input" : `--sent-text ${sentPath}`} is empty. An edited send needs the message as you sent it.`);
9844
+ }
9729
9845
  if (!sendId?.trim() && !recipient?.trim() && !hash?.trim()) {
9730
- throw usageError(`Say which send this is with one of ${SEND_KEYS.join(", ")}.`, "Amdahl matches a report to a send by its provider's own id, or by the recipient (stored only as a hash).");
9846
+ throw usageError("Say which send this is: give --provider-send-id, a recipient (--recipient or --recipient-key-hash), or both.", "Amdahl matches a report to a send by its provider's own id, by the recipient (stored only as a hash), or both.");
9731
9847
  }
9732
9848
  const credential = await resolveCredential(ctx);
9733
9849
  const result = await call(
@@ -10142,9 +10258,9 @@ async function waitForDecision(ctx, client, req) {
10142
10258
  }
10143
10259
  }
10144
10260
  async function keysCreate(ctx) {
10145
- const name = str(ctx.flags.name)?.trim();
10146
- if (!name) throw usageError("--name is required.", `Example: ${amdahlCommand('keys create --name "CI pipeline"')}`);
10147
- if (name.length > 80) throw usageError("--name must be at most 80 characters.");
10261
+ const name2 = str(ctx.flags.name)?.trim();
10262
+ if (!name2) throw usageError("--name is required.", `Example: ${amdahlCommand('keys create --name "CI pipeline"')}`);
10263
+ if (name2.length > 80) throw usageError("--name must be at most 80 characters.");
10148
10264
  const preset = str(ctx.flags.preset) ?? "read-only";
10149
10265
  const bundle = PRESET_BUNDLES[preset];
10150
10266
  if (!bundle) throw usageError(`--preset must be one of: ${Object.keys(PRESET_BUNDLES).join(", ")}.`);
@@ -10153,22 +10269,22 @@ async function keysCreate(ctx) {
10153
10269
  if (!days) throw usageError(`--expires must be one of: ${Object.keys(EXPIRES_DAYS).join(", ")}.`);
10154
10270
  const credential = await requireOAuthCredential(ctx);
10155
10271
  const client = apiClient(ctx, credential);
10156
- const key = generateKey();
10272
+ const key2 = generateKey();
10157
10273
  const req = await call(
10158
10274
  () => client.cli.createKeyRequest({
10159
10275
  action: "create",
10160
- name,
10276
+ name: name2,
10161
10277
  bundle_name: bundle,
10162
10278
  expires_in_days: days,
10163
- key_hash: key.hash,
10164
- key_prefix: key.prefix,
10279
+ key_hash: key2.hash,
10280
+ key_prefix: key2.prefix,
10165
10281
  device_name: ctx.io.hostname.slice(0, 120)
10166
10282
  })
10167
10283
  );
10168
- announce(ctx, req, `create the key "${name}"`);
10284
+ announce(ctx, req, `create the key "${name2}"`);
10169
10285
  const state = await waitForDecision(ctx, client, req);
10170
10286
  const minted = state.key;
10171
- if (!minted || minted.key_prefix !== key.prefix) {
10287
+ if (!minted || minted.key_prefix !== key2.prefix) {
10172
10288
  throw new CliError(
10173
10289
  "key_mismatch",
10174
10290
  "The approved key does not match the one this terminal generated. Nothing was printed.",
@@ -10177,7 +10293,7 @@ async function keysCreate(ctx) {
10177
10293
  );
10178
10294
  }
10179
10295
  ctx.out.info(`Key created: ${minted.name} (${minted.key_prefix}...). Copy it now: it is shown once and stored nowhere.`);
10180
- ctx.out.result({ ok: true, secret: key.secret, key: minted }, key.secret);
10296
+ ctx.out.result({ ok: true, secret: key2.secret, key: minted }, key2.secret);
10181
10297
  return EXIT.ok;
10182
10298
  }
10183
10299
  async function workspaceId(ctx, client, fromProfile) {
@@ -10247,12 +10363,366 @@ async function keysRevoke(ctx, args) {
10247
10363
  return EXIT.ok;
10248
10364
  }
10249
10365
 
10250
- // src/commands/optimize.ts
10251
- import { readFileSync as readFileSync7 } from "fs";
10366
+ // src/next-steps.ts
10367
+ function sentLines(runId2, lead = "Sent it?") {
10368
+ const id = field(runId2);
10369
+ if (!id) return [];
10370
+ return [
10371
+ `${lead} ${amdahlCommand("sent")} ${id} --provider <provider> --channel <channel> --sent-as returned --provider-send-id <id>`,
10372
+ `Or say how it went: ${amdahlCommand("feedback")} ${id} --evidence did_it_myself --outcome used`
10373
+ ];
10374
+ }
10375
+
10376
+ // src/commands/batch.ts
10377
+ import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
10378
+ import { basename as basename3, dirname as dirname4, extname, join as join7 } from "path";
10379
+
10380
+ // src/send-to.ts
10381
+ import { basename as basename2 } from "path";
10382
+ var SEND_TO_IDS = ["instantly", "smartlead", "lemlist", "heyreach", "reply_io", "apollo", "outreach", "salesloft", "hubspot", "lgm", "woodpecker", "other"];
10383
+ var braces = (column) => `{{${column}}}`;
10384
+ var name = (column) => column;
10385
+ var single = (column) => `{${column}}`;
10386
+ var PASTE = "Paste into your step";
10387
+ var MENU = "Add them from the variable menu in your step";
10388
+ var cols = (channel, to) => channel === "email" ? `amdahl_subject and amdahl_message ${to}` : `amdahl_message ${to}`;
10389
+ var key = (channel, email, linkedin) => channel === "linkedin" ? linkedin : email;
10390
+ var SEND_TO_PRESETS = [
10391
+ {
10392
+ id: "instantly",
10393
+ label: "Instantly",
10394
+ channels: ["email"],
10395
+ headers: { company: "company_name" },
10396
+ where: "In your campaign, open Leads and choose Upload CSV.",
10397
+ match: (c) => `Map email to Email, and ${cols(c, "to custom variables")}.`,
10398
+ insert: PASTE,
10399
+ variable: braces
10400
+ },
10401
+ {
10402
+ id: "smartlead",
10403
+ label: "Smartlead",
10404
+ channels: ["email"],
10405
+ headers: { company: "company_name" },
10406
+ where: "Engage > Campaigns > your campaign > Import Leads > Import CSV.",
10407
+ match: (c) => `Map email to Email, and ${cols(c, "to custom variables")}.`,
10408
+ insert: PASTE,
10409
+ variable: braces
10410
+ },
10411
+ {
10412
+ id: "lemlist",
10413
+ label: "lemlist",
10414
+ channels: ["email", "linkedin"],
10415
+ headers: { first_name: "firstName", last_name: "lastName", company: "companyName", linkedin_url: "linkedinUrl" },
10416
+ where: "In your campaign, open Leads list > Import new leads > CSV.",
10417
+ match: (c) => `${key(c, "email", "linkedinUrl")} and the name columns map on their own. ${cols(c, "become custom variables")}.`,
10418
+ insert: PASTE,
10419
+ maxValue: 2e3,
10420
+ variable: braces
10421
+ },
10422
+ {
10423
+ id: "heyreach",
10424
+ label: "HeyReach",
10425
+ channels: ["linkedin"],
10426
+ needsNames: true,
10427
+ where: "Leads > Add Leads > Import from CSV.",
10428
+ match: () => "Map linkedin_url to the profile URL. Add a custom variable from amdahl_message.",
10429
+ insert: "Add amdahl_message from the Add variable menu, or paste",
10430
+ note: "Set a fallback message on the step: HeyReach sends it when a lead has no value. Its docs do not show the brace syntax, so use the Add variable menu if the pasted text does not turn orange.",
10431
+ variable: single
10432
+ },
10433
+ {
10434
+ id: "reply_io",
10435
+ label: "Reply.io",
10436
+ channels: ["email", "linkedin"],
10437
+ where: "Add contacts > Import from a CSV file.",
10438
+ match: (c) => `Map ${key(c, "email to Email", "linkedin_url to LinkedIn")}. Create a custom field for ${c === "email" ? "each of amdahl_subject and amdahl_message" : "amdahl_message"}.`,
10439
+ insert: PASTE,
10440
+ variable: braces
10441
+ },
10442
+ {
10443
+ id: "apollo",
10444
+ label: "Apollo",
10445
+ channels: ["email"],
10446
+ where: "Import a CSV of contacts.",
10447
+ match: () => "Map email to Email. Create a custom contact field for amdahl_subject and one for amdahl_message, then map to them.",
10448
+ insert: MENU,
10449
+ variable: name
10450
+ },
10451
+ {
10452
+ id: "outreach",
10453
+ label: "Outreach",
10454
+ channels: ["email"],
10455
+ where: "Actions > Bulk Create > Prospects.",
10456
+ match: () => "Map email to Email, amdahl_subject to Custom 1 and amdahl_message to Custom 2.",
10457
+ insert: PASTE,
10458
+ variable: (column) => column === "amdahl_subject" ? "{{custom1}}" : "{{custom2}}"
10459
+ },
10460
+ {
10461
+ id: "salesloft",
10462
+ label: "Salesloft",
10463
+ channels: ["email"],
10464
+ where: "People > Create People > Import from CSV.",
10465
+ match: () => "Map email to Email Address. An admin first adds a person field for amdahl_subject and one for amdahl_message, then you map to them.",
10466
+ insert: MENU,
10467
+ variable: name
10468
+ },
10469
+ {
10470
+ id: "hubspot",
10471
+ label: "HubSpot",
10472
+ channels: ["email"],
10473
+ where: "Contacts > Import, then upload the CSV.",
10474
+ match: () => "Map email to Email. Choose Create new property for amdahl_subject and for amdahl_message.",
10475
+ insert: "Add them with Personalize in your sequence email",
10476
+ note: "HubSpot sequences can drop line breaks from a multi-line property, so check one email before you enroll.",
10477
+ variable: name
10478
+ },
10479
+ {
10480
+ id: "lgm",
10481
+ label: "La Growth Machine",
10482
+ channels: ["linkedin"],
10483
+ where: "Leads > Import leads > Import CSV.",
10484
+ match: () => "Map linkedin_url to the profile URL. Import amdahl_message as a custom attribute.",
10485
+ insert: "Add it from the variable menu in your step",
10486
+ note: "La Growth Machine does not document the exact form of a custom attribute, so copy it from its menu.",
10487
+ variable: name
10488
+ },
10489
+ {
10490
+ id: "woodpecker",
10491
+ label: "Woodpecker",
10492
+ channels: ["email"],
10493
+ where: "In your campaign, choose Add prospects > Import.",
10494
+ match: () => "Map email to Email, amdahl_subject to Snippet 1 and amdahl_message to Snippet 2.",
10495
+ insert: "Add them with the {{ }} button in your step",
10496
+ variable: (column) => column === "amdahl_subject" ? "Snippet 1" : "Snippet 2"
10497
+ },
10498
+ {
10499
+ id: "other",
10500
+ label: "Other (any tool)",
10501
+ channels: ["email", "linkedin"],
10502
+ where: "Use the CSV import for leads in your tool.",
10503
+ match: (c) => `${key(c, "Map email to your email field", "Map linkedin_url to your profile URL field")}, and ${cols(c, "to custom fields")}.`,
10504
+ insert: "Paste into your step (most tools use this form)",
10505
+ variable: braces
10506
+ }
10507
+ ];
10508
+ function presetFor(value) {
10509
+ const id = value.trim().toLowerCase().replace(/-/g, "_");
10510
+ return SEND_TO_PRESETS.find((p) => p.id === id);
10511
+ }
10512
+ var subjectColumn = "amdahl_subject";
10513
+ var messageColumn = "amdahl_message";
10514
+ var LEAD_FIELDS = [
10515
+ { key: "email", names: ["email", "email_address", "work_email", "e_mail", "contact_email", "person_email", "business_email"] },
10516
+ { key: "first", names: ["first_name", "firstname", "first", "given_name", "contact_first_name"] },
10517
+ { key: "last", names: ["last_name", "lastname", "last", "surname", "family_name", "contact_last_name"] },
10518
+ { key: "name", names: ["name", "full_name", "contact_name", "person_name", "contact"] },
10519
+ { key: "company", names: ["company", "company_name", "account", "account_name", "organization", "organisation", "organization_name"] },
10520
+ { key: "linkedin", names: ["linkedin", "linkedin_url", "linkedin_profile", "person_linkedin_url", "li_url", "linkedin_profile_url"] }
10521
+ ];
10522
+ var norm = (h) => h.replace(/^\uFEFF/, "").trim().toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "_").replace(/^_+|_+$/g, "");
10523
+ function autoMap(headers) {
10524
+ const n = headers.map(norm);
10525
+ const map = {};
10526
+ for (const f of LEAD_FIELDS) map[f.key] = n.findIndex((h) => f.names.includes(h));
10527
+ return map;
10528
+ }
10529
+ function leadOf(cells, map) {
10530
+ const cell3 = (k) => map[k] >= 0 ? (cells[map[k]] ?? "").trim() : "";
10531
+ const first2 = cell3("first");
10532
+ return { cells, email: cell3("email"), name: cell3("name") || [first2, cell3("last")].filter(Boolean).join(" "), company: cell3("company"), linkedin: cell3("linkedin") };
10533
+ }
10534
+ function stripUnverifiedMarkers(text3) {
10535
+ const open2 = "<unverified>";
10536
+ const close = "</unverified>";
10537
+ if (!text3.includes(open2) && !text3.includes(close)) return text3;
10538
+ return text3.replace(/<unverified>([\s\S]*?)<\/unverified>/g, "$1").split(open2).join("").split(close).join("");
10539
+ }
10540
+ function splitSubject(text3) {
10541
+ const m = text3.match(/^\s*subject\s*:\s*(.*)\r?\n/i);
10542
+ if (!m) return { subject: null, body: text3.trim() };
10543
+ return { subject: m[1]?.trim() || null, body: text3.slice(m[0].length).trim() };
10544
+ }
10545
+ function writtenOf(item) {
10546
+ const result = item?.result && typeof item.result === "object" && !Array.isArray(item.result) ? item.result : null;
10547
+ if (item?.status === "failed" || result?.ok === false) return { kind: "failed" };
10548
+ if (!item || item.status !== "done" || !result) return { kind: "unwritten" };
10549
+ if (result.outcome === "needs_input") return { kind: "needs_input" };
10550
+ if (typeof result.message !== "string") return { kind: "unwritten" };
10551
+ const { subject, body } = splitSubject(stripUnverifiedMarkers(result.message));
10552
+ return { kind: "written", subject: subject ?? "", body };
10553
+ }
10554
+ function cell(value) {
10555
+ return /[",\n\r]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
10556
+ }
10557
+ var AMDAHL_INPUT_COLUMNS = ["message", "channel", "evidence", "on_unsupported", "facts", "rules", "item_id"];
10558
+ var SUBJECT_HEADERS = ["subject", "subject_line", "email_subject", "subjectline", "betreff", "objet", "sujet", "asunto", "oggetto", "onderwerp"];
10559
+ var CHANNEL_HEADERS = ["channel", "type", "medium"];
10560
+ var ADDRESS = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
10561
+ var isDraftHeader = (h) => ["message", "draft", "email_copy"].includes(h) || /(^|_)body(_html|_text)?$/.test(h);
10562
+ function draftColumn(headers, rows) {
10563
+ const n = headers.map(norm);
10564
+ const subjectCol = n.findIndex((h) => SUBJECT_HEADERS.includes(h));
10565
+ const channelCol = n.findIndex((h) => CHANNEL_HEADERS.includes(h));
10566
+ const onlyAddresses = (c) => {
10567
+ const cells = rows.map((r) => (r[c] ?? "").trim()).filter(Boolean);
10568
+ return cells.length > 0 && cells.every((v) => ADDRESS.test(v));
10569
+ };
10570
+ return n.findIndex((h, i) => isDraftHeader(h) && i !== subjectCol && i !== channelCol && !onlyAddresses(i));
10571
+ }
10572
+ function snakeHeader(header) {
10573
+ return header.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
10574
+ }
10575
+ function sendToFileName(listPath, preset) {
10576
+ const list2 = basename2(listPath).replace(/\.(csv|tsv|txt)$/i, "");
10577
+ const base = snakeHeader(list2).replace(/_/g, "-") || "leads";
10578
+ return `${base}-${preset.id.replace(/_/g, "-")}.csv`;
10579
+ }
10580
+ function namesOf(lead, map) {
10581
+ const at = (field3) => map[field3] >= 0 ? (lead.cells[map[field3]] ?? "").trim() : "";
10582
+ const parts = lead.name.split(/\s+/).filter(Boolean);
10583
+ return { first: at("first") || parts[0] || "", last: at("last") || (map.last >= 0 ? "" : parts.slice(1).join(" ")) };
10584
+ }
10585
+ function hasContact(lead, map, channel, preset) {
10586
+ if (channel === "email") return Boolean(lead.email);
10587
+ if (!lead.linkedin) return false;
10588
+ if (!preset.needsNames) return true;
10589
+ const { first: first2, last } = namesOf(lead, map);
10590
+ return Boolean(first2 && last);
10591
+ }
10592
+ function identityColumns(map, preset) {
10593
+ const used = /* @__PURE__ */ new Set();
10594
+ const columns = [];
10595
+ const head = (k) => preset.headers?.[k] ?? k;
10596
+ const take = (...fields) => fields.forEach((f) => map[f] >= 0 && used.add(map[f]));
10597
+ if (map.email >= 0) {
10598
+ take("email");
10599
+ columns.push({ name: "email", value: (_, l) => l.email });
10600
+ }
10601
+ if (map.name >= 0 || map.first >= 0) {
10602
+ take("first", "last");
10603
+ columns.push({ name: head("first_name"), value: (_, l) => namesOf(l, map).first });
10604
+ columns.push({ name: head("last_name"), value: (_, l) => namesOf(l, map).last });
10605
+ }
10606
+ if (map.company >= 0) {
10607
+ take("company");
10608
+ columns.push({ name: head("company"), value: (_, l) => l.company });
10609
+ }
10610
+ if (map.linkedin >= 0) {
10611
+ take("linkedin");
10612
+ columns.push({ name: head("linkedin_url"), value: (_, l) => l.linkedin });
10613
+ }
10614
+ return { columns, used };
10615
+ }
10616
+ function otherColumns(headers, used, taken, draft) {
10617
+ const out = [];
10618
+ headers.forEach((header, i) => {
10619
+ if (used.has(i) || i === draft) return;
10620
+ const base = snakeHeader(header) || `column_${i + 1}`;
10621
+ if (AMDAHL_INPUT_COLUMNS.includes(base)) return;
10622
+ let columnName = base;
10623
+ for (let n = 2; taken.has(columnName); n += 1) columnName = `${base}_${n}`;
10624
+ taken.add(columnName);
10625
+ out.push({ name: columnName, value: (_, l) => l.cells[i] ?? "" });
10626
+ });
10627
+ return out;
10628
+ }
10629
+ function buildSendToFile({ path, headers: raw, rows, channel, preset }) {
10630
+ const headers = raw.map((h) => h.replace(/^\uFEFF/, "").trim());
10631
+ const map = autoMap(headers);
10632
+ const leftOut = { failed: 0, unwritten: 0, needsInput: 0, noContact: 0 };
10633
+ const ready = [];
10634
+ for (const row of rows) {
10635
+ const lead = leadOf(row.cells, map);
10636
+ const written = row.written;
10637
+ if (written.kind === "failed") leftOut.failed += 1;
10638
+ else if (written.kind === "unwritten") leftOut.unwritten += 1;
10639
+ else if (written.kind === "needs_input") leftOut.needsInput += 1;
10640
+ else if (!hasContact(lead, map, channel, preset)) leftOut.noContact += 1;
10641
+ else ready.push({ written, lead });
10642
+ }
10643
+ const identity = identityColumns(map, preset);
10644
+ const taken = /* @__PURE__ */ new Set([...identity.columns.map((c) => c.name), subjectColumn, messageColumn]);
10645
+ const columns = [
10646
+ ...identity.columns,
10647
+ ...channel === "email" ? [{ name: subjectColumn, value: (w) => w.subject }] : [],
10648
+ { name: messageColumn, value: (w) => w.body },
10649
+ ...otherColumns(headers, identity.used, taken, draftColumn(headers, rows.map((r) => r.cells)))
10650
+ ];
10651
+ const lines = ready.map(({ written, lead }) => columns.map((c) => cell(c.value(written, lead).replace(/\r\n?/g, "\n"))).join(","));
10652
+ const overLimit = preset.maxValue ? ready.filter(({ written }) => written.body.length > (preset.maxValue ?? 0)).length : 0;
10653
+ return {
10654
+ name: sendToFileName(path, preset),
10655
+ csv: [columns.map((c) => c.name).join(","), ...lines].join("\r\n"),
10656
+ ready: ready.length,
10657
+ leftOut,
10658
+ overLimit
10659
+ };
10660
+ }
10661
+ function leftOutLine(left, channel, preset) {
10662
+ const total = left.failed + left.unwritten + left.needsInput + left.noContact;
10663
+ if (total === 0) return null;
10664
+ const missing = channel === "email" ? "email" : preset.needsNames ? "LinkedIn URL or full name" : "LinkedIn URL";
10665
+ const parts = [
10666
+ left.failed ? `${left.failed} failed` : null,
10667
+ left.unwritten ? `${left.unwritten} not written yet` : null,
10668
+ left.needsInput ? `${left.needsInput} needed input` : null,
10669
+ left.noContact ? `${left.noContact} with no ${missing}` : null
10670
+ ].filter((p) => p !== null);
10671
+ return `${total} left out: ${parts.join(", ")}.`;
10672
+ }
10673
+ function guideLines(preset, channel) {
10674
+ const email = channel === "email";
10675
+ const variables = [...email ? [`Subject ${preset.variable(subjectColumn)}`] : [], `${email ? "Body" : "Message"} ${preset.variable(messageColumn)}`];
10676
+ return [`Import: ${preset.where}`, preset.match(channel), `${preset.insert}: ${variables.join(", ")}`];
10677
+ }
10678
+ function readyLine(ready, preset) {
10679
+ return `${ready} ${ready === 1 ? "message" : "messages"} ready ${preset.id === "other" ? "to import" : `for ${preset.label}`}`;
10680
+ }
10681
+ function overLimitLine(over, preset) {
10682
+ return `${over} ${over === 1 ? "message is" : "messages are"} over ${preset.label}'s ${(preset.maxValue ?? 0).toLocaleString("en-US")} character limit for a variable.`;
10683
+ }
10684
+ var NO_FILE = ["gmail", "outlook"];
10685
+ function toolFor(value) {
10686
+ const preset = presetFor(value);
10687
+ if (preset) return preset;
10688
+ const lower = value.trim().toLowerCase();
10689
+ if (NO_FILE.includes(lower)) {
10690
+ throw usageError(`${lower === "gmail" ? "Gmail" : "Outlook"} sends from a mailbox, so it has no import file.`, "Use --for other for any tool that imports a CSV.");
10691
+ }
10692
+ throw usageError(`--for must be one of: ${SEND_TO_IDS.join(", ")}.`, "Use other for a tool that is not listed.");
10693
+ }
10694
+ function planSendTo(preset, list2, flagChannel) {
10695
+ if (list2.kind !== "csv" || !list2.header) {
10696
+ throw usageError("--for needs a CSV list: the import file keeps your list's columns.", `${list2.path} is not a CSV. Make one, or leave --for out.`);
10697
+ }
10698
+ const valid = (c) => c === "email" || c === "linkedin";
10699
+ const own = new Set(list2.items.map((i) => i.channel).filter(valid));
10700
+ if (valid(flagChannel)) own.add(flagChannel);
10701
+ if (own.size > 1) {
10702
+ throw usageError("--for writes one channel at a time, and this list has both email and LinkedIn rows.", `Split ${list2.path} by channel, and run each part with --for.`);
10703
+ }
10704
+ const only = preset.channels.length === 1 ? preset.channels[0] : void 0;
10705
+ const channel = [...own][0] ?? only;
10706
+ if (!channel) {
10707
+ throw usageError(`${preset.label} takes email and LinkedIn lists, so --for needs to know which this is.`, "Add --channel email or --channel linkedin.");
10708
+ }
10709
+ if (!preset.channels.includes(channel)) {
10710
+ throw usageError(`${preset.label} takes ${preset.channels[0] === "email" ? "email" : "LinkedIn"} lists only, and this one is ${channel === "email" ? "email" : "LinkedIn"}.`, "Pick a tool for this channel, or use --for other.");
10711
+ }
10712
+ const map = autoMap(list2.header);
10713
+ if (channel === "email" && map.email < 0) {
10714
+ throw usageError(`${list2.path} has no email column.`, "Name the column email, email_address or work_email, so the tool can import it.");
10715
+ }
10716
+ if (channel === "linkedin" && map.linkedin < 0) {
10717
+ throw usageError(`${list2.path} has no LinkedIn URL column.`, "Name the column linkedin_url, linkedin or linkedin_profile, so the tool can import it.");
10718
+ }
10719
+ if (channel === "linkedin" && preset.needsNames && map.name < 0 && !(map.first >= 0 && map.last >= 0)) {
10720
+ throw usageError(`${preset.label} needs a first and a last name for each lead, and ${list2.path} has no name columns.`, "Add first_name and last_name columns, or one name column.");
10721
+ }
10722
+ return { preset, channel };
10723
+ }
10252
10724
 
10253
10725
  // src/commands/batch.ts
10254
- import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
10255
- import { basename as basename2, dirname as dirname4, extname, join as join7 } from "path";
10256
10726
  var PLATFORM4 = "/api/platform/v1";
10257
10727
  var BATCH_WAIT_MS = 15e3;
10258
10728
  var PAGE = 100;
@@ -10267,52 +10737,52 @@ function parseCsv(text3) {
10267
10737
  const rows = [];
10268
10738
  const lines = [];
10269
10739
  let row = [];
10270
- let cell2 = "";
10740
+ let cell3 = "";
10271
10741
  let quoted = false;
10272
10742
  let line = 1;
10273
10743
  let rowLine = 1;
10274
10744
  let touched = false;
10275
10745
  const src = text3.replace(/^\uFEFF/, "");
10276
10746
  const endRow = () => {
10277
- row.push(cell2);
10278
- if (touched || row.length > 1 || cell2 !== "") {
10747
+ row.push(cell3);
10748
+ if (touched || row.length > 1 || cell3 !== "") {
10279
10749
  rows.push(row);
10280
10750
  lines.push(rowLine);
10281
10751
  }
10282
10752
  row = [];
10283
- cell2 = "";
10753
+ cell3 = "";
10284
10754
  touched = false;
10285
10755
  };
10286
10756
  for (let i = 0; i < src.length; i++) {
10287
10757
  const c = src[i];
10288
10758
  if (quoted) {
10289
10759
  if (c === '"' && src[i + 1] === '"') {
10290
- cell2 += '"';
10760
+ cell3 += '"';
10291
10761
  i++;
10292
10762
  } else if (c === '"') quoted = false;
10293
10763
  else if (c === "\r" && src[i + 1] === "\n") continue;
10294
10764
  else {
10295
10765
  if (c === "\n") line++;
10296
- cell2 += c;
10766
+ cell3 += c;
10297
10767
  }
10298
10768
  continue;
10299
10769
  }
10300
- if (c === '"' && cell2 === "") {
10770
+ if (c === '"' && cell3 === "") {
10301
10771
  quoted = true;
10302
10772
  touched = true;
10303
10773
  } else if (c === ",") {
10304
- row.push(cell2);
10305
- cell2 = "";
10774
+ row.push(cell3);
10775
+ cell3 = "";
10306
10776
  touched = true;
10307
10777
  } else if (c === "\n" || c === "\r") {
10308
10778
  if (c === "\r" && src[i + 1] === "\n") i++;
10309
10779
  endRow();
10310
10780
  line++;
10311
10781
  rowLine = line;
10312
- } else cell2 += c;
10782
+ } else cell3 += c;
10313
10783
  }
10314
10784
  if (quoted) throw usageError(`A quoted CSV field that starts on line ${rowLine} never ends.`);
10315
- if (cell2 !== "" || row.length > 0 || touched) endRow();
10785
+ if (cell3 !== "" || row.length > 0 || touched) endRow();
10316
10786
  return { rows, lines };
10317
10787
  }
10318
10788
  function csvCell(value) {
@@ -10321,36 +10791,39 @@ function csvCell(value) {
10321
10791
  function toCsv(rows) {
10322
10792
  return rows.map((r) => r.map(csvCell).join(",")).join("\n") + "\n";
10323
10793
  }
10324
- function perLine(cell2) {
10325
- return cell2.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
10794
+ function perLine(cell3) {
10795
+ return cell3.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
10326
10796
  }
10327
10797
  function csvList(path, text3) {
10328
10798
  const { rows, lines } = parseCsv(text3);
10329
10799
  const [header, ...body] = rows;
10330
10800
  if (!header) throw usageError(`${path} is empty.`);
10331
10801
  const names = header.map((h) => h.trim().toLowerCase());
10332
- const unknown = names.find((n) => !CSV_COLUMNS.includes(n));
10333
- const headers = `The first row must name the columns, from: ${CSV_COLUMNS.join(", ")} (message is required). For any other field, use a JSONL file.`;
10334
- if (unknown !== void 0) {
10335
- throw usageError(`${path} has a column Amdahl doesn't read: "${clip(unknown, 40)}".`, headers);
10802
+ const twice = CSV_COLUMNS.find((c) => names.indexOf(c) !== names.lastIndexOf(c));
10803
+ if (twice !== void 0) throw usageError(`${path} has the column "${twice}" twice.`);
10804
+ if (!names.includes("message")) {
10805
+ throw usageError(
10806
+ `${path} needs a "message" column.`,
10807
+ `The first row must name the columns. Required: message. Read: ${CSV_COLUMNS.filter((c) => c !== "message").join(", ")}. Any other column is copied to the results as it is.`
10808
+ );
10336
10809
  }
10337
- if (!names.includes("message")) throw usageError(`${path} needs a "message" column.`, headers);
10810
+ const ignored = header.map((h, i) => ({ name: h.trim(), known: CSV_COLUMNS.includes(names[i] ?? "") })).filter((c) => !c.known && c.name).map((c) => clip(c.name, 30));
10338
10811
  const items = body.map((row, i) => {
10339
10812
  if (row.length !== names.length) {
10340
10813
  throw usageError(`Line ${lines[i + 1]} of ${path} has ${count(row.length, "cell")}; the header has ${names.length}.`);
10341
10814
  }
10342
10815
  const item = {};
10343
10816
  const context = {};
10344
- names.forEach((name, j) => {
10817
+ names.forEach((name2, j) => {
10345
10818
  const value = row[j] ?? "";
10346
- if (!value.trim()) return;
10347
- if (name === "facts" || name === "rules") context[name] = perLine(value);
10348
- else item[name] = name === "message" ? value : value.trim();
10819
+ if (!CSV_COLUMNS.includes(name2) || !value.trim()) return;
10820
+ if (name2 === "facts" || name2 === "rules") context[name2] = perLine(value);
10821
+ else item[name2] = name2 === "message" ? value : name2 === "channel" ? value.trim().toLowerCase() : value.trim();
10349
10822
  });
10350
10823
  if (Object.keys(context).length > 0) item.context = context;
10351
10824
  return item;
10352
10825
  });
10353
- return { path, kind: "csv", items, header, rows: body, lines: lines.slice(1) };
10826
+ return { path, kind: "csv", items, header, rows: body, ignored, lines: lines.slice(1) };
10354
10827
  }
10355
10828
  function jsonlList(path, text3) {
10356
10829
  const items = [];
@@ -10371,12 +10844,7 @@ function jsonlList(path, text3) {
10371
10844
  return { path, kind: "jsonl", items, lines };
10372
10845
  }
10373
10846
  function readList(path) {
10374
- let text3;
10375
- try {
10376
- text3 = readFileSync6(path, "utf8");
10377
- } catch (err) {
10378
- throw usageError(`Could not read ${path}: ${err instanceof Error ? err.message : String(err)}`);
10379
- }
10847
+ const text3 = readTextFile(path);
10380
10848
  const ext = extname(path).toLowerCase();
10381
10849
  const jsonl = ext === ".jsonl" || ext === ".ndjson" || ext !== ".csv" && text3.trimStart().startsWith("{");
10382
10850
  const list2 = jsonl ? jsonlList(path, text3) : csvList(path, text3);
@@ -10385,19 +10853,23 @@ function readList(path) {
10385
10853
  }
10386
10854
  function resultsPath(path, kind) {
10387
10855
  const ext = extname(path);
10388
- const stem = basename2(path, ext);
10856
+ const stem = basename3(path, ext);
10389
10857
  return join7(dirname4(path), `${stem}.optimized.${kind === "csv" ? "csv" : "jsonl"}`);
10390
10858
  }
10391
10859
  function withDefaults(items, defaults) {
10392
10860
  return items.map((item) => {
10393
10861
  const merged = { ...item };
10394
- for (const [key, value] of Object.entries(defaults)) if (merged[key] === void 0) merged[key] = value;
10862
+ for (const [key2, value] of Object.entries(defaults)) if (merged[key2] === void 0) merged[key2] = value;
10395
10863
  const shared = record(defaults.context);
10396
10864
  const own = record(item.context);
10397
10865
  if (shared && own) merged.context = { ...shared, ...own };
10398
10866
  return merged;
10399
10867
  });
10400
10868
  }
10869
+ function progressKey(batch) {
10870
+ const c = record(batch.counts) ?? {};
10871
+ return ["total", "queued", "running", "done", "failed"].map((k) => typeof c[k] === "number" ? c[k] : 0).join("/");
10872
+ }
10401
10873
  function progressLine(batch) {
10402
10874
  const c = record(batch.counts) ?? {};
10403
10875
  const n = (k) => typeof c[k] === "number" ? c[k] : 0;
@@ -10409,7 +10881,7 @@ function progressLine(batch) {
10409
10881
  const left = seconds === null || finished === total ? "" : seconds < 90 ? ", about a minute left" : `, about ${Math.round(seconds / 60)} min left`;
10410
10882
  if (finished === 0 && n("running") === 0) {
10411
10883
  const ahead = typeof batch.queue_position === "number" && batch.queue_position > 0 ? ` behind ${count(batch.queue_position, "item")}` : "";
10412
- return `Queued${ahead}: 0 of ${total} done${left}`;
10884
+ return `Queued${ahead}${left}`;
10413
10885
  }
10414
10886
  return `${n("done")} of ${total} done${failed}${left}`;
10415
10887
  }
@@ -10417,12 +10889,14 @@ async function waitForBatch(ctx, credential, id, first2) {
10417
10889
  const client = apiClient(ctx, credential);
10418
10890
  const tty = ctx.io.stderrIsTTY && !ctx.out.json;
10419
10891
  let last = "";
10892
+ let lastKey = "";
10420
10893
  const show = (batch2) => {
10421
10894
  const line = progressLine(batch2);
10422
10895
  if (line === last) return;
10423
10896
  last = line;
10424
10897
  if (tty) ctx.io.stderr(`\r\x1B[2K${line}`);
10425
- else ctx.out.info(line);
10898
+ else if (progressKey(batch2) !== lastKey) ctx.out.info(line);
10899
+ lastKey = progressKey(batch2);
10426
10900
  };
10427
10901
  let batch = first2;
10428
10902
  for (; ; ) {
@@ -10451,16 +10925,25 @@ async function allItems(ctx, credential, id) {
10451
10925
  } while (cursor);
10452
10926
  return { batch, items };
10453
10927
  }
10454
- function neutralise(cell2) {
10455
- return /^[=+\-@\t\r]/.test(cell2) ? `'${cell2}` : cell2;
10928
+ function neutralise(cell3) {
10929
+ return /^[=+\-@\t\r]/.test(cell3) ? `'${cell3}` : cell3;
10930
+ }
10931
+ var RESULT_COLUMNS = ["optimized_message", "status", "summary", "run_id", "error"];
10932
+ function resultColumnNames(header) {
10933
+ const taken = new Set(header.map((h) => h.trim().toLowerCase()));
10934
+ return RESULT_COLUMNS.map((name2) => {
10935
+ let candidate = name2;
10936
+ while (taken.has(candidate)) candidate = `amdahl_${candidate}`;
10937
+ taken.add(candidate);
10938
+ return candidate;
10939
+ });
10456
10940
  }
10457
- var RESULT_COLUMNS = ["optimized_message", "status", "what_changed", "run_id", "error"];
10458
- function resultCells(item) {
10941
+ function resultCells(item, context) {
10459
10942
  const result = record(item?.result);
10460
10943
  if (!item || !result) return ["", field(item?.status) || "missing", "", "", ""];
10461
10944
  if (result.ok === false) return ["", "failed", "", field(result.run_id), `${field(result.reason)}: ${field(result.detail)}`.replace(/: $/, "")];
10462
10945
  const message2 = typeof result.message === "string" ? result.message : "";
10463
- return [message2, result.unchanged === true ? "unchanged" : "done", field(result.what_changed), field(result.run_id), ""];
10946
+ return [message2, result.unchanged === true ? "unchanged" : "done", truthfulSummary(field(result.summary), context), field(result.run_id), ""];
10464
10947
  }
10465
10948
  function resultsFile(list2, id, cwd) {
10466
10949
  if (list2) return resultsPath(list2.path, list2.kind);
@@ -10469,18 +10952,67 @@ function resultsFile(list2, id, cwd) {
10469
10952
  function assertFree(path, force) {
10470
10953
  if (!force && existsSync5(path)) throw usageError(`${path} already exists.`, "Move it, or add --force to replace it.");
10471
10954
  }
10472
- function writeResults(list2, id, items, cwd, force) {
10955
+ function writeNew(path, text3, force, pickUp) {
10956
+ try {
10957
+ writeFileSync4(path, text3, { flag: force ? "w" : "wx" });
10958
+ } catch (err) {
10959
+ if (err?.code === "EEXIST") {
10960
+ throw usageError(`${path} already exists.`, `Move it, or add --force to replace it. The batch is done, and you can read it again: ${pickUp}`);
10961
+ }
10962
+ throw err;
10963
+ }
10964
+ }
10965
+ function writeResults(list2, id, items, cwd, force, contexts, pickUp) {
10473
10966
  const byIndex = new Map(items.map((i) => [i.index, i]));
10474
10967
  const path = resultsFile(list2, id, cwd);
10475
- const flag = force ? "w" : "wx";
10476
10968
  if (list2?.kind === "csv" && list2.header && list2.rows) {
10477
- const rows = [[...list2.header, ...RESULT_COLUMNS], ...list2.rows.map((row, i) => [...row, ...resultCells(byIndex.get(i))])];
10478
- writeFileSync4(path, toCsv(rows.map((row) => row.map(neutralise))), { flag });
10969
+ const rows = [[...list2.header, ...resultColumnNames(list2.header)], ...list2.rows.map((row, i) => [...row, ...resultCells(byIndex.get(i), contexts[i])])];
10970
+ writeNew(path, toCsv(rows.map((row) => row.map(neutralise))), force, pickUp);
10479
10971
  return path;
10480
10972
  }
10481
- writeFileSync4(path, items.map((i) => JSON.stringify(i)).join("\n") + "\n", { flag });
10973
+ writeNew(path, items.map((i) => JSON.stringify(i)).join("\n") + "\n", force, pickUp);
10482
10974
  return path;
10483
10975
  }
10976
+ function writeSendTo(plan, list2, items, force, pickUp) {
10977
+ const byIndex = new Map(items.map((i) => [i.index, i]));
10978
+ const built = buildSendToFile({
10979
+ path: list2.path,
10980
+ headers: list2.header ?? [],
10981
+ rows: (list2.rows ?? []).map((cells, i) => ({ cells, written: writtenOf(byIndex.get(i)) })),
10982
+ channel: plan.channel,
10983
+ preset: plan.preset
10984
+ });
10985
+ if (built.ready === 0) return { built, path: null };
10986
+ const path = join7(dirname4(list2.path), built.name);
10987
+ writeNew(path, built.csv, force, pickUp);
10988
+ return { built, path };
10989
+ }
10990
+ function sendToJson(plan, sent2) {
10991
+ const { leftOut, ready, overLimit } = sent2.built;
10992
+ return {
10993
+ tool: plan.preset.id,
10994
+ channel: plan.channel,
10995
+ file: sent2.path,
10996
+ ready,
10997
+ left_out: { failed: leftOut.failed, not_written: leftOut.unwritten, needs_input: leftOut.needsInput, no_contact: leftOut.noContact },
10998
+ over_limit: overLimit
10999
+ };
11000
+ }
11001
+ function printSendTo(ctx, plan, sent2) {
11002
+ const { out } = ctx;
11003
+ const { preset, channel } = plan;
11004
+ const left = leftOutLine(sent2.built.leftOut, channel, preset);
11005
+ if (!sent2.path) {
11006
+ out.info(`Nothing to send to ${preset.label}.${left ? ` ${left}` : ""}`);
11007
+ return;
11008
+ }
11009
+ out.result({}, `Import file${preset.id === "other" ? "" : ` for ${preset.label}`}: ${sent2.path}`);
11010
+ out.info(`${readyLine(sent2.built.ready, preset)}.${left ? ` ${left}` : ""}`);
11011
+ const steps = guideLines(preset, channel).map((line, i) => `${i + 1}. ${line}`);
11012
+ const notes = [preset.note, sent2.built.overLimit > 0 ? overLimitLine(sent2.built.overLimit, preset) : void 0].filter((n) => Boolean(n));
11013
+ out.info(wrap(steps.join("\n"), COLUMNS, " ").join("\n"));
11014
+ for (const note of notes) out.info(wrap(note, COLUMNS).join("\n"));
11015
+ }
10484
11016
  function withLine(err, list2) {
10485
11017
  if (!(err instanceof CliError)) return err;
10486
11018
  const index = /\$\.items\[(\d+)\]/.exec(err.message)?.[1];
@@ -10488,7 +11020,8 @@ function withLine(err, list2) {
10488
11020
  if (line === void 0) return err;
10489
11021
  return new CliError(err.code, err.message, err.exit, `items[${index}] is line ${line} of ${list2.path}.`, err.detail, err.data);
10490
11022
  }
10491
- async function optimizeList(ctx, defaults) {
11023
+ async function optimizeList(ctx, requested) {
11024
+ let defaults = requested;
10492
11025
  const { out, flags } = ctx;
10493
11026
  const listPath = str(flags.list);
10494
11027
  const resume = str(flags.batch);
@@ -10499,12 +11032,26 @@ async function optimizeList(ctx, defaults) {
10499
11032
  if (resume && !BATCH_ID.test(resume)) {
10500
11033
  throw usageError("--batch needs a batch id: a UUID like the one `amdahl optimize --list` printed.");
10501
11034
  }
11035
+ const forFlag = str(flags.for);
11036
+ if (typeof flags.for === "string" && !forFlag) throw usageError("--for needs a tool.", `One of: ${SEND_TO_IDS.join(", ")}.`);
11037
+ if (forFlag && !listPath) throw usageError("--for needs the list: add --list <file.csv>.");
11038
+ const preset = forFlag ? toolFor(forFlag) : null;
10502
11039
  const list2 = listPath ? readList(listPath) : null;
11040
+ const plan = preset && list2 ? planSendTo(preset, list2, typeof defaults.channel === "string" ? defaults.channel : void 0) : null;
11041
+ if (plan) defaults = { ...defaults, channel: defaults.channel ?? plan.channel };
11042
+ const forPart = plan ? ` --for ${plan.preset.id}${plan.preset.channels.length > 1 ? ` --channel ${plan.channel}` : ""}` : "";
11043
+ const pickUpFor = (batchId) => amdahlCommand(`optimize ${listPath ? `--list ${shellWord(listPath, ctx.io.platform)} ` : ""}--batch ${batchId}${forPart}`);
11044
+ if (list2?.ignored?.length) {
11045
+ const shown = list2.ignored.slice(0, 6).join(", ");
11046
+ const more = list2.ignored.length > 6 ? ` and ${list2.ignored.length - 6} more` : "";
11047
+ out.info(`Not sent, and copied to the results as they are: ${count(list2.ignored.length, "column")} Amdahl does not read (${shown}${more}).`);
11048
+ }
10503
11049
  if (!resume && list2 && list2.items.length > MAX_BATCH_ITEMS) {
10504
11050
  throw usageError(`A batch takes at most ${MAX_BATCH_ITEMS} messages, and ${list2.path} has ${list2.items.length}.`, "Split the list into files of 500 or fewer, and run each one.");
10505
11051
  }
10506
11052
  const force = flags.force === true;
10507
11053
  if (!out.json && flags["no-wait"] !== true) assertFree(resultsFile(list2, resume ?? "", ctx.io.cwd), force);
11054
+ if (plan && list2 && flags["no-wait"] !== true) assertFree(join7(dirname4(list2.path), sendToFileName(list2.path, plan.preset)), force);
10508
11055
  const credential = await resolveCredential(ctx);
10509
11056
  let id = resume ?? "";
10510
11057
  let first2 = null;
@@ -10528,7 +11075,7 @@ async function optimizeList(ctx, defaults) {
10528
11075
  }
10529
11076
  id = field(first2.batch_id);
10530
11077
  if (!id) throw new CliError("no_batch_id", "Amdahl accepted the list but sent no batch id.", EXIT.general);
10531
- const pickUp = amdahlCommand(`optimize --list ${shellWord(listPath ?? "", ctx.io.platform)} --batch ${id}`);
11078
+ const pickUp = pickUpFor(id);
10532
11079
  if (flags["no-wait"] === true) {
10533
11080
  out.result({ ...first2, ok: true }, `Queued ${count(list2.items.length, "message")} as batch ${id}.
10534
11081
  Get the results: ${pickUp}`);
@@ -10543,14 +11090,21 @@ Get the results: ${pickUp}`);
10543
11090
  const { batch, items } = await allItems(ctx, credential, id);
10544
11091
  const failed = items.filter((i) => i.status === "failed" || record(i.result)?.ok === false).length;
10545
11092
  if (out.json) {
10546
- out.result({ ...batch, ok: failed === 0, items, next_cursor: null });
10547
- return failed === 0 ? EXIT.ok : EXIT.general;
11093
+ const sent3 = plan && list2 ? writeSendTo(plan, list2, items, force, pickUpFor(id)) : null;
11094
+ const ok = failed === 0 && (!sent3 || sent3.built.ready > 0);
11095
+ out.result({ ...batch, ok, items, next_cursor: null, ...plan && sent3 ? { send_to: sendToJson(plan, sent3) } : {} });
11096
+ return ok ? EXIT.ok : EXIT.general;
10548
11097
  }
10549
- const path = writeResults(list2, id, items, ctx.io.cwd, force);
11098
+ const contexts = list2 ? withDefaults(list2.items, defaults).map((item) => item.context) : [];
11099
+ const path = writeResults(list2, id, items, ctx.io.cwd, force, contexts, pickUpFor(id));
10550
11100
  const done = items.length - failed;
10551
11101
  out.info(`${done} of ${count(items.length, "message")} optimized${failed ? `, ${failed} failed` : ""}.`);
10552
11102
  out.result({}, `Results: ${path}`);
10553
- return failed === 0 ? EXIT.ok : EXIT.general;
11103
+ const sent2 = plan && list2 ? writeSendTo(plan, list2, items, force, pickUpFor(id)) : null;
11104
+ if (plan && sent2) printSendTo(ctx, plan, sent2);
11105
+ const runId2 = items.map((i) => field(record(i.result)?.run_id)).find(Boolean);
11106
+ for (const line of sentLines(runId2, "Sent one?")) out.info(line);
11107
+ return failed === 0 && (!sent2 || sent2.built.ready > 0) ? EXIT.ok : EXIT.general;
10554
11108
  }
10555
11109
 
10556
11110
  // src/commands/optimize.ts
@@ -10565,11 +11119,12 @@ function oneOf2(flag, value, allowed) {
10565
11119
  return value;
10566
11120
  }
10567
11121
  function readContext(path) {
11122
+ const text3 = readTextFile(path);
10568
11123
  let parsed;
10569
11124
  try {
10570
- parsed = JSON.parse(readFileSync7(path, "utf8"));
11125
+ parsed = JSON.parse(text3);
10571
11126
  } catch (err) {
10572
- throw usageError(`Could not read --context ${path}: ${err instanceof Error ? err.message : String(err)}`);
11127
+ throw usageError(`--context ${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
10573
11128
  }
10574
11129
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
10575
11130
  throw usageError("--context must be a JSON object with rules, voice_examples and facts.");
@@ -10688,7 +11243,6 @@ function whatAmdahlDid(result) {
10688
11243
  const more = whys.length > 1 ? ` (${plural(whys.length - 1, "more reason")} in --json)` : "";
10689
11244
  const leftOut = omitted.length ? `Left out ${plural(omitted.length, "thing")}${whys[0] ? `: ${sentence(whys[0])}` : "."}${more}` : "";
10690
11245
  const lines = [
10691
- { rank: 2, line: sentence(text2(result.what_changed)) },
10692
11246
  { rank: 5, line: checked },
10693
11247
  { rank: 3, line: removed },
10694
11248
  { rank: 6, line: usedLine },
@@ -10800,7 +11354,7 @@ ${nextTimeLine(ctx)}`);
10800
11354
  out.result({}, String(result.message ?? ""));
10801
11355
  const did = whatAmdahlDid(result);
10802
11356
  const connect2 = tipsOn(ctx) && (saysNoData(result) || await noData) ? [noDataLine()] : [];
10803
- out.info([...did, ...feedbackLines(ctx, result), ...connect2, nextTimeLine(ctx)].join("\n"));
11357
+ out.info([...did, ...feedbackLines(ctx, result), ...sentLines(result.run_id), ...connect2, nextTimeLine(ctx)].join("\n"));
10804
11358
  return EXIT.ok;
10805
11359
  }
10806
11360
  async function optimize(ctx, args) {
@@ -10811,6 +11365,7 @@ async function optimize(ctx, args) {
10811
11365
  }
10812
11366
  if (ctx.flags["no-wait"] === true) throw usageError("--no-wait goes with --list <file>.");
10813
11367
  if (ctx.flags.force === true) throw usageError("--force goes with --list <file>.");
11368
+ if (ctx.flags.for !== void 0) throw usageError("--for goes with --list <file.csv>.");
10814
11369
  let files = args;
10815
11370
  if (files.length === 0) {
10816
11371
  if (atTerminal(ctx)) return optimizePasted(ctx);
@@ -10826,12 +11381,7 @@ async function optimize(ctx, args) {
10826
11381
  const base = requestOptions(ctx);
10827
11382
  const inputs = [];
10828
11383
  for (const file of files) {
10829
- let message2;
10830
- try {
10831
- message2 = file === "-" ? await io.readStdin() : readFileSync7(file, "utf8");
10832
- } catch (err) {
10833
- throw usageError(`Could not read ${file}: ${err instanceof Error ? err.message : String(err)}`);
10834
- }
11384
+ const message2 = file === "-" ? await io.readStdin() : readTextFile(file);
10835
11385
  if (!message2.trim()) throw usageError(`${file === "-" ? "Stdin" : file} is empty.`);
10836
11386
  inputs.push({ file, message: message2 });
10837
11387
  }
@@ -10851,9 +11401,9 @@ async function optimize(ctx, args) {
10851
11401
  if (!out.json) {
10852
11402
  if (inputs.length > 1) out.info(`== ${file} ==`);
10853
11403
  if (result.unchanged === true) out.info("No rewrite beat your draft; it is returned unchanged.");
10854
- else if (typeof result.summary === "string") out.info(result.summary);
11404
+ else if (typeof result.summary === "string" && truthfulSummary(result.summary, base.context)) out.info(truthfulSummary(result.summary, base.context));
10855
11405
  out.result({}, String(result.message ?? ""));
10856
- const lines = [...whatAmdahlDid(result), ...feedbackLines(ctx, result)];
11406
+ const lines = [...whatAmdahlDid(result), ...feedbackLines(ctx, result), ...sentLines(result.run_id)];
10857
11407
  if (lines.length) out.info(lines.join("\n"));
10858
11408
  }
10859
11409
  }
@@ -10867,7 +11417,7 @@ var SYNTHESIZED_ID = "answer-text";
10867
11417
  var MAX_TABLE_ROWS = 25;
10868
11418
  var MAX_CELL_LINES = 4;
10869
11419
  var MIN_COLUMN = 6;
10870
- function cell(value) {
11420
+ function cell2(value) {
10871
11421
  if (typeof value === "number") return String(value);
10872
11422
  return typeof value === "string" ? field(value).replace(/\s+/g, " ") : "";
10873
11423
  }
@@ -10943,8 +11493,8 @@ function dataTable(block) {
10943
11493
  const columns = records(block.columns).filter((c) => field(c.key));
10944
11494
  const data = records(block.data);
10945
11495
  if (columns.length === 0 || data.length === 0) return [];
10946
- const labels = columns.map((c) => cell(c.label) || cell(c.key));
10947
- return asciiTable(labels, data.map((row) => columns.map((c) => cell(row[field(c.key)]))));
11496
+ const labels = columns.map((c) => cell2(c.label) || cell2(c.key));
11497
+ return asciiTable(labels, data.map((row) => columns.map((c) => cell2(row[field(c.key)]))));
10948
11498
  }
10949
11499
  function prose(markdown) {
10950
11500
  return plainMarkdown(paragraphs(markdown).replace(/<\s*unverified\s*>([^<]*)<\s*\/\s*unverified\s*>/gi, "$1 (unverified)"));
@@ -10968,7 +11518,7 @@ function renderBlock(block, answer) {
10968
11518
  const data = record(block.data);
10969
11519
  const value = data?.value ?? block.value;
10970
11520
  const delta = field(data?.delta ?? block.delta);
10971
- const shown = value === void 0 ? "" : cell(value);
11521
+ const shown = value === void 0 ? "" : cell2(value);
10972
11522
  return wrap(`${field(block.label)}: ${shown}${delta ? ` (${delta})` : ""}`);
10973
11523
  }
10974
11524
  case "citation": {
@@ -11005,17 +11555,20 @@ var RESEARCH_TIMEOUT_MS = 15 * 6e4;
11005
11555
  var ROWS_UNDER_ANSWER = 5;
11006
11556
  var ROWS_ALONE = 10;
11007
11557
  function researchKeyLine() {
11008
- return `A deeper answer needs a Customer agent key: ${amdahlCommand("keys create --name research --preset agent")}`;
11558
+ return `Research needs a Customer agent key: ${amdahlCommand("keys create --name research --preset agent")}`;
11559
+ }
11560
+ function researchKeyFirst() {
11561
+ return `${researchKeyLine()}. Here is the search answer instead.`;
11009
11562
  }
11010
11563
  var SETTLED = /* @__PURE__ */ new Set(["complete", "failed", "canceled", "needs_input"]);
11011
11564
  var TITLE_FIELDS = ["title", "call_title", "meeting_title", "subject", "deal_name", "account_name", "company_name", "name"];
11012
11565
  var DATE_FIELDS = ["occurred_at", "started_at", "date", "call_date", "call_timestamp", "close_date", "created_at", "updated_at"];
11013
- var TEXT_FIELDS = ["text", "quote", "snippet", "excerpt", "content", "summary", "body", "description"];
11014
- var WHO_FIELDS = ["speaker", "speaker_name", "account_name", "company_name", "owner_name", "deal_stage_label"];
11566
+ var TEXT_FIELDS = ["content_preview", "text", "quote", "snippet", "excerpt", "content", "summary", "body", "description"];
11567
+ var WHO_FIELDS = ["speaker", "speaker_name", "account_name", "company_name", "owner_name", "deal_stage_label", "speaker_type"];
11015
11568
  var AMOUNT_FIELDS = ["deal_amount", "amount"];
11016
11569
  function first(row, names) {
11017
- for (const name of names) {
11018
- const value = row[name];
11570
+ for (const name2 of names) {
11571
+ const value = row[name2];
11019
11572
  if (typeof value === "string" && value.trim()) return field(value);
11020
11573
  if (typeof value === "number") return String(value);
11021
11574
  }
@@ -11039,15 +11592,24 @@ function rowLines(row, n) {
11039
11592
  function rowsOf(result) {
11040
11593
  return records(result.results);
11041
11594
  }
11595
+ function noAnswerLines(status2, rows) {
11596
+ if (rows === 0) return [];
11597
+ if (status2 === "not_enough_data") {
11598
+ return [...wrap(`Amdahl couldn't answer that from these rows. Try rephrasing the question, or run ${amdahlCommand("connections")} to see whether your data is synced.`), ""];
11599
+ }
11600
+ if (status2 === "failed") return [...wrap(`Amdahl found rows but couldn't write an answer this time. Here are the rows. Try again, or rephrase the question.`), ""];
11601
+ return [];
11602
+ }
11042
11603
  function searchLines(result) {
11043
11604
  const lines = [];
11044
11605
  const rows = rowsOf(result);
11045
11606
  const answer = record(result.answer);
11046
11607
  const answered = answer?.status === "answered" && paragraphs(answer.text);
11047
11608
  if (answered) lines.push(...wrap(plainMarkdown(answered)), "");
11609
+ else lines.push(...noAnswerLines(field(answer?.status), rows.length));
11048
11610
  const shown = rows.slice(0, answered ? ROWS_UNDER_ANSWER : ROWS_ALONE);
11049
11611
  if (shown.length > 0) {
11050
- if (answered) lines.push("From:");
11612
+ if (answered || lines.length > 0) lines.push(answered ? "From:" : "Rows it read:");
11051
11613
  shown.forEach((row, i) => lines.push(...rowLines(row, i + 1)));
11052
11614
  if (rows.length > shown.length) lines.push(`And ${count(rows.length - shown.length, "more row")} (all of them: add --json).`);
11053
11615
  } else if (!answered) {
@@ -11126,13 +11688,12 @@ async function search(ctx, args) {
11126
11688
  out.result({}, [...wrap(field(setup2.message) || "This workspace has no data connected yet."), connectLine()].join("\n"));
11127
11689
  return EXIT.ok;
11128
11690
  }
11129
- const lines = searchLines(result);
11130
- out.result({}, lines.join("\n"));
11131
- if (refusedForScope(notes)) out.info(researchKeyLine());
11691
+ if (refusedForScope(notes)) out.info(wrap(researchKeyFirst()).join("\n"));
11132
11692
  else {
11133
11693
  const refused = notes.find((n) => /ran search instead/i.test(n));
11134
11694
  if (refused) out.info(wrap(refused).join("\n"));
11135
11695
  }
11696
+ out.result({}, searchLines(result).join("\n"));
11136
11697
  return EXIT.ok;
11137
11698
  }
11138
11699
  function printResearch(ctx, search2, job) {
@@ -11186,12 +11747,12 @@ var TRY_IN = {
11186
11747
  };
11187
11748
  var CODEX_TRY = `Run codex, paste an email and say: ${ASK}`;
11188
11749
  var CODEX_LOGIN = "codex mcp login amdahl";
11189
- function toolScreen(name, already) {
11750
+ function toolScreen(name2, already) {
11190
11751
  return [
11191
- already ? `\u2713 ${name} already has Amdahl.` : `\u2713 Added Amdahl to ${name}.`,
11192
- `One step left, in ${name}:`,
11193
- ` 1. ${SIGN_IN[name] ?? ""}`,
11194
- ` 2. ${TRY_IN[name] ?? ""}`
11752
+ already ? `\u2713 ${name2} already has Amdahl.` : `\u2713 Added Amdahl to ${name2}.`,
11753
+ `One step left, in ${name2}:`,
11754
+ ` 1. ${SIGN_IN[name2] ?? ""}`,
11755
+ ` 2. ${TRY_IN[name2] ?? ""}`
11195
11756
  ];
11196
11757
  }
11197
11758
  function whatNextLines() {
@@ -11478,8 +12039,8 @@ async function tryHere(ctx, say, lead) {
11478
12039
  say(`\u2713 Signed in \xB7 ${printable(signedIn.workspace.name)}`);
11479
12040
  if (verbose) say(` ${signedInLine(ctx, signedIn)}`);
11480
12041
  } else {
11481
- const name = who.source === "flag" ? "--api-key" : who.source.slice("env:".length);
11482
- out.info(`Amdahl rejected the key in ${name}, so a message can't be tried here. Fix it, or unset it and run \`${amdahlCommand("login")}\`.`);
12042
+ const name2 = who.source === "flag" ? "--api-key" : who.source.slice("env:".length);
12043
+ out.info(`Amdahl rejected the key in ${name2}, so a message can't be tried here. Fix it, or unset it and run \`${amdahlCommand("login")}\`.`);
11483
12044
  return EXIT.unauthenticated;
11484
12045
  }
11485
12046
  return optimizePasted(ctx);
@@ -11609,9 +12170,9 @@ function readTar(tar) {
11609
12170
  if (header.toString("latin1", 257, 263) !== "ustar\0" || header.toString("latin1", 263, 265) !== "00") {
11610
12171
  throw new ArchiveError("its tar is not ustar");
11611
12172
  }
11612
- const name = field2(header, 0, 100);
12173
+ const name2 = field2(header, 0, 100);
11613
12174
  const prefix2 = field2(header, 345, 155);
11614
- let path = prefix2 ? `${prefix2}/${name}` : name;
12175
+ let path = prefix2 ? `${prefix2}/${name2}` : name2;
11615
12176
  const type = String.fromCharCode(header[156]);
11616
12177
  const size = octal(header, 124, 12);
11617
12178
  const mode = octal(header, 100, 8);
@@ -11623,22 +12184,22 @@ function readTar(tar) {
11623
12184
  }
11624
12185
  if (isDir && path.endsWith("/")) path = path.slice(0, -1);
11625
12186
  checkPath(path);
11626
- const key = path.toLowerCase();
11627
- const parts = key.split("/");
12187
+ const key2 = path.toLowerCase();
12188
+ const parts = key2.split("/");
11628
12189
  for (let i = 1; i < parts.length; i += 1) {
11629
12190
  const parent = parts.slice(0, i).join("/");
11630
12191
  const kind = seen.get(parent);
11631
12192
  if (kind === "file") throw new ArchiveError(`it puts ${JSON.stringify(path)} inside a file`);
11632
12193
  if (!kind) seen.set(parent, "implied");
11633
12194
  }
11634
- const before = seen.get(key);
12195
+ const before = seen.get(key2);
11635
12196
  if (before === "file" || before === "dir" || before === "implied" && !isDir) {
11636
12197
  throw new ArchiveError(`it lists ${JSON.stringify(path)} twice`);
11637
12198
  }
11638
12199
  const start = at + BLOCK;
11639
12200
  if (isDir) {
11640
12201
  if (size !== 0) throw new ArchiveError(`its folder ${JSON.stringify(path)} has a size`);
11641
- seen.set(key, "dir");
12202
+ seen.set(key2, "dir");
11642
12203
  dirs.push(path);
11643
12204
  } else {
11644
12205
  if (!FILE_MODES.has(mode & 4095)) {
@@ -11648,7 +12209,7 @@ function readTar(tar) {
11648
12209
  unpacked += size;
11649
12210
  if (unpacked > MAX_UNPACKED_BYTES) throw new ArchiveError(`its files come to more than ${MAX_UNPACKED_BYTES / 1024 / 1024} MB`, true);
11650
12211
  if (start + size > tar.length) throw new ArchiveError(`its tar is cut short inside ${JSON.stringify(path)}`);
11651
- seen.set(key, "file");
12212
+ seen.set(key2, "file");
11652
12213
  files.push({ path, bytes: Buffer.from(tar.subarray(start, start + size)), mode: mode & 4095 });
11653
12214
  }
11654
12215
  at = start + Math.ceil(size / BLOCK) * BLOCK;
@@ -11713,8 +12274,8 @@ function canAsk(ctx) {
11713
12274
  async function confirmWarning(ctx, ref, site, warning) {
11714
12275
  const { out } = ctx;
11715
12276
  const host = new URL(site).host;
11716
- const name = `${ref.publisher}/${ref.skill}`;
11717
- const head = warning.result === "fail" ? `${name} failed ${host}'s security review.` : `${host}'s security review flagged ${name}.`;
12277
+ const name2 = `${ref.publisher}/${ref.skill}`;
12278
+ const head = warning.result === "fail" ? `${name2} failed ${host}'s security review.` : `${host}'s security review flagged ${name2}.`;
11718
12279
  out.info([out.bold(`Warning: ${head}`), ` ${warning.text}`, ` See what was flagged: ${skillPage(site, ref)}`].join("\n"));
11719
12280
  if (ctx.flags["accept-warning"] === true) {
11720
12281
  out.info("Installing it anyway, because of --accept-warning.");
@@ -11723,7 +12284,7 @@ async function confirmWarning(ctx, ref, site, warning) {
11723
12284
  if (!canAsk(ctx)) {
11724
12285
  throw new CliError(
11725
12286
  "skill_flagged",
11726
- `Did not install ${name}, because the security review flagged it. Nothing was written.`,
12287
+ `Did not install ${name2}, because the security review flagged it. Nothing was written.`,
11727
12288
  EXIT.flagged,
11728
12289
  "Show this warning to the person you are working for. Once they agree, run the same command again with --accept-warning.",
11729
12290
  void 0,
@@ -11914,10 +12475,10 @@ function checkSkillMd(body, what, ref) {
11914
12475
  } catch (err) {
11915
12476
  throw bad(`its front matter could not be read (${printable(err instanceof Error ? err.message : String(err))})`);
11916
12477
  }
11917
- const { name, description, version, metadata } = meta && typeof meta === "object" && !Array.isArray(meta) ? meta : {};
11918
- if (typeof name !== "string") throw bad("its front matter has no string name");
12478
+ const { name: name2, description, version, metadata } = meta && typeof meta === "object" && !Array.isArray(meta) ? meta : {};
12479
+ if (typeof name2 !== "string") throw bad("its front matter has no string name");
11919
12480
  if (typeof description !== "string" || description.trim() === "") throw bad("its front matter has no string description");
11920
- if (name !== ref.skill) throw bad(`its front matter names it "${printable(name)}", but the index lists ${ref.skill}`);
12481
+ if (name2 !== ref.skill) throw bad(`its front matter names it "${printable(name2)}", but the index lists ${ref.skill}`);
11921
12482
  const nested = metadata && typeof metadata === "object" ? metadata.version : void 0;
11922
12483
  const v = printable(typeof version === "string" ? version : typeof nested === "string" ? nested : "");
11923
12484
  return { description, ...v ? { version: v } : {} };
@@ -12088,8 +12649,8 @@ async function status(ctx) {
12088
12649
  }
12089
12650
  async function workspaceList(ctx) {
12090
12651
  const { config } = ctx;
12091
- const profiles = Object.entries(config.profiles).map(([name, p]) => ({
12092
- name,
12652
+ const profiles = Object.entries(config.profiles).map(([name2, p]) => ({
12653
+ name: name2,
12093
12654
  workspace: p.workspace,
12094
12655
  email: p.email,
12095
12656
  api_url: p.api_url
@@ -12101,17 +12662,291 @@ async function workspaceList(ctx) {
12101
12662
  return EXIT.ok;
12102
12663
  }
12103
12664
  async function workspaceUse(ctx, args) {
12104
- const name = args[0];
12105
- if (!name || args.length > 1) throw usageError("Usage: amdahl workspace use <profile>");
12106
- if (!ctx.config.profiles[name]) {
12107
- throw usageError(`No profile named "${name}".`, `Run \`${amdahlCommand("workspace list")}\` to see your profiles.`);
12665
+ const name2 = args[0];
12666
+ if (!name2 || args.length > 1) throw usageError("Usage: amdahl workspace use <profile>");
12667
+ if (!ctx.config.profiles[name2]) {
12668
+ throw usageError(`No profile named "${name2}".`, `Run \`${amdahlCommand("workspace list")}\` to see your profiles.`);
12108
12669
  }
12109
- ctx.config.default_profile = name;
12670
+ ctx.config.default_profile = name2;
12110
12671
  ctx.saveConfig();
12111
- ctx.out.result({ ok: true, default: name }, `Default profile: ${name}`);
12672
+ ctx.out.result({ ok: true, default: name2 }, `Default profile: ${name2}`);
12112
12673
  return EXIT.ok;
12113
12674
  }
12114
12675
 
12676
+ // src/help.ts
12677
+ var GLOBAL = `
12678
+ Global options: --profile, --api-url, --api-key, --json, --no-color, --verbose.
12679
+ Run \`amdahl --help\` for what each does.
12680
+ `;
12681
+ var HELP = {
12682
+ setup: `Usage: amdahl [setup] [--no-browser]
12683
+
12684
+ The guided setup. In a terminal it asks where Amdahl should work: Claude Code,
12685
+ Cursor, Codex or this terminal. Run by an agent, or with no terminal, it sets
12686
+ up that agent (or every client it finds) and asks nothing.
12687
+
12688
+ --no-browser Print the sign-in link instead of opening a browser.
12689
+ `,
12690
+ login: `Usage: amdahl login [--workspace <slug>] [--no-browser] [--port <n>]
12691
+
12692
+ Sign in through the browser with your console account. Also: amdahl auth login.
12693
+
12694
+ --workspace <slug> Sign in to this workspace.
12695
+ --no-browser Print the sign-in link instead of opening a browser.
12696
+ --port <n> Listen for the browser's answer on this local port.
12697
+ `,
12698
+ logout: `Usage: amdahl logout [--all]
12699
+
12700
+ Sign out and forget the sign-in on this machine. Also: amdahl auth logout.
12701
+
12702
+ --all Sign out of every saved profile, not just the current one.
12703
+ `,
12704
+ whoami: `Usage: amdahl whoami [--show-token]
12705
+
12706
+ Show who you are: the account, workspace, role and which credential won. Also:
12707
+ amdahl auth status.
12708
+
12709
+ --show-token Print the full access token, not its first characters.
12710
+ `,
12711
+ "auth token": `Usage: amdahl auth token
12712
+
12713
+ Print a fresh access token from your sign-in. It needs your own sign-in: a key
12714
+ in AMDAHL_KEY or --api-key wins over it, and then this exits 4.
12715
+ `,
12716
+ status: `Usage: amdahl status
12717
+
12718
+ Can this workspace optimize, and what blocks it. It shows your role, the
12719
+ monthly optimize quota and any connection that needs attention. Each blocked
12720
+ item names its fix: the command, the console page, or who to ask.
12721
+
12722
+ With --json it prints the server's setup status as one object.
12723
+ `,
12724
+ optimize: `Usage: amdahl optimize [<file>... | -] [--channel email|linkedin]
12725
+ [--evidence off|workspace] [--on-unsupported flag|remove]
12726
+ [--tries] [--context <file.json>]
12727
+ amdahl optimize --list <file.csv|file.jsonl> [--no-wait] [--force]
12728
+ amdahl optimize --list <file.csv> --for <tool> [--channel email|linkedin]
12729
+ amdahl optimize --batch <id> [--list <file>] [--force]
12730
+
12731
+ Rewrite an outbound email or LinkedIn message. Each file is one message; "-"
12732
+ reads standard input. With no file, at a terminal, paste the message.
12733
+
12734
+ --channel email|linkedin What the message is. Left out, it is inferred.
12735
+ --evidence off|workspace "workspace" also evaluates each claim against
12736
+ your customers' own conversations. Slower.
12737
+ --on-unsupported flag|remove What to do with a claim nothing backs.
12738
+ --tries Also show every version the run scored.
12739
+ --context <file.json> Your rules, voice_examples and facts, as JSON.
12740
+ --list <file> Rewrite a whole list in one batch: a CSV with a
12741
+ message column (it may also have item_id,
12742
+ channel, evidence, on_unsupported, facts and
12743
+ rules; any other column is copied to the
12744
+ results) or a JSONL file, one item per line.
12745
+ Up to 500 messages. The results go next to the
12746
+ list: drafts.csv gets drafts.optimized.csv.
12747
+ --no-wait With --list: queue the batch and print its id.
12748
+ --batch <id> Pick up a batch queued earlier. With --list, the
12749
+ same list; the results go next to it.
12750
+ --for <tool> With a CSV --list: also write the messages Amdahl
12751
+ wrote as a CSV that tool imports, next to the
12752
+ list (drafts.csv for lemlist gets
12753
+ drafts-lemlist.csv), then print how to import it.
12754
+ Tools: instantly, smartlead, lemlist, heyreach,
12755
+ reply_io, apollo, outreach, salesloft, hubspot,
12756
+ lgm, woodpecker, or other (any tool that imports
12757
+ a CSV).
12758
+ --force Replace a results file that already exists.
12759
+
12760
+ In the results CSV, a cell that starts with =, +, - or @ gets a ' in front, so
12761
+ a spreadsheet does not run it as a formula. That includes a phone number such
12762
+ as +1 555 0100 or a negative number you copied through. Delete the ' to use it.
12763
+
12764
+ The --for file holds only the leads Amdahl wrote, with your columns after
12765
+ Amdahl's, except the column the message came from and the ones Amdahl reads
12766
+ (message, channel, evidence, on_unsupported, facts, rules, item_id). Your tool
12767
+ imports a value as it is, so no cell gets the ' guard (the results file keeps
12768
+ it). A lead that failed, needed input or has no email (no LinkedIn URL for
12769
+ LinkedIn) is left out, and counted. A tool that takes one channel needs no
12770
+ --channel; lemlist, reply_io and other take both, so add --channel. With
12771
+ --json, .send_to has the file and the counts.
12772
+
12773
+ Output: the rewrite goes to stdout. What Amdahl did, and the run id, go to
12774
+ stderr. With --json it prints {"ok", "file", "result"}: the rewritten message
12775
+ is .result.message and the run id is .result.run_id. Several files give an
12776
+ array of those. A list prints the batch, and each item's result, with --json.
12777
+ Exit 1 when a message could not be rewritten.
12778
+ ${GLOBAL}`,
12779
+ search: `Usage: amdahl search "<question>" [--research] [--limit <n>]
12780
+
12781
+ Ask a question about your customers, calls and deals. It prints a short answer
12782
+ with the rows it came from.
12783
+
12784
+ --research Start a research job: a deeper, cited answer. It needs a
12785
+ Customer agent key (amdahl keys create --preset agent). With a
12786
+ sign-in you get the search answer and a line that says so.
12787
+ --limit <n> Ask for at most n rows.
12788
+
12789
+ With --json it prints the server's whole answer, every row included.
12790
+ ${GLOBAL}`,
12791
+ connections: `Usage: amdahl connections [--all]
12792
+
12793
+ List the sources this workspace has connected, with each one's health and last
12794
+ sync. A source that needs attention gets a link to its console page.
12795
+
12796
+ --all Also list disconnected and never-synced sources.
12797
+ `,
12798
+ connect: `Usage: amdahl connect [<connector>] [--no-browser]
12799
+
12800
+ Open the console's add page. The connector only fills its search box: you pick
12801
+ it there and sign in to that tool. If that connector is already connected and
12802
+ healthy, this says so and prints the page instead of opening it. The CLI never
12803
+ creates a connection itself.
12804
+
12805
+ --no-browser Print the link instead of opening a browser. It also prints
12806
+ under --json, for an agent, in CI, and when stdin or stdout
12807
+ is not a terminal.
12808
+ `,
12809
+ open: `Usage: amdahl open [--no-browser]
12810
+
12811
+ Open this workspace in the console.
12812
+
12813
+ --no-browser Print the link instead of opening a browser.
12814
+ `,
12815
+ feedback: `Usage: amdahl feedback <run-id> --evidence did_it_myself|reported_to_me|inferred
12816
+ [--outcome used|used_with_edits|not_used|unsure]
12817
+ [--sent-text <file> | -] [--comment <text>]
12818
+
12819
+ Say what happened to the message an optimize run returned. The run id is the
12820
+ one \`amdahl optimize\` printed. Amdahl learns your preferences from an edit,
12821
+ unless the evidence is "inferred". Each call stores one event.
12822
+
12823
+ --evidence <how> How you know. Required.
12824
+ --outcome <what> What happened to it.
12825
+ --sent-text <file> The text as sent (a file, or - for standard input).
12826
+ --comment <text> Your own words about it.
12827
+
12828
+ Give at least one of --outcome, --sent-text and --comment.
12829
+ ${GLOBAL}`,
12830
+ sent: `Usage: amdahl sent <run-id> --provider <provider> --channel <channel>
12831
+ --sent-as returned|edited [--sent-at <time>]
12832
+ [--provider-send-id <id>]
12833
+ [--recipient <email|url> | --recipient-key-hash <sha256>]
12834
+ [--provider-campaign-id <id>] [--sequence-step <n>]
12835
+ [--provider-variant-id <id>] [--provider-lead-id <id>]
12836
+ [--thread-id <id>] [--sent-text <file> | -]
12837
+
12838
+ Report that the system that sent a message sent it. One send per command; the
12839
+ API takes up to 100 (POST /messages/report_send).
12840
+
12841
+ --provider <name> ${SEND_PROVIDERS.slice(0, 4).join(", ")},
12842
+ ${SEND_PROVIDERS.slice(4, 8).join(", ")},
12843
+ ${SEND_PROVIDERS.slice(8).join(", ")}.
12844
+ A sender not on the list: use "other".
12845
+ --channel <kind> email, linkedin (the same as linkedin_message),
12846
+ linkedin_invite, linkedin_message or linkedin_inmail.
12847
+ --sent-as <how> returned (as Amdahl returned it) or edited. An edit needs
12848
+ --sent-text: the message as you sent it.
12849
+ --sent-at <time> A time with a zone, like 2026-11-12T09:30:00Z. Default:
12850
+ now.
12851
+ --provider-send-id The provider's own id for the send. For gmail, the
12852
+ Message-ID header with its angle brackets.
12853
+ --recipient An email address, or a LinkedIn profile URL for a LinkedIn
12854
+ channel. Amdahl stores only a hash of it.
12855
+ --recipient-key-hash A SHA-256 you made, instead of --recipient (not both).
12856
+ --provider-campaign-id, --sequence-step, --provider-variant-id,
12857
+ --provider-lead-id, --thread-id The provider's ids, when you have them.
12858
+ --sent-text <file> The text as sent (a file, or - for standard input). Only
12859
+ a hash of it is kept.
12860
+
12861
+ Give at least one of --provider-send-id and a recipient (--recipient or
12862
+ --recipient-key-hash, not both). A send id with a recipient is fine: Amdahl
12863
+ matches a report to a send by the id, the recipient, or both.
12864
+ ${GLOBAL}`,
12865
+ "keys create": `Usage: amdahl keys create --name <name>
12866
+ [--preset read-only|agent|internal|admin]
12867
+ [--expires 30d|90d|365d] [--no-browser]
12868
+
12869
+ Make an API key for a server or CI. You approve it in the console, then the key
12870
+ is printed once. It needs your own sign-in.
12871
+
12872
+ --name <name> What to call the key. Required.
12873
+ --preset <kind> read-only (default), agent (reads, optimize and research),
12874
+ internal or admin (workspace admins only).
12875
+ --expires <days> 30d, 90d (default) or 365d.
12876
+ --no-browser Print the approval link instead of opening a browser.
12877
+ `,
12878
+ "keys list": `Usage: amdahl keys list
12879
+
12880
+ List the workspace's keys, without their secrets. It needs your own sign-in.
12881
+ `,
12882
+ "keys revoke": `Usage: amdahl keys revoke <id|prefix> [--yes] [--no-browser]
12883
+
12884
+ Revoke a key. You type the key's name, then approve in the console. It needs
12885
+ your own sign-in.
12886
+
12887
+ --yes Skip the question that asks you to type the name.
12888
+ --no-browser Print the approval link instead of opening a browser.
12889
+ `,
12890
+ "workspace list": `Usage: amdahl workspace list
12891
+
12892
+ List your saved sign-ins (profiles). One sign-in is one workspace.
12893
+ `,
12894
+ "workspace use": `Usage: amdahl workspace use <profile>
12895
+
12896
+ Make a saved profile the default.
12897
+ `,
12898
+ install: `Usage: amdahl install claude-code|codex|cursor [--print] [--force]
12899
+
12900
+ Add the Amdahl MCP server and the Amdahl skill to one AI client. It writes no
12901
+ key or token: the client signs in itself the first time it connects.
12902
+
12903
+ --print Show what it would change, and change nothing.
12904
+ --force Replace a skill file you have edited.
12905
+ `,
12906
+ "skills add": `Usage: amdahl skills add <publisher>/<skill> [-y] [-g] [--agents] [--force]
12907
+ [--accept-warning] [--site <origin>]
12908
+
12909
+ Add a skill from https://amdahl.ai/skills to this project. It needs no sign-in.
12910
+ In a terminal it asks where to install it.
12911
+
12912
+ -y, --yes Add it to this project without asking.
12913
+ -g, --global Add it for every project (~/.claude/skills).
12914
+ --agents Also write .agents/skills, for Codex, Cursor and others.
12915
+ --force Replace a different copy that is already there.
12916
+ --accept-warning Install a skill amdahl.ai's security review flagged,
12917
+ after printing the warning. Without it, -y, --json or no
12918
+ terminal stops (exit 9).
12919
+ --site <origin> Read from another origin (https, or http for localhost).
12920
+ `,
12921
+ help: `Usage: amdahl help [<command>]
12922
+
12923
+ Show the help for one command, or all of them. Also: amdahl <command> --help.
12924
+ `
12925
+ };
12926
+ var GROUPS = {
12927
+ keys: ["keys create", "keys list", "keys revoke"],
12928
+ auth: ["login", "logout", "whoami", "auth token"],
12929
+ workspace: ["workspace list", "workspace use"],
12930
+ skills: ["skills add"]
12931
+ };
12932
+ var ALIASES2 = { "auth login": "login", "auth logout": "logout", "auth status": "whoami" };
12933
+ var HELP_COMMANDS = Object.keys(HELP);
12934
+ function commandHelp(words) {
12935
+ const key2 = ALIASES2[words.join(" ")] ?? words.join(" ");
12936
+ const text3 = HELP[key2];
12937
+ if (text3) return text3;
12938
+ const group = words.length === 1 ? GROUPS[words[0] ?? ""] : void 0;
12939
+ if (!group) return null;
12940
+ const lines = group.map((name2) => ` ${amdahlCommand(name2)}`);
12941
+ return `Usage: amdahl ${words[0]} <command>
12942
+
12943
+ Commands:
12944
+ ${lines.join("\n")}
12945
+
12946
+ Run \`amdahl ${words[0]} <command> --help\` for one command.
12947
+ `;
12948
+ }
12949
+
12115
12950
  // src/main.ts
12116
12951
  var COMMANDS = {
12117
12952
  login: (ctx) => login(ctx),
@@ -12134,13 +12969,25 @@ var COMMANDS = {
12134
12969
  install,
12135
12970
  "skills add": skillsAdd
12136
12971
  };
12972
+ function helpFor(words) {
12973
+ return commandHelp(words.slice(0, 2)) ?? commandHelp(words.slice(0, 1)) ?? USAGE;
12974
+ }
12137
12975
  var NO_ARGS = /* @__PURE__ */ new Set(["setup", "login", "logout", "whoami", "auth token", "status", "connections", "open", "keys create", "keys list", "workspace list"]);
12138
12976
  async function runCli(argv, io) {
12139
12977
  setCommandName(io);
12140
12978
  const json = argv.includes("--json");
12141
12979
  let out = createOutput(io, json, argv.includes("--no-color"));
12142
12980
  try {
12143
- const parsed = parseCommandLine(argv);
12981
+ let parsed;
12982
+ try {
12983
+ parsed = parseCommandLine(argv);
12984
+ } catch (err) {
12985
+ if (argv.includes("--help") || argv.includes("-h")) {
12986
+ io.stdout(helpFor(helpWords(argv)));
12987
+ return EXIT.ok;
12988
+ }
12989
+ throw err;
12990
+ }
12144
12991
  out = createOutput(io, parsed.flags.json === true, parsed.flags["no-color"] === true);
12145
12992
  if (parsed.flags.version) {
12146
12993
  io.stdout(`${VERSION}
@@ -12148,7 +12995,7 @@ async function runCli(argv, io) {
12148
12995
  return EXIT.ok;
12149
12996
  }
12150
12997
  if (parsed.flags.help || parsed.command === "help") {
12151
- io.stdout(USAGE);
12998
+ io.stdout(helpFor(parsed.command === "help" ? parsed.args : parsed.command?.split(" ") ?? []));
12152
12999
  return EXIT.ok;
12153
13000
  }
12154
13001
  if (NO_ARGS.has(parsed.command ?? "") && parsed.args.length > 0) {