@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/README.md +153 -31
- package/bin/amdahl.js +3 -3
- package/dist/amdahl.js +1257 -410
- package/package.json +1 -1
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
|
|
22
|
-
if (!__hasOwnProp.call(to,
|
|
23
|
-
__defProp(to,
|
|
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_(
|
|
114
|
-
const ctrl = callVisitor(
|
|
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(
|
|
117
|
-
return visit_(
|
|
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_(
|
|
162
|
-
const ctrl = await callVisitor(
|
|
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(
|
|
165
|
-
return visitAsync_(
|
|
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(
|
|
215
|
+
function callVisitor(key2, node, visitor, path) {
|
|
216
216
|
if (typeof visitor === "function")
|
|
217
|
-
return visitor(
|
|
217
|
+
return visitor(key2, node, path);
|
|
218
218
|
if (identity.isMap(node))
|
|
219
|
-
return visitor.Map?.(
|
|
219
|
+
return visitor.Map?.(key2, node, path);
|
|
220
220
|
if (identity.isSeq(node))
|
|
221
|
-
return visitor.Seq?.(
|
|
221
|
+
return visitor.Seq?.(key2, node, path);
|
|
222
222
|
if (identity.isPair(node))
|
|
223
|
-
return visitor.Pair?.(
|
|
223
|
+
return visitor.Pair?.(key2, node, path);
|
|
224
224
|
if (identity.isScalar(node))
|
|
225
|
-
return visitor.Scalar?.(
|
|
225
|
+
return visitor.Scalar?.(key2, node, path);
|
|
226
226
|
if (identity.isAlias(node))
|
|
227
|
-
return visitor.Alias?.(
|
|
227
|
+
return visitor.Alias?.(key2, node, path);
|
|
228
228
|
return void 0;
|
|
229
229
|
}
|
|
230
|
-
function replaceNode(
|
|
230
|
+
function replaceNode(key2, path, node) {
|
|
231
231
|
const parent = path[path.length - 1];
|
|
232
232
|
if (identity.isCollection(parent)) {
|
|
233
|
-
parent.items[
|
|
233
|
+
parent.items[key2] = node;
|
|
234
234
|
} else if (identity.isPair(parent)) {
|
|
235
|
-
if (
|
|
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
|
|
311
|
-
switch (
|
|
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 ${
|
|
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
|
|
449
|
-
if (!exclude.has(
|
|
450
|
-
return
|
|
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,
|
|
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,
|
|
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 [
|
|
900
|
-
const node = this.get(
|
|
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(
|
|
904
|
+
this.set(key2, collectionFromPath(this.schema, rest, value));
|
|
905
905
|
else
|
|
906
|
-
throw new Error(`Expected YAML collection at ${
|
|
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 [
|
|
914
|
+
const [key2, ...rest] = path;
|
|
915
915
|
if (rest.length === 0)
|
|
916
|
-
return this.delete(
|
|
917
|
-
const node = this.get(
|
|
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 ${
|
|
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 [
|
|
930
|
-
const node = this.get(
|
|
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 [
|
|
948
|
+
const [key2, ...rest] = path;
|
|
949
949
|
if (rest.length === 0)
|
|
950
|
-
return this.has(
|
|
951
|
-
const node = this.get(
|
|
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 [
|
|
959
|
+
const [key2, ...rest] = path;
|
|
960
960
|
if (rest.length === 0) {
|
|
961
|
-
this.set(
|
|
961
|
+
this.set(key2, value);
|
|
962
962
|
} else {
|
|
963
|
-
const node = this.get(
|
|
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(
|
|
967
|
+
this.set(key2, collectionFromPath(this.schema, rest, value));
|
|
968
968
|
else
|
|
969
|
-
throw new Error(`Expected YAML collection at ${
|
|
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
|
|
1488
|
-
throw new Error(`Tag not resolved for ${
|
|
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(
|
|
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(
|
|
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 && (!
|
|
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(
|
|
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,
|
|
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 [
|
|
1728
|
+
for (const [key2, value2] of srcMap) {
|
|
1729
1729
|
if (map instanceof Map) {
|
|
1730
|
-
if (!map.has(
|
|
1731
|
-
map.set(
|
|
1730
|
+
if (!map.has(key2))
|
|
1731
|
+
map.set(key2, value2);
|
|
1732
1732
|
} else if (map instanceof Set) {
|
|
1733
|
-
map.add(
|
|
1734
|
-
} else if (!Object.prototype.hasOwnProperty.call(map,
|
|
1735
|
-
Object.defineProperty(map,
|
|
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(
|
|
1765
|
-
|
|
1766
|
-
else if (merge.isMergeKey(ctx,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
1827
|
-
const k = createNode.createNode(
|
|
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(
|
|
1832
|
+
constructor(key2, value = null) {
|
|
1833
1833
|
Object.defineProperty(this, identity.NODE_TYPE, { value: identity.PAIR });
|
|
1834
|
-
this.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(
|
|
1840
|
-
|
|
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(
|
|
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,
|
|
2020
|
-
const k = identity.isScalar(
|
|
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 ===
|
|
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 = (
|
|
2046
|
+
const add = (key2, value) => {
|
|
2047
2047
|
if (typeof replacer === "function")
|
|
2048
|
-
value = replacer.call(obj,
|
|
2049
|
-
else if (Array.isArray(replacer) && !replacer.includes(
|
|
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(
|
|
2052
|
+
map.items.push(Pair.createPair(key2, value, ctx));
|
|
2053
2053
|
};
|
|
2054
2054
|
if (obj instanceof Map) {
|
|
2055
|
-
for (const [
|
|
2056
|
-
add(
|
|
2055
|
+
for (const [key2, value] of obj)
|
|
2056
|
+
add(key2, value);
|
|
2057
2057
|
} else if (obj && typeof obj === "object") {
|
|
2058
|
-
for (const
|
|
2059
|
-
add(
|
|
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(
|
|
2100
|
-
const it = findPair(this.items,
|
|
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(
|
|
2107
|
-
const it = findPair(this.items,
|
|
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(
|
|
2112
|
-
return !!findPair(this.items,
|
|
2111
|
+
has(key2) {
|
|
2112
|
+
return !!findPair(this.items, key2);
|
|
2113
2113
|
}
|
|
2114
|
-
set(
|
|
2115
|
-
this.add(new Pair.Pair(
|
|
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(
|
|
2205
|
-
const idx = asItemIndex(
|
|
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(
|
|
2212
|
-
const idx = asItemIndex(
|
|
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(
|
|
2225
|
-
const idx = asItemIndex(
|
|
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(
|
|
2236
|
-
const idx = asItemIndex(
|
|
2235
|
+
set(key2, value) {
|
|
2236
|
+
const idx = asItemIndex(key2);
|
|
2237
2237
|
if (typeof idx !== "number")
|
|
2238
|
-
throw new Error(`Expected a valid index, not ${
|
|
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
|
|
2273
|
-
it = replacer.call(obj,
|
|
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(
|
|
2282
|
-
let idx = identity.isScalar(
|
|
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
|
|
2696
|
+
let key2, value;
|
|
2697
2697
|
if (Array.isArray(it)) {
|
|
2698
2698
|
if (it.length === 2) {
|
|
2699
|
-
|
|
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
|
-
|
|
2707
|
-
value = it[
|
|
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
|
-
|
|
2712
|
+
key2 = it;
|
|
2713
2713
|
}
|
|
2714
|
-
pairs2.items.push(Pair.createPair(
|
|
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
|
|
2761
|
+
let key2, value;
|
|
2762
2762
|
if (identity.isPair(pair)) {
|
|
2763
|
-
|
|
2764
|
-
value = toJS.toJS(pair.value,
|
|
2763
|
+
key2 = toJS.toJS(pair.key, "", ctx);
|
|
2764
|
+
value = toJS.toJS(pair.value, key2, ctx);
|
|
2765
2765
|
} else {
|
|
2766
|
-
|
|
2766
|
+
key2 = toJS.toJS(pair, "", ctx);
|
|
2767
2767
|
}
|
|
2768
|
-
if (map.has(
|
|
2768
|
+
if (map.has(key2))
|
|
2769
2769
|
throw new Error("Ordered maps must not include duplicate keys");
|
|
2770
|
-
map.set(
|
|
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(
|
|
2793
|
-
if (seenKeys.includes(
|
|
2794
|
-
onError(`Ordered maps must not include duplicate keys: ${
|
|
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(
|
|
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(
|
|
2981
|
+
add(key2) {
|
|
2982
2982
|
let pair;
|
|
2983
|
-
if (identity.isPair(
|
|
2984
|
-
pair =
|
|
2985
|
-
else if (
|
|
2986
|
-
pair = new Pair.Pair(
|
|
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(
|
|
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(
|
|
2998
|
-
const pair = YAMLMap.findPair(this.items,
|
|
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(
|
|
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,
|
|
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(
|
|
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((
|
|
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((
|
|
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,
|
|
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
|
-
!
|
|
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(
|
|
3536
|
-
const k = this.createNode(
|
|
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(
|
|
3545
|
-
return assertCollection(this.contents) ? this.contents.delete(
|
|
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(
|
|
3566
|
-
return identity.isCollection(this.contents) ? this.contents.get(
|
|
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(
|
|
3582
|
-
return identity.isCollection(this.contents) ? this.contents.has(
|
|
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(
|
|
3596
|
+
set(key2, value) {
|
|
3597
3597
|
if (this.contents == null) {
|
|
3598
|
-
this.contents = Collection.collectionFromPath(this.schema, [
|
|
3598
|
+
this.contents = Collection.collectionFromPath(this.schema, [key2], value);
|
|
3599
3599
|
} else if (assertCollection(this.contents)) {
|
|
3600
|
-
this.contents.set(
|
|
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(
|
|
3710
|
+
constructor(name2, pos, code, message2) {
|
|
3711
3711
|
super();
|
|
3712
|
-
this.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(
|
|
3909
|
-
if (!
|
|
3908
|
+
function containsNewline(key2) {
|
|
3909
|
+
if (!key2)
|
|
3910
3910
|
return null;
|
|
3911
|
-
switch (
|
|
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 (
|
|
3916
|
+
if (key2.source.includes("\n"))
|
|
3917
3917
|
return true;
|
|
3918
|
-
if (
|
|
3919
|
-
for (const st of
|
|
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
|
|
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:
|
|
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 (
|
|
4011
|
-
if (
|
|
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
|
|
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(
|
|
4027
|
-
onError(
|
|
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 =
|
|
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,
|
|
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: !
|
|
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:
|
|
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(
|
|
4235
|
+
if (!isMap && ctx.options.strict && utilContainsNewline.containsNewline(key2))
|
|
4236
4236
|
onError(
|
|
4237
|
-
|
|
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 =
|
|
4285
|
-
if (isBlock(
|
|
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
|
|
4353
|
-
const msg = atRoot ? `${
|
|
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
|
|
5436
|
-
if (
|
|
5437
|
-
delete token[
|
|
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
|
|
5486
|
-
if (
|
|
5487
|
-
delete token[
|
|
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 (
|
|
5547
|
-
res += stringifyToken(
|
|
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
|
|
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
|
|
7400
|
-
const lines = [...rows(), ...fitHint(hint,
|
|
7401
|
-
drawn = lines.reduce((n, line) => n + screenLines(line,
|
|
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,
|
|
7430
|
-
const
|
|
7431
|
-
if (
|
|
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,
|
|
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
|
|
7444
|
-
const top = [question, ...note ? note.split("\n").flatMap((line) => wrapWords(line,
|
|
7445
|
-
headLines = top.reduce((n, line) => n + screenLines(line,
|
|
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,
|
|
7481
|
-
if (
|
|
7482
|
-
if (
|
|
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,
|
|
7491
|
-
if (
|
|
7492
|
-
const next = move(current, choices.length, text3,
|
|
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
|
|
7507
|
-
if (!tag) return
|
|
7508
|
-
const
|
|
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 <=
|
|
7511
|
-
if (6 +
|
|
7512
|
-
return
|
|
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,
|
|
7517
|
-
if (
|
|
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 (
|
|
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,
|
|
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,
|
|
7557
|
-
if (
|
|
7558
|
-
else if (
|
|
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,
|
|
7597
|
-
if (
|
|
7598
|
-
else if (
|
|
7599
|
-
else if (settle &&
|
|
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
|
|
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
|
-
|
|
7961
|
+
name2 = `${first2} ${second}`;
|
|
7954
7962
|
words = 2;
|
|
7955
7963
|
}
|
|
7956
|
-
|
|
7957
|
-
const spec = COMMAND_OPTIONS[
|
|
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:
|
|
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
|
|
8028
|
-
evidence, on_unsupported, facts and rules.
|
|
8029
|
-
|
|
8030
|
-
|
|
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
|
-
|
|
8045
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
8195
|
+
function assertUuid(name2, value) {
|
|
8163
8196
|
if (typeof value !== "string" || value.length === 0) {
|
|
8164
8197
|
throw new Error(
|
|
8165
|
-
`mcpKeys client: ${
|
|
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: ${
|
|
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: ${
|
|
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
|
|
8975
|
-
await ctx.secrets().set(keychainAccount(ctx.apiUrl,
|
|
8976
|
-
ctx.config.profiles[
|
|
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 =
|
|
9039
|
+
ctx.config.default_profile = name2;
|
|
8985
9040
|
ctx.saveConfig();
|
|
8986
|
-
ctx.profileName =
|
|
8987
|
-
ctx.profile = ctx.config.profiles[
|
|
8988
|
-
return { profile:
|
|
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
|
|
9028
|
-
const profile = config.profiles[
|
|
9082
|
+
for (const name2 of names) {
|
|
9083
|
+
const profile = config.profiles[name2];
|
|
9029
9084
|
if (!profile) continue;
|
|
9030
|
-
const account = keychainAccount(profile.api_url,
|
|
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(
|
|
9097
|
+
if (!ok) notRevoked.push(name2);
|
|
9043
9098
|
}
|
|
9044
9099
|
await store.delete(account);
|
|
9045
|
-
delete config.profiles[
|
|
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((
|
|
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,
|
|
9171
|
+
function skillDirs(targets, name2) {
|
|
9117
9172
|
const dirs = [];
|
|
9118
|
-
if (targets.claude) dirs.push(join3(targets.base, ".claude", "skills",
|
|
9119
|
-
if (targets.agents) dirs.push(join3(targets.base, ".agents", "skills",
|
|
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,
|
|
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 ${
|
|
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,
|
|
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), `.${
|
|
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,
|
|
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,
|
|
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,
|
|
9295
|
-
io.uninterruptible(() => payload.type === "archive" ? writeFolders(plan,
|
|
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
|
|
9519
|
-
return entries.find((e) => e.type.toLowerCase() ===
|
|
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
|
|
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
|
|
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(
|
|
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 = (
|
|
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/
|
|
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
|
-
|
|
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
|
|
9689
|
-
|
|
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, [
|
|
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[
|
|
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(
|
|
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
|
|
10146
|
-
if (!
|
|
10147
|
-
if (
|
|
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
|
|
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:
|
|
10164
|
-
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 "${
|
|
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 !==
|
|
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:
|
|
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/
|
|
10251
|
-
|
|
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
|
|
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(
|
|
10278
|
-
if (touched || row.length > 1 ||
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10766
|
+
cell3 += c;
|
|
10297
10767
|
}
|
|
10298
10768
|
continue;
|
|
10299
10769
|
}
|
|
10300
|
-
if (c === '"' &&
|
|
10770
|
+
if (c === '"' && cell3 === "") {
|
|
10301
10771
|
quoted = true;
|
|
10302
10772
|
touched = true;
|
|
10303
10773
|
} else if (c === ",") {
|
|
10304
|
-
row.push(
|
|
10305
|
-
|
|
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
|
|
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 (
|
|
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(
|
|
10325
|
-
return
|
|
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
|
|
10333
|
-
|
|
10334
|
-
if (
|
|
10335
|
-
throw usageError(
|
|
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
|
-
|
|
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((
|
|
10817
|
+
names.forEach((name2, j) => {
|
|
10345
10818
|
const value = row[j] ?? "";
|
|
10346
|
-
if (!value.trim()) return;
|
|
10347
|
-
if (
|
|
10348
|
-
else item[
|
|
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
|
-
|
|
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 =
|
|
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 [
|
|
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}
|
|
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(
|
|
10455
|
-
return /^[=+\-@\t\r]/.test(
|
|
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
|
-
|
|
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.
|
|
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
|
|
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, ...
|
|
10478
|
-
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
10547
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
11125
|
+
parsed = JSON.parse(text3);
|
|
10571
11126
|
} catch (err) {
|
|
10572
|
-
throw usageError(
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
10947
|
-
return asciiTable(labels, data.map((row) => columns.map((c) =>
|
|
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 ? "" :
|
|
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 `
|
|
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
|
|
11018
|
-
const value = row[
|
|
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
|
-
|
|
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(
|
|
11750
|
+
function toolScreen(name2, already) {
|
|
11190
11751
|
return [
|
|
11191
|
-
already ? `\u2713 ${
|
|
11192
|
-
`One step left, in ${
|
|
11193
|
-
` 1. ${SIGN_IN[
|
|
11194
|
-
` 2. ${TRY_IN[
|
|
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
|
|
11482
|
-
out.info(`Amdahl rejected the key in ${
|
|
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
|
|
12173
|
+
const name2 = field2(header, 0, 100);
|
|
11613
12174
|
const prefix2 = field2(header, 345, 155);
|
|
11614
|
-
let path = prefix2 ? `${prefix2}/${
|
|
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
|
|
11627
|
-
const parts =
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
11717
|
-
const head = warning.result === "fail" ? `${
|
|
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 ${
|
|
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
|
|
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 (
|
|
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(([
|
|
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
|
|
12105
|
-
if (!
|
|
12106
|
-
if (!ctx.config.profiles[
|
|
12107
|
-
throw usageError(`No profile named "${
|
|
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 =
|
|
12670
|
+
ctx.config.default_profile = name2;
|
|
12110
12671
|
ctx.saveConfig();
|
|
12111
|
-
ctx.out.result({ ok: true, default:
|
|
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
|
-
|
|
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(
|
|
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) {
|