@ghostystudio/cli 0.66.0 → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +10 -1
  2. package/dist/index.js +1243 -1039
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -32,9 +32,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
32
32
  mod
33
33
  ));
34
34
 
35
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/identity.js
35
+ // ../../node_modules/yaml/dist/nodes/identity.js
36
36
  var require_identity = __commonJS({
37
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/identity.js"(exports) {
37
+ "../../node_modules/yaml/dist/nodes/identity.js"(exports) {
38
38
  "use strict";
39
39
  var ALIAS = /* @__PURE__ */ Symbol.for("yaml.alias");
40
40
  var DOC = /* @__PURE__ */ Symbol.for("yaml.document");
@@ -89,9 +89,9 @@ var require_identity = __commonJS({
89
89
  }
90
90
  });
91
91
 
92
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/visit.js
92
+ // ../../node_modules/yaml/dist/visit.js
93
93
  var require_visit = __commonJS({
94
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/visit.js"(exports) {
94
+ "../../node_modules/yaml/dist/visit.js"(exports) {
95
95
  "use strict";
96
96
  var identity = require_identity();
97
97
  var BREAK = /* @__PURE__ */ Symbol("break visit");
@@ -247,9 +247,9 @@ var require_visit = __commonJS({
247
247
  }
248
248
  });
249
249
 
250
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/doc/directives.js
250
+ // ../../node_modules/yaml/dist/doc/directives.js
251
251
  var require_directives = __commonJS({
252
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/doc/directives.js"(exports) {
252
+ "../../node_modules/yaml/dist/doc/directives.js"(exports) {
253
253
  "use strict";
254
254
  var identity = require_identity();
255
255
  var visit = require_visit();
@@ -418,9 +418,9 @@ var require_directives = __commonJS({
418
418
  }
419
419
  });
420
420
 
421
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/doc/anchors.js
421
+ // ../../node_modules/yaml/dist/doc/anchors.js
422
422
  var require_anchors = __commonJS({
423
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/doc/anchors.js"(exports) {
423
+ "../../node_modules/yaml/dist/doc/anchors.js"(exports) {
424
424
  "use strict";
425
425
  var identity = require_identity();
426
426
  var visit = require_visit();
@@ -488,9 +488,9 @@ var require_anchors = __commonJS({
488
488
  }
489
489
  });
490
490
 
491
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/doc/applyReviver.js
491
+ // ../../node_modules/yaml/dist/doc/applyReviver.js
492
492
  var require_applyReviver = __commonJS({
493
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/doc/applyReviver.js"(exports) {
493
+ "../../node_modules/yaml/dist/doc/applyReviver.js"(exports) {
494
494
  "use strict";
495
495
  function applyReviver(reviver, obj, key, val) {
496
496
  if (val && typeof val === "object") {
@@ -538,9 +538,9 @@ var require_applyReviver = __commonJS({
538
538
  }
539
539
  });
540
540
 
541
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/toJS.js
541
+ // ../../node_modules/yaml/dist/nodes/toJS.js
542
542
  var require_toJS = __commonJS({
543
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/toJS.js"(exports) {
543
+ "../../node_modules/yaml/dist/nodes/toJS.js"(exports) {
544
544
  "use strict";
545
545
  var identity = require_identity();
546
546
  function toJS(value, arg, ctx) {
@@ -568,9 +568,9 @@ var require_toJS = __commonJS({
568
568
  }
569
569
  });
570
570
 
571
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/Node.js
571
+ // ../../node_modules/yaml/dist/nodes/Node.js
572
572
  var require_Node = __commonJS({
573
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/Node.js"(exports) {
573
+ "../../node_modules/yaml/dist/nodes/Node.js"(exports) {
574
574
  "use strict";
575
575
  var applyReviver = require_applyReviver();
576
576
  var identity = require_identity();
@@ -609,9 +609,9 @@ var require_Node = __commonJS({
609
609
  }
610
610
  });
611
611
 
612
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/Alias.js
612
+ // ../../node_modules/yaml/dist/nodes/Alias.js
613
613
  var require_Alias = __commonJS({
614
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/Alias.js"(exports) {
614
+ "../../node_modules/yaml/dist/nodes/Alias.js"(exports) {
615
615
  "use strict";
616
616
  var anchors = require_anchors();
617
617
  var visit = require_visit();
@@ -727,9 +727,9 @@ var require_Alias = __commonJS({
727
727
  }
728
728
  });
729
729
 
730
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/Scalar.js
730
+ // ../../node_modules/yaml/dist/nodes/Scalar.js
731
731
  var require_Scalar = __commonJS({
732
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/Scalar.js"(exports) {
732
+ "../../node_modules/yaml/dist/nodes/Scalar.js"(exports) {
733
733
  "use strict";
734
734
  var identity = require_identity();
735
735
  var Node = require_Node();
@@ -757,9 +757,9 @@ var require_Scalar = __commonJS({
757
757
  }
758
758
  });
759
759
 
760
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/doc/createNode.js
760
+ // ../../node_modules/yaml/dist/doc/createNode.js
761
761
  var require_createNode = __commonJS({
762
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/doc/createNode.js"(exports) {
762
+ "../../node_modules/yaml/dist/doc/createNode.js"(exports) {
763
763
  "use strict";
764
764
  var Alias = require_Alias();
765
765
  var identity = require_identity();
@@ -832,9 +832,9 @@ var require_createNode = __commonJS({
832
832
  }
833
833
  });
834
834
 
835
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/Collection.js
835
+ // ../../node_modules/yaml/dist/nodes/Collection.js
836
836
  var require_Collection = __commonJS({
837
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/Collection.js"(exports) {
837
+ "../../node_modules/yaml/dist/nodes/Collection.js"(exports) {
838
838
  "use strict";
839
839
  var createNode = require_createNode();
840
840
  var identity = require_identity();
@@ -975,9 +975,9 @@ var require_Collection = __commonJS({
975
975
  }
976
976
  });
977
977
 
978
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringifyComment.js
978
+ // ../../node_modules/yaml/dist/stringify/stringifyComment.js
979
979
  var require_stringifyComment = __commonJS({
980
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringifyComment.js"(exports) {
980
+ "../../node_modules/yaml/dist/stringify/stringifyComment.js"(exports) {
981
981
  "use strict";
982
982
  var stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, "#");
983
983
  function indentComment(comment, indent) {
@@ -992,9 +992,9 @@ var require_stringifyComment = __commonJS({
992
992
  }
993
993
  });
994
994
 
995
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/foldFlowLines.js
995
+ // ../../node_modules/yaml/dist/stringify/foldFlowLines.js
996
996
  var require_foldFlowLines = __commonJS({
997
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/foldFlowLines.js"(exports) {
997
+ "../../node_modules/yaml/dist/stringify/foldFlowLines.js"(exports) {
998
998
  "use strict";
999
999
  var FOLD_FLOW = "flow";
1000
1000
  var FOLD_BLOCK = "block";
@@ -1128,9 +1128,9 @@ ${indent}${text.slice(fold + 1, end2)}`;
1128
1128
  }
1129
1129
  });
1130
1130
 
1131
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringifyString.js
1131
+ // ../../node_modules/yaml/dist/stringify/stringifyString.js
1132
1132
  var require_stringifyString = __commonJS({
1133
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringifyString.js"(exports) {
1133
+ "../../node_modules/yaml/dist/stringify/stringifyString.js"(exports) {
1134
1134
  "use strict";
1135
1135
  var Scalar = require_Scalar();
1136
1136
  var foldFlowLines = require_foldFlowLines();
@@ -1411,9 +1411,9 @@ ${indent}`);
1411
1411
  }
1412
1412
  });
1413
1413
 
1414
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringify.js
1414
+ // ../../node_modules/yaml/dist/stringify/stringify.js
1415
1415
  var require_stringify = __commonJS({
1416
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringify.js"(exports) {
1416
+ "../../node_modules/yaml/dist/stringify/stringify.js"(exports) {
1417
1417
  "use strict";
1418
1418
  var anchors = require_anchors();
1419
1419
  var identity = require_identity();
@@ -1535,9 +1535,9 @@ ${ctx.indent}${str}`;
1535
1535
  }
1536
1536
  });
1537
1537
 
1538
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringifyPair.js
1538
+ // ../../node_modules/yaml/dist/stringify/stringifyPair.js
1539
1539
  var require_stringifyPair = __commonJS({
1540
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringifyPair.js"(exports) {
1540
+ "../../node_modules/yaml/dist/stringify/stringifyPair.js"(exports) {
1541
1541
  "use strict";
1542
1542
  var identity = require_identity();
1543
1543
  var Scalar = require_Scalar();
@@ -1668,9 +1668,9 @@ ${ctx.indent}`;
1668
1668
  }
1669
1669
  });
1670
1670
 
1671
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/log.js
1671
+ // ../../node_modules/yaml/dist/log.js
1672
1672
  var require_log = __commonJS({
1673
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/log.js"(exports) {
1673
+ "../../node_modules/yaml/dist/log.js"(exports) {
1674
1674
  "use strict";
1675
1675
  var node_process = __require("process");
1676
1676
  function debug(logLevel, ...messages) {
@@ -1690,9 +1690,9 @@ var require_log = __commonJS({
1690
1690
  }
1691
1691
  });
1692
1692
 
1693
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/merge.js
1693
+ // ../../node_modules/yaml/dist/schema/yaml-1.1/merge.js
1694
1694
  var require_merge = __commonJS({
1695
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/merge.js"(exports) {
1695
+ "../../node_modules/yaml/dist/schema/yaml-1.1/merge.js"(exports) {
1696
1696
  "use strict";
1697
1697
  var identity = require_identity();
1698
1698
  var Scalar = require_Scalar();
@@ -1750,9 +1750,9 @@ var require_merge = __commonJS({
1750
1750
  }
1751
1751
  });
1752
1752
 
1753
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/addPairToJSMap.js
1753
+ // ../../node_modules/yaml/dist/nodes/addPairToJSMap.js
1754
1754
  var require_addPairToJSMap = __commonJS({
1755
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/addPairToJSMap.js"(exports) {
1755
+ "../../node_modules/yaml/dist/nodes/addPairToJSMap.js"(exports) {
1756
1756
  "use strict";
1757
1757
  var log = require_log();
1758
1758
  var merge = require_merge();
@@ -1814,9 +1814,9 @@ var require_addPairToJSMap = __commonJS({
1814
1814
  }
1815
1815
  });
1816
1816
 
1817
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/Pair.js
1817
+ // ../../node_modules/yaml/dist/nodes/Pair.js
1818
1818
  var require_Pair = __commonJS({
1819
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/Pair.js"(exports) {
1819
+ "../../node_modules/yaml/dist/nodes/Pair.js"(exports) {
1820
1820
  "use strict";
1821
1821
  var createNode = require_createNode();
1822
1822
  var stringifyPair = require_stringifyPair();
@@ -1854,9 +1854,9 @@ var require_Pair = __commonJS({
1854
1854
  }
1855
1855
  });
1856
1856
 
1857
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringifyCollection.js
1857
+ // ../../node_modules/yaml/dist/stringify/stringifyCollection.js
1858
1858
  var require_stringifyCollection = __commonJS({
1859
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringifyCollection.js"(exports) {
1859
+ "../../node_modules/yaml/dist/stringify/stringifyCollection.js"(exports) {
1860
1860
  "use strict";
1861
1861
  var identity = require_identity();
1862
1862
  var stringify = require_stringify();
@@ -2005,9 +2005,9 @@ ${indent}${end}`;
2005
2005
  }
2006
2006
  });
2007
2007
 
2008
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/YAMLMap.js
2008
+ // ../../node_modules/yaml/dist/nodes/YAMLMap.js
2009
2009
  var require_YAMLMap = __commonJS({
2010
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/YAMLMap.js"(exports) {
2010
+ "../../node_modules/yaml/dist/nodes/YAMLMap.js"(exports) {
2011
2011
  "use strict";
2012
2012
  var stringifyCollection = require_stringifyCollection();
2013
2013
  var addPairToJSMap = require_addPairToJSMap();
@@ -2149,9 +2149,9 @@ var require_YAMLMap = __commonJS({
2149
2149
  }
2150
2150
  });
2151
2151
 
2152
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/common/map.js
2152
+ // ../../node_modules/yaml/dist/schema/common/map.js
2153
2153
  var require_map = __commonJS({
2154
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/common/map.js"(exports) {
2154
+ "../../node_modules/yaml/dist/schema/common/map.js"(exports) {
2155
2155
  "use strict";
2156
2156
  var identity = require_identity();
2157
2157
  var YAMLMap = require_YAMLMap();
@@ -2171,9 +2171,9 @@ var require_map = __commonJS({
2171
2171
  }
2172
2172
  });
2173
2173
 
2174
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/YAMLSeq.js
2174
+ // ../../node_modules/yaml/dist/nodes/YAMLSeq.js
2175
2175
  var require_YAMLSeq = __commonJS({
2176
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/nodes/YAMLSeq.js"(exports) {
2176
+ "../../node_modules/yaml/dist/nodes/YAMLSeq.js"(exports) {
2177
2177
  "use strict";
2178
2178
  var createNode = require_createNode();
2179
2179
  var stringifyCollection = require_stringifyCollection();
@@ -2287,9 +2287,9 @@ var require_YAMLSeq = __commonJS({
2287
2287
  }
2288
2288
  });
2289
2289
 
2290
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/common/seq.js
2290
+ // ../../node_modules/yaml/dist/schema/common/seq.js
2291
2291
  var require_seq = __commonJS({
2292
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/common/seq.js"(exports) {
2292
+ "../../node_modules/yaml/dist/schema/common/seq.js"(exports) {
2293
2293
  "use strict";
2294
2294
  var identity = require_identity();
2295
2295
  var YAMLSeq = require_YAMLSeq();
@@ -2309,9 +2309,9 @@ var require_seq = __commonJS({
2309
2309
  }
2310
2310
  });
2311
2311
 
2312
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/common/string.js
2312
+ // ../../node_modules/yaml/dist/schema/common/string.js
2313
2313
  var require_string = __commonJS({
2314
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/common/string.js"(exports) {
2314
+ "../../node_modules/yaml/dist/schema/common/string.js"(exports) {
2315
2315
  "use strict";
2316
2316
  var stringifyString = require_stringifyString();
2317
2317
  var string = {
@@ -2328,9 +2328,9 @@ var require_string = __commonJS({
2328
2328
  }
2329
2329
  });
2330
2330
 
2331
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/common/null.js
2331
+ // ../../node_modules/yaml/dist/schema/common/null.js
2332
2332
  var require_null = __commonJS({
2333
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/common/null.js"(exports) {
2333
+ "../../node_modules/yaml/dist/schema/common/null.js"(exports) {
2334
2334
  "use strict";
2335
2335
  var Scalar = require_Scalar();
2336
2336
  var nullTag = {
@@ -2346,9 +2346,9 @@ var require_null = __commonJS({
2346
2346
  }
2347
2347
  });
2348
2348
 
2349
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/core/bool.js
2349
+ // ../../node_modules/yaml/dist/schema/core/bool.js
2350
2350
  var require_bool = __commonJS({
2351
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/core/bool.js"(exports) {
2351
+ "../../node_modules/yaml/dist/schema/core/bool.js"(exports) {
2352
2352
  "use strict";
2353
2353
  var Scalar = require_Scalar();
2354
2354
  var boolTag = {
@@ -2370,9 +2370,9 @@ var require_bool = __commonJS({
2370
2370
  }
2371
2371
  });
2372
2372
 
2373
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringifyNumber.js
2373
+ // ../../node_modules/yaml/dist/stringify/stringifyNumber.js
2374
2374
  var require_stringifyNumber = __commonJS({
2375
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringifyNumber.js"(exports) {
2375
+ "../../node_modules/yaml/dist/stringify/stringifyNumber.js"(exports) {
2376
2376
  "use strict";
2377
2377
  function stringifyNumber({ format, minFractionDigits, tag, value }) {
2378
2378
  if (typeof value === "bigint")
@@ -2397,9 +2397,9 @@ var require_stringifyNumber = __commonJS({
2397
2397
  }
2398
2398
  });
2399
2399
 
2400
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/core/float.js
2400
+ // ../../node_modules/yaml/dist/schema/core/float.js
2401
2401
  var require_float = __commonJS({
2402
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/core/float.js"(exports) {
2402
+ "../../node_modules/yaml/dist/schema/core/float.js"(exports) {
2403
2403
  "use strict";
2404
2404
  var Scalar = require_Scalar();
2405
2405
  var stringifyNumber = require_stringifyNumber();
@@ -2443,9 +2443,9 @@ var require_float = __commonJS({
2443
2443
  }
2444
2444
  });
2445
2445
 
2446
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/core/int.js
2446
+ // ../../node_modules/yaml/dist/schema/core/int.js
2447
2447
  var require_int = __commonJS({
2448
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/core/int.js"(exports) {
2448
+ "../../node_modules/yaml/dist/schema/core/int.js"(exports) {
2449
2449
  "use strict";
2450
2450
  var stringifyNumber = require_stringifyNumber();
2451
2451
  var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
@@ -2488,9 +2488,9 @@ var require_int = __commonJS({
2488
2488
  }
2489
2489
  });
2490
2490
 
2491
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/core/schema.js
2491
+ // ../../node_modules/yaml/dist/schema/core/schema.js
2492
2492
  var require_schema = __commonJS({
2493
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/core/schema.js"(exports) {
2493
+ "../../node_modules/yaml/dist/schema/core/schema.js"(exports) {
2494
2494
  "use strict";
2495
2495
  var map = require_map();
2496
2496
  var _null = require_null();
@@ -2516,9 +2516,9 @@ var require_schema = __commonJS({
2516
2516
  }
2517
2517
  });
2518
2518
 
2519
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/json/schema.js
2519
+ // ../../node_modules/yaml/dist/schema/json/schema.js
2520
2520
  var require_schema2 = __commonJS({
2521
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/json/schema.js"(exports) {
2521
+ "../../node_modules/yaml/dist/schema/json/schema.js"(exports) {
2522
2522
  "use strict";
2523
2523
  var Scalar = require_Scalar();
2524
2524
  var map = require_map();
@@ -2583,9 +2583,9 @@ var require_schema2 = __commonJS({
2583
2583
  }
2584
2584
  });
2585
2585
 
2586
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/binary.js
2586
+ // ../../node_modules/yaml/dist/schema/yaml-1.1/binary.js
2587
2587
  var require_binary = __commonJS({
2588
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/binary.js"(exports) {
2588
+ "../../node_modules/yaml/dist/schema/yaml-1.1/binary.js"(exports) {
2589
2589
  "use strict";
2590
2590
  var node_buffer = __require("buffer");
2591
2591
  var Scalar = require_Scalar();
@@ -2649,9 +2649,9 @@ var require_binary = __commonJS({
2649
2649
  }
2650
2650
  });
2651
2651
 
2652
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/pairs.js
2652
+ // ../../node_modules/yaml/dist/schema/yaml-1.1/pairs.js
2653
2653
  var require_pairs = __commonJS({
2654
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/pairs.js"(exports) {
2654
+ "../../node_modules/yaml/dist/schema/yaml-1.1/pairs.js"(exports) {
2655
2655
  "use strict";
2656
2656
  var identity = require_identity();
2657
2657
  var Pair = require_Pair();
@@ -2727,9 +2727,9 @@ ${cn.comment}` : item.comment;
2727
2727
  }
2728
2728
  });
2729
2729
 
2730
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/omap.js
2730
+ // ../../node_modules/yaml/dist/schema/yaml-1.1/omap.js
2731
2731
  var require_omap = __commonJS({
2732
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/omap.js"(exports) {
2732
+ "../../node_modules/yaml/dist/schema/yaml-1.1/omap.js"(exports) {
2733
2733
  "use strict";
2734
2734
  var identity = require_identity();
2735
2735
  var toJS = require_toJS();
@@ -2805,9 +2805,9 @@ var require_omap = __commonJS({
2805
2805
  }
2806
2806
  });
2807
2807
 
2808
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/bool.js
2808
+ // ../../node_modules/yaml/dist/schema/yaml-1.1/bool.js
2809
2809
  var require_bool2 = __commonJS({
2810
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/bool.js"(exports) {
2810
+ "../../node_modules/yaml/dist/schema/yaml-1.1/bool.js"(exports) {
2811
2811
  "use strict";
2812
2812
  var Scalar = require_Scalar();
2813
2813
  function boolStringify({ value, source }, ctx) {
@@ -2837,9 +2837,9 @@ var require_bool2 = __commonJS({
2837
2837
  }
2838
2838
  });
2839
2839
 
2840
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/float.js
2840
+ // ../../node_modules/yaml/dist/schema/yaml-1.1/float.js
2841
2841
  var require_float2 = __commonJS({
2842
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/float.js"(exports) {
2842
+ "../../node_modules/yaml/dist/schema/yaml-1.1/float.js"(exports) {
2843
2843
  "use strict";
2844
2844
  var Scalar = require_Scalar();
2845
2845
  var stringifyNumber = require_stringifyNumber();
@@ -2886,9 +2886,9 @@ var require_float2 = __commonJS({
2886
2886
  }
2887
2887
  });
2888
2888
 
2889
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/int.js
2889
+ // ../../node_modules/yaml/dist/schema/yaml-1.1/int.js
2890
2890
  var require_int2 = __commonJS({
2891
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/int.js"(exports) {
2891
+ "../../node_modules/yaml/dist/schema/yaml-1.1/int.js"(exports) {
2892
2892
  "use strict";
2893
2893
  var stringifyNumber = require_stringifyNumber();
2894
2894
  var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
@@ -2965,9 +2965,9 @@ var require_int2 = __commonJS({
2965
2965
  }
2966
2966
  });
2967
2967
 
2968
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/set.js
2968
+ // ../../node_modules/yaml/dist/schema/yaml-1.1/set.js
2969
2969
  var require_set = __commonJS({
2970
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/set.js"(exports) {
2970
+ "../../node_modules/yaml/dist/schema/yaml-1.1/set.js"(exports) {
2971
2971
  "use strict";
2972
2972
  var identity = require_identity();
2973
2973
  var Pair = require_Pair();
@@ -3054,9 +3054,9 @@ var require_set = __commonJS({
3054
3054
  }
3055
3055
  });
3056
3056
 
3057
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
3057
+ // ../../node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
3058
3058
  var require_timestamp = __commonJS({
3059
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js"(exports) {
3059
+ "../../node_modules/yaml/dist/schema/yaml-1.1/timestamp.js"(exports) {
3060
3060
  "use strict";
3061
3061
  var stringifyNumber = require_stringifyNumber();
3062
3062
  function parseSexagesimal(str, asBigInt) {
@@ -3142,9 +3142,9 @@ var require_timestamp = __commonJS({
3142
3142
  }
3143
3143
  });
3144
3144
 
3145
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/schema.js
3145
+ // ../../node_modules/yaml/dist/schema/yaml-1.1/schema.js
3146
3146
  var require_schema3 = __commonJS({
3147
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/yaml-1.1/schema.js"(exports) {
3147
+ "../../node_modules/yaml/dist/schema/yaml-1.1/schema.js"(exports) {
3148
3148
  "use strict";
3149
3149
  var map = require_map();
3150
3150
  var _null = require_null();
@@ -3186,9 +3186,9 @@ var require_schema3 = __commonJS({
3186
3186
  }
3187
3187
  });
3188
3188
 
3189
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/tags.js
3189
+ // ../../node_modules/yaml/dist/schema/tags.js
3190
3190
  var require_tags = __commonJS({
3191
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/tags.js"(exports) {
3191
+ "../../node_modules/yaml/dist/schema/tags.js"(exports) {
3192
3192
  "use strict";
3193
3193
  var map = require_map();
3194
3194
  var _null = require_null();
@@ -3280,9 +3280,9 @@ var require_tags = __commonJS({
3280
3280
  }
3281
3281
  });
3282
3282
 
3283
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/Schema.js
3283
+ // ../../node_modules/yaml/dist/schema/Schema.js
3284
3284
  var require_Schema = __commonJS({
3285
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/schema/Schema.js"(exports) {
3285
+ "../../node_modules/yaml/dist/schema/Schema.js"(exports) {
3286
3286
  "use strict";
3287
3287
  var identity = require_identity();
3288
3288
  var map = require_map();
@@ -3312,9 +3312,9 @@ var require_Schema = __commonJS({
3312
3312
  }
3313
3313
  });
3314
3314
 
3315
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringifyDocument.js
3315
+ // ../../node_modules/yaml/dist/stringify/stringifyDocument.js
3316
3316
  var require_stringifyDocument = __commonJS({
3317
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/stringify/stringifyDocument.js"(exports) {
3317
+ "../../node_modules/yaml/dist/stringify/stringifyDocument.js"(exports) {
3318
3318
  "use strict";
3319
3319
  var identity = require_identity();
3320
3320
  var stringify = require_stringify();
@@ -3392,9 +3392,9 @@ var require_stringifyDocument = __commonJS({
3392
3392
  }
3393
3393
  });
3394
3394
 
3395
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/doc/Document.js
3395
+ // ../../node_modules/yaml/dist/doc/Document.js
3396
3396
  var require_Document = __commonJS({
3397
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/doc/Document.js"(exports) {
3397
+ "../../node_modules/yaml/dist/doc/Document.js"(exports) {
3398
3398
  "use strict";
3399
3399
  var Alias = require_Alias();
3400
3400
  var Collection = require_Collection();
@@ -3701,9 +3701,9 @@ var require_Document = __commonJS({
3701
3701
  }
3702
3702
  });
3703
3703
 
3704
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/errors.js
3704
+ // ../../node_modules/yaml/dist/errors.js
3705
3705
  var require_errors = __commonJS({
3706
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/errors.js"(exports) {
3706
+ "../../node_modules/yaml/dist/errors.js"(exports) {
3707
3707
  "use strict";
3708
3708
  var YAMLError = class extends Error {
3709
3709
  constructor(name, pos, code, message) {
@@ -3766,9 +3766,9 @@ ${pointer}
3766
3766
  }
3767
3767
  });
3768
3768
 
3769
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-props.js
3769
+ // ../../node_modules/yaml/dist/compose/resolve-props.js
3770
3770
  var require_resolve_props = __commonJS({
3771
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-props.js"(exports) {
3771
+ "../../node_modules/yaml/dist/compose/resolve-props.js"(exports) {
3772
3772
  "use strict";
3773
3773
  function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) {
3774
3774
  let spaceBefore = false;
@@ -3900,9 +3900,9 @@ var require_resolve_props = __commonJS({
3900
3900
  }
3901
3901
  });
3902
3902
 
3903
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/util-contains-newline.js
3903
+ // ../../node_modules/yaml/dist/compose/util-contains-newline.js
3904
3904
  var require_util_contains_newline = __commonJS({
3905
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/util-contains-newline.js"(exports) {
3905
+ "../../node_modules/yaml/dist/compose/util-contains-newline.js"(exports) {
3906
3906
  "use strict";
3907
3907
  function containsNewline(key) {
3908
3908
  if (!key)
@@ -3942,9 +3942,9 @@ var require_util_contains_newline = __commonJS({
3942
3942
  }
3943
3943
  });
3944
3944
 
3945
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/util-flow-indent-check.js
3945
+ // ../../node_modules/yaml/dist/compose/util-flow-indent-check.js
3946
3946
  var require_util_flow_indent_check = __commonJS({
3947
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/util-flow-indent-check.js"(exports) {
3947
+ "../../node_modules/yaml/dist/compose/util-flow-indent-check.js"(exports) {
3948
3948
  "use strict";
3949
3949
  var utilContainsNewline = require_util_contains_newline();
3950
3950
  function flowIndentCheck(indent, fc, onError) {
@@ -3960,9 +3960,9 @@ var require_util_flow_indent_check = __commonJS({
3960
3960
  }
3961
3961
  });
3962
3962
 
3963
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/util-map-includes.js
3963
+ // ../../node_modules/yaml/dist/compose/util-map-includes.js
3964
3964
  var require_util_map_includes = __commonJS({
3965
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/util-map-includes.js"(exports) {
3965
+ "../../node_modules/yaml/dist/compose/util-map-includes.js"(exports) {
3966
3966
  "use strict";
3967
3967
  var identity = require_identity();
3968
3968
  function mapIncludes(ctx, items, search) {
@@ -3976,9 +3976,9 @@ var require_util_map_includes = __commonJS({
3976
3976
  }
3977
3977
  });
3978
3978
 
3979
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-block-map.js
3979
+ // ../../node_modules/yaml/dist/compose/resolve-block-map.js
3980
3980
  var require_resolve_block_map = __commonJS({
3981
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-block-map.js"(exports) {
3981
+ "../../node_modules/yaml/dist/compose/resolve-block-map.js"(exports) {
3982
3982
  "use strict";
3983
3983
  var Pair = require_Pair();
3984
3984
  var YAMLMap = require_YAMLMap();
@@ -4084,9 +4084,9 @@ var require_resolve_block_map = __commonJS({
4084
4084
  }
4085
4085
  });
4086
4086
 
4087
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-block-seq.js
4087
+ // ../../node_modules/yaml/dist/compose/resolve-block-seq.js
4088
4088
  var require_resolve_block_seq = __commonJS({
4089
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-block-seq.js"(exports) {
4089
+ "../../node_modules/yaml/dist/compose/resolve-block-seq.js"(exports) {
4090
4090
  "use strict";
4091
4091
  var YAMLSeq = require_YAMLSeq();
4092
4092
  var resolveProps = require_resolve_props();
@@ -4135,9 +4135,9 @@ var require_resolve_block_seq = __commonJS({
4135
4135
  }
4136
4136
  });
4137
4137
 
4138
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-end.js
4138
+ // ../../node_modules/yaml/dist/compose/resolve-end.js
4139
4139
  var require_resolve_end = __commonJS({
4140
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-end.js"(exports) {
4140
+ "../../node_modules/yaml/dist/compose/resolve-end.js"(exports) {
4141
4141
  "use strict";
4142
4142
  function resolveEnd(end, offset, reqSpace, onError) {
4143
4143
  let comment = "";
@@ -4178,9 +4178,9 @@ var require_resolve_end = __commonJS({
4178
4178
  }
4179
4179
  });
4180
4180
 
4181
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-flow-collection.js
4181
+ // ../../node_modules/yaml/dist/compose/resolve-flow-collection.js
4182
4182
  var require_resolve_flow_collection = __commonJS({
4183
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-flow-collection.js"(exports) {
4183
+ "../../node_modules/yaml/dist/compose/resolve-flow-collection.js"(exports) {
4184
4184
  "use strict";
4185
4185
  var identity = require_identity();
4186
4186
  var Pair = require_Pair();
@@ -4372,9 +4372,9 @@ var require_resolve_flow_collection = __commonJS({
4372
4372
  }
4373
4373
  });
4374
4374
 
4375
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/compose-collection.js
4375
+ // ../../node_modules/yaml/dist/compose/compose-collection.js
4376
4376
  var require_compose_collection = __commonJS({
4377
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/compose-collection.js"(exports) {
4377
+ "../../node_modules/yaml/dist/compose/compose-collection.js"(exports) {
4378
4378
  "use strict";
4379
4379
  var identity = require_identity();
4380
4380
  var Scalar = require_Scalar();
@@ -4437,9 +4437,9 @@ var require_compose_collection = __commonJS({
4437
4437
  }
4438
4438
  });
4439
4439
 
4440
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-block-scalar.js
4440
+ // ../../node_modules/yaml/dist/compose/resolve-block-scalar.js
4441
4441
  var require_resolve_block_scalar = __commonJS({
4442
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-block-scalar.js"(exports) {
4442
+ "../../node_modules/yaml/dist/compose/resolve-block-scalar.js"(exports) {
4443
4443
  "use strict";
4444
4444
  var Scalar = require_Scalar();
4445
4445
  function resolveBlockScalar(ctx, scalar, onError) {
@@ -4620,9 +4620,9 @@ var require_resolve_block_scalar = __commonJS({
4620
4620
  }
4621
4621
  });
4622
4622
 
4623
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-flow-scalar.js
4623
+ // ../../node_modules/yaml/dist/compose/resolve-flow-scalar.js
4624
4624
  var require_resolve_flow_scalar = __commonJS({
4625
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/resolve-flow-scalar.js"(exports) {
4625
+ "../../node_modules/yaml/dist/compose/resolve-flow-scalar.js"(exports) {
4626
4626
  "use strict";
4627
4627
  var Scalar = require_Scalar();
4628
4628
  var resolveEnd = require_resolve_end();
@@ -4841,9 +4841,9 @@ var require_resolve_flow_scalar = __commonJS({
4841
4841
  }
4842
4842
  });
4843
4843
 
4844
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/compose-scalar.js
4844
+ // ../../node_modules/yaml/dist/compose/compose-scalar.js
4845
4845
  var require_compose_scalar = __commonJS({
4846
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/compose-scalar.js"(exports) {
4846
+ "../../node_modules/yaml/dist/compose/compose-scalar.js"(exports) {
4847
4847
  "use strict";
4848
4848
  var identity = require_identity();
4849
4849
  var Scalar = require_Scalar();
@@ -4922,9 +4922,9 @@ var require_compose_scalar = __commonJS({
4922
4922
  }
4923
4923
  });
4924
4924
 
4925
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/util-empty-scalar-position.js
4925
+ // ../../node_modules/yaml/dist/compose/util-empty-scalar-position.js
4926
4926
  var require_util_empty_scalar_position = __commonJS({
4927
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/util-empty-scalar-position.js"(exports) {
4927
+ "../../node_modules/yaml/dist/compose/util-empty-scalar-position.js"(exports) {
4928
4928
  "use strict";
4929
4929
  function emptyScalarPosition(offset, before, pos) {
4930
4930
  if (before) {
@@ -4952,9 +4952,9 @@ var require_util_empty_scalar_position = __commonJS({
4952
4952
  }
4953
4953
  });
4954
4954
 
4955
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/compose-node.js
4955
+ // ../../node_modules/yaml/dist/compose/compose-node.js
4956
4956
  var require_compose_node = __commonJS({
4957
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/compose-node.js"(exports) {
4957
+ "../../node_modules/yaml/dist/compose/compose-node.js"(exports) {
4958
4958
  "use strict";
4959
4959
  var Alias = require_Alias();
4960
4960
  var identity = require_identity();
@@ -5058,9 +5058,9 @@ var require_compose_node = __commonJS({
5058
5058
  }
5059
5059
  });
5060
5060
 
5061
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/compose-doc.js
5061
+ // ../../node_modules/yaml/dist/compose/compose-doc.js
5062
5062
  var require_compose_doc = __commonJS({
5063
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/compose-doc.js"(exports) {
5063
+ "../../node_modules/yaml/dist/compose/compose-doc.js"(exports) {
5064
5064
  "use strict";
5065
5065
  var Document = require_Document();
5066
5066
  var composeNode = require_compose_node();
@@ -5101,9 +5101,9 @@ var require_compose_doc = __commonJS({
5101
5101
  }
5102
5102
  });
5103
5103
 
5104
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/composer.js
5104
+ // ../../node_modules/yaml/dist/compose/composer.js
5105
5105
  var require_composer = __commonJS({
5106
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/compose/composer.js"(exports) {
5106
+ "../../node_modules/yaml/dist/compose/composer.js"(exports) {
5107
5107
  "use strict";
5108
5108
  var node_process = __require("process");
5109
5109
  var directives = require_directives();
@@ -5309,9 +5309,9 @@ ${end.comment}` : end.comment;
5309
5309
  }
5310
5310
  });
5311
5311
 
5312
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/cst-scalar.js
5312
+ // ../../node_modules/yaml/dist/parse/cst-scalar.js
5313
5313
  var require_cst_scalar = __commonJS({
5314
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/cst-scalar.js"(exports) {
5314
+ "../../node_modules/yaml/dist/parse/cst-scalar.js"(exports) {
5315
5315
  "use strict";
5316
5316
  var resolveBlockScalar = require_resolve_block_scalar();
5317
5317
  var resolveFlowScalar = require_resolve_flow_scalar();
@@ -5494,9 +5494,9 @@ var require_cst_scalar = __commonJS({
5494
5494
  }
5495
5495
  });
5496
5496
 
5497
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/cst-stringify.js
5497
+ // ../../node_modules/yaml/dist/parse/cst-stringify.js
5498
5498
  var require_cst_stringify = __commonJS({
5499
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/cst-stringify.js"(exports) {
5499
+ "../../node_modules/yaml/dist/parse/cst-stringify.js"(exports) {
5500
5500
  "use strict";
5501
5501
  var stringify = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
5502
5502
  function stringifyToken(token) {
@@ -5555,9 +5555,9 @@ var require_cst_stringify = __commonJS({
5555
5555
  }
5556
5556
  });
5557
5557
 
5558
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/cst-visit.js
5558
+ // ../../node_modules/yaml/dist/parse/cst-visit.js
5559
5559
  var require_cst_visit = __commonJS({
5560
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/cst-visit.js"(exports) {
5560
+ "../../node_modules/yaml/dist/parse/cst-visit.js"(exports) {
5561
5561
  "use strict";
5562
5562
  var BREAK = /* @__PURE__ */ Symbol("break visit");
5563
5563
  var SKIP2 = /* @__PURE__ */ Symbol("skip children");
@@ -5617,9 +5617,9 @@ var require_cst_visit = __commonJS({
5617
5617
  }
5618
5618
  });
5619
5619
 
5620
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/cst.js
5620
+ // ../../node_modules/yaml/dist/parse/cst.js
5621
5621
  var require_cst = __commonJS({
5622
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/cst.js"(exports) {
5622
+ "../../node_modules/yaml/dist/parse/cst.js"(exports) {
5623
5623
  "use strict";
5624
5624
  var cstScalar = require_cst_scalar();
5625
5625
  var cstStringify = require_cst_stringify();
@@ -5719,9 +5719,9 @@ var require_cst = __commonJS({
5719
5719
  }
5720
5720
  });
5721
5721
 
5722
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/lexer.js
5722
+ // ../../node_modules/yaml/dist/parse/lexer.js
5723
5723
  var require_lexer = __commonJS({
5724
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/lexer.js"(exports) {
5724
+ "../../node_modules/yaml/dist/parse/lexer.js"(exports) {
5725
5725
  "use strict";
5726
5726
  var cst = require_cst();
5727
5727
  function isEmpty(ch) {
@@ -6308,9 +6308,9 @@ var require_lexer = __commonJS({
6308
6308
  }
6309
6309
  });
6310
6310
 
6311
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/line-counter.js
6311
+ // ../../node_modules/yaml/dist/parse/line-counter.js
6312
6312
  var require_line_counter = __commonJS({
6313
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/line-counter.js"(exports) {
6313
+ "../../node_modules/yaml/dist/parse/line-counter.js"(exports) {
6314
6314
  "use strict";
6315
6315
  var LineCounter = class {
6316
6316
  constructor() {
@@ -6339,9 +6339,9 @@ var require_line_counter = __commonJS({
6339
6339
  }
6340
6340
  });
6341
6341
 
6342
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/parser.js
6342
+ // ../../node_modules/yaml/dist/parse/parser.js
6343
6343
  var require_parser = __commonJS({
6344
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/parse/parser.js"(exports) {
6344
+ "../../node_modules/yaml/dist/parse/parser.js"(exports) {
6345
6345
  "use strict";
6346
6346
  var node_process = __require("process");
6347
6347
  var cst = require_cst();
@@ -7213,9 +7213,9 @@ var require_parser = __commonJS({
7213
7213
  }
7214
7214
  });
7215
7215
 
7216
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/public-api.js
7216
+ // ../../node_modules/yaml/dist/public-api.js
7217
7217
  var require_public_api = __commonJS({
7218
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/public-api.js"(exports) {
7218
+ "../../node_modules/yaml/dist/public-api.js"(exports) {
7219
7219
  "use strict";
7220
7220
  var composer = require_composer();
7221
7221
  var Document = require_Document();
@@ -7310,9 +7310,9 @@ var require_public_api = __commonJS({
7310
7310
  }
7311
7311
  });
7312
7312
 
7313
- // ../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/index.js
7313
+ // ../../node_modules/yaml/dist/index.js
7314
7314
  var require_dist = __commonJS({
7315
- "../../../../../../../../../Users/bliss/ghosty-studio/node_modules/yaml/dist/index.js"(exports) {
7315
+ "../../node_modules/yaml/dist/index.js"(exports) {
7316
7316
  "use strict";
7317
7317
  var composer = require_composer();
7318
7318
  var Document = require_Document();
@@ -8478,7 +8478,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8478
8478
  const req = httpsRequest(`${base}${path}`, {
8479
8479
  method: "POST",
8480
8480
  timeout: timeoutMs,
8481
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.66.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8481
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.68.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8482
8482
  }, (res) => {
8483
8483
  let buf = "";
8484
8484
  res.on("data", (c) => buf += c);
@@ -8520,7 +8520,7 @@ async function request(path, opts = {}, retried = false) {
8520
8520
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8521
8521
  headers: {
8522
8522
  authorization: `Bearer ${token}`,
8523
- "user-agent": `ghosty-cli/${"0.66.0"}`,
8523
+ "user-agent": `ghosty-cli/${"0.68.0"}`,
8524
8524
  "accept-language": lang(),
8525
8525
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8526
8526
  ...opts.headers
@@ -10318,6 +10318,54 @@ ${dim(`${t("conversation", "conversaci\xF3n")} ${p.sessionId}`)}`
10318
10318
  throw usage(t(`Unknown schedule subcommand: ${sub ?? "(none)"}.`, `Subcomando de schedule desconocido: ${sub ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty schedule ls | add | rm");
10319
10319
  }
10320
10320
 
10321
+ // src/commands/teams.ts
10322
+ var enc5 = encodeURIComponent;
10323
+ var sleep2 = (ms) => new Promise((ok) => setTimeout(ok, ms));
10324
+ function allAnswered(respondents, replies) {
10325
+ return respondents.filter((r) => !r.steered).every((r) => replies.some((x) => x.handle === r.handle && !x.live && x.body.trim()));
10326
+ }
10327
+ async function post(target, text, v) {
10328
+ const hint = `ghosty teams post business#mailmask "@plan \u2026" \xB7 ghosty teams post business#mailmask/5612 "\u2026"`;
10329
+ if (!target) throw usage(t("Missing where: workspace#room[/thread].", "Falta d\xF3nde: espacio#room[/hilo]."), hint);
10330
+ const where = parseTeamsTarget(target);
10331
+ if (!where.channel) throw usage(t("Missing the room.", "Falta el room."), hint);
10332
+ if (!text?.trim()) throw usage(t("Missing the message.", "Falta el mensaje."), hint);
10333
+ const timeoutS = v.timeout ? Number(v.timeout) : 600;
10334
+ if (!Number.isFinite(timeoutS) || timeoutS <= 0) throw usage(t(`Invalid --timeout: ${v.timeout}.`, `--timeout inv\xE1lido: ${v.timeout}.`), t("Seconds, e.g. --timeout 900", "Segundos, p. ej. --timeout 900"));
10335
+ const r = await api("/api/v2/teams/post", {
10336
+ method: "POST",
10337
+ body: { workspace: where.workspace, channel: where.channel, ...where.parentId ? { parentId: where.parentId } : {}, text }
10338
+ });
10339
+ const who2 = r.respondents.map((x) => `@${x.handle}${x.steered ? t(" (steer)", " (al turno en curso)") : ""}`).join(", ");
10340
+ const head = () => `${lavender("\u2713")} ${t(`Posted in #${r.channel}`, `Publicado en #${r.channel}`)}${where.parentId ? t(` (thread ${r.threadId})`, ` (hilo ${r.threadId})`) : ""}` + (who2 ? ` \xB7 ${t("answering", "contesta")}: ${who2}` : "") + (r.unresolved.length ? ` \xB7 ${t("nobody is", "no existe")}: ${r.unresolved.map((h) => `@${h}`).join(", ")}` : "") + `
10341
+ ${dim(r.url)}`;
10342
+ if (!v.wait || !r.respondents.length) return out(r, head);
10343
+ if (!globals.json) console.log(head());
10344
+ const deadline = Date.now() + timeoutS * 1e3;
10345
+ let replies = [];
10346
+ const q = `workspace=${enc5(where.workspace)}&channel=${enc5(r.channel)}&thread=${r.threadId}&after=${r.id}`;
10347
+ while (Date.now() < deadline) {
10348
+ await sleep2(3e3);
10349
+ replies = (await api(`/api/v2/teams/post?${q}`)).replies;
10350
+ if (allAnswered(r.respondents, replies)) break;
10351
+ }
10352
+ const done = allAnswered(r.respondents, replies);
10353
+ const res = { ...r, replies, done };
10354
+ out(
10355
+ res,
10356
+ () => replies.map((x) => `
10357
+ ${lavender(`@${x.handle}`)}${x.live ? dim(t(" (still working)", " (sigue trabajando)")) : ""}
10358
+ ${x.body.trim() || dim("\u2026")}`).join("\n") + (done ? "" : `
10359
+
10360
+ ${dim(t(`Timed out after ${timeoutS}s; follow it at ${r.url}`, `Pasaron ${timeoutS} s; s\xEDguelo en ${r.url}`))}`)
10361
+ );
10362
+ if (!done) process.exitCode = 1;
10363
+ }
10364
+ async function teamsCmd(sub, target, text, v) {
10365
+ if (sub === "post") return post(target, text, v);
10366
+ throw usage(t(`Unknown teams subcommand: ${sub ?? "(none)"}.`, `Subcomando de teams desconocido: ${sub ?? "(ninguno)"}.`), `ghosty teams post <workspace>#<room>[/<thread>] "\u2026" [--wait]`);
10367
+ }
10368
+
10321
10369
  // src/commands/browser.ts
10322
10370
  async function browserCmd(sub, v) {
10323
10371
  if (sub !== "logout") throw usage(t(`Unknown browser subcommand: ${sub ?? "(none)"}.`, `Subcomando de browser desconocido: ${sub ?? "(ninguno)"}.`), "ghosty browser logout");
@@ -10424,7 +10472,7 @@ ${dim("(GHOSTY_PARTNER_SECRET tambi\xE9n vale; NO es el secreto global del servi
10424
10472
  ${dim("El mismo par gpk_/gps_ firma POST /api/v2/fleet-agents/<id>/message-stream de los agentes normales de este espacio (claude-worker), con turnToken por usuario final.")}
10425
10473
  ${dim("Docs: https://www.ghosty.studio/docs/cli/partners")}`
10426
10474
  );
10427
- var enc5 = encodeURIComponent;
10475
+ var enc6 = encodeURIComponent;
10428
10476
  function partnerCreds() {
10429
10477
  const key = process.env.GHOSTY_PARTNER_KEY;
10430
10478
  const secret = process.env.GHOSTY_PARTNER_KEY_SECRET || process.env.GHOSTY_PARTNER_SECRET;
@@ -10444,7 +10492,7 @@ async function call(path, v, init = {}) {
10444
10492
  "x-ghosty-ts": ts,
10445
10493
  "x-ghosty-sig": sig,
10446
10494
  "content-type": "application/json",
10447
- "user-agent": `ghosty-cli/${"0.66.0"}`
10495
+ "user-agent": `ghosty-cli/${"0.68.0"}`
10448
10496
  },
10449
10497
  body: method === "GET" ? void 0 : raw
10450
10498
  });
@@ -10503,17 +10551,17 @@ async function credentialsCmd(sub, id, v) {
10503
10551
  if (sub === "rotate") {
10504
10552
  const grace = v.grace !== void 0 ? Number(v.grace) : void 0;
10505
10553
  if (grace !== void 0 && (!Number.isFinite(grace) || grace < 0)) throw usage(t("--grace must be minutes \u2265 0.", "--grace deben ser minutos \u2265 0."));
10506
- return showSecret(await json(`/api/v2/partner/credentials/${enc5(id)}/rotate`, v, { ...owner, method: "POST", body: { graceMinutes: grace } }), v);
10554
+ return showSecret(await json(`/api/v2/partner/credentials/${enc6(id)}/rotate`, v, { ...owner, method: "POST", body: { graceMinutes: grace } }), v);
10507
10555
  }
10508
10556
  if (sub === "revoke" || sub === "rm") {
10509
- const r = await json(`/api/v2/partner/credentials/${enc5(id)}`, v, { ...owner, method: "DELETE" });
10557
+ const r = await json(`/api/v2/partner/credentials/${enc6(id)}`, v, { ...owner, method: "DELETE" });
10510
10558
  return out(r, () => `${lavender("\u2713")} ${t(`Revoked ${r.keyId}.`, `${r.keyId} revocada.`)}`);
10511
10559
  }
10512
10560
  throw usage(t(`Unknown credentials subcommand: ${sub}.`, `Subcomando de credentials desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty partner credentials ls | create | rotate | revoke");
10513
10561
  }
10514
10562
  async function agentCmd(sub, v) {
10515
10563
  if (!sub || sub === "get") {
10516
- const r = await json(`/api/v2/partner/agent${v.agent ? `?slug=${enc5(v.agent)}` : ""}`, v);
10564
+ const r = await json(`/api/v2/partner/agent${v.agent ? `?slug=${enc6(v.agent)}` : ""}`, v);
10517
10565
  return out(r, () => {
10518
10566
  const a = r.agent;
10519
10567
  return [
@@ -10542,7 +10590,7 @@ async function tenantsCmd(sub, id, v) {
10542
10590
  return out(r, () => r.tenants.length ? table(r.tenants.map((tn) => ({ externalId: tn.externalId, config: JSON.stringify(tn.config ?? {}), updated: fmt(tn.updatedAt) })), ["externalId", "config", "updated"]) : dim(t("No tenants yet: each one appears on its first turn, or create it with ghosty partner tenants set <externalId> --config '{\u2026}'", "Todav\xEDa no hay tenants: cada uno aparece en su primer turno, o cr\xE9alo con ghosty partner tenants set <externalId> --config '{\u2026}'")));
10543
10591
  }
10544
10592
  if (!id) throw usage(t("Missing externalId.", "Falta el externalId."), `${t("Example", "Ejemplo")}: ghosty partner tenants ${sub} org_123`);
10545
- const path = `/api/v2/partner/tenants/${enc5(id)}`;
10593
+ const path = `/api/v2/partner/tenants/${enc6(id)}`;
10546
10594
  if (sub === "get") return out(await json(path, v));
10547
10595
  if (sub === "set" || sub === "apply") {
10548
10596
  if (!v.config) throw usage(t("Missing --config.", "Falta --config."), `${t("Example", "Ejemplo")}: --config '{"name":"Spa","timezone":"America/Mexico_City"}'`);
@@ -10615,11 +10663,11 @@ ${t("Invite", "Invitaci\xF3n")}: ${r.inviteUrl}`);
10615
10663
  if (!jid) throw usage(t("Missing the group jid (\u2026@g.us).", "Falta el jid del grupo (\u2026@g.us)."), `${t("See them", "Velos")}: ghosty partner wa groups ls`);
10616
10664
  if (sub === "link") {
10617
10665
  if (!v.tenant || !v.agent) throw usage(t("Missing --tenant or --agent.", "Falta --tenant o --agent."), `${t("Example", "Ejemplo")}: ghosty partner wa groups link ${jid} --tenant org_123 --agent nik-admin`);
10618
- const r = await json(`${base}/${enc5(jid)}`, v, { method: "PUT", body: { externalId: v.tenant, agent: v.agent, enabled: !v.off } });
10666
+ const r = await json(`${base}/${enc6(jid)}`, v, { method: "PUT", body: { externalId: v.tenant, agent: v.agent, enabled: !v.off } });
10619
10667
  return out(r, () => `${lavender("\u2713")} ${r.group.name} \u2192 ${r.group.externalId} \xB7 ${r.group.enabled ? t("on (answers everything)", "prendido (contesta todo)") : t("off", "apagado")}`);
10620
10668
  }
10621
10669
  if (sub === "unlink" || sub === "rm") {
10622
- const r = await json(`${base}/${enc5(jid)}${v.agent ? `?agent=${enc5(v.agent)}` : ""}`, v, { method: "DELETE" });
10670
+ const r = await json(`${base}/${enc6(jid)}${v.agent ? `?agent=${enc6(v.agent)}` : ""}`, v, { method: "DELETE" });
10623
10671
  return out(r, () => r.unlinked ? `${lavender("\u2713")} ${t("Unlinked and off. The number stays in the group.", "Desligado y apagado. El n\xFAmero sigue en el grupo.")}` : dim(t("It wasn't linked.", "No estaba ligado.")));
10624
10672
  }
10625
10673
  throw usage(t(`Unknown wa groups subcommand: ${sub}.`, `Subcomando de wa groups desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty partner wa groups ls | create | link | unlink");
@@ -10630,7 +10678,7 @@ async function wabaCmd(what, sub, id, v) {
10630
10678
  const row = (l) => ({ id: l.id, business: l.externalId, agent: l.agent ?? "app", number: l.phoneNumber ? `+${l.phoneNumber}` : dim("\u2014"), integration: l.integrationId ?? dim("\u2014"), status: l.enabled ? lavender("on") : dim("off") });
10631
10679
  const cols = ["id", "business", "agent", "number", "integration", "status"];
10632
10680
  if (!sub || sub === "ls" || sub === "list") {
10633
- const r = await json(`${base}${v.tenant ? `?externalId=${enc5(v.tenant)}` : ""}`, v);
10681
+ const r = await json(`${base}${v.tenant ? `?externalId=${enc6(v.tenant)}` : ""}`, v);
10634
10682
  return out(r, () => r.links.length ? table(r.links.map(row), cols) : dim(t("No links. Create one: ghosty partner waba links create --tenant <id> --agent nik-public", "Sin links. Crea uno: ghosty partner waba links create --tenant <id> --agent nik-public")));
10635
10683
  }
10636
10684
  if (sub === "create") {
@@ -10652,11 +10700,11 @@ async function wabaCmd(what, sub, id, v) {
10652
10700
  if (v.on && v.off) throw usage(t("--on or --off, not both.", "--on o --off, no los dos."));
10653
10701
  const body = { ...v.integration ? { integrationId: v.integration } : {}, ...v.phone ? { phoneNumber: v.phone } : {}, ...v.name ? { displayName: v.name } : {}, ...v.on ? { enabled: true } : v.off ? { enabled: false } : {} };
10654
10702
  if (!Object.keys(body).length) throw usage(t("Nothing to change.", "Nada que cambiar."), "--integration ID --phone N --name N --on|--off");
10655
- const r = await json(`${base}/${enc5(id)}`, v, { method: "PUT", body });
10703
+ const r = await json(`${base}/${enc6(id)}`, v, { method: "PUT", body });
10656
10704
  return out(r, () => table([row(r.link)], cols));
10657
10705
  }
10658
10706
  if (sub === "rm" || sub === "off") {
10659
- const r = await json(`${base}/${enc5(id)}`, v, { method: "DELETE" });
10707
+ const r = await json(`${base}/${enc6(id)}`, v, { method: "DELETE" });
10660
10708
  return out(r, () => `${lavender("\u2713")} ${t("Off: it records and doesn't answer.", "Apagado: registra y no contesta.")}`);
10661
10709
  }
10662
10710
  throw usage(t(`Unknown waba links subcommand: ${sub}.`, `Subcomando de waba links desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty partner waba links ls | create | set | rm");
@@ -10744,8 +10792,8 @@ function parseRowScope(raw) {
10744
10792
  var describeRowScope = (scope) => Object.entries(scope ?? {}).map(([t2, c]) => `${t2} (${c.join(", ")})`).join("; ");
10745
10793
 
10746
10794
  // src/commands/data.ts
10747
- var enc6 = encodeURIComponent;
10748
- var agentPath2 = (id, rest = "") => `/api/v2/agents/${enc6(id)}${rest}`;
10795
+ var enc7 = encodeURIComponent;
10796
+ var agentPath2 = (id, rest = "") => `/api/v2/agents/${enc7(id)}${rest}`;
10749
10797
  var need2 = (v, what, example) => {
10750
10798
  if (!v) throw usage(t(`Missing ${what}.`, `Falta ${what}.`), `${t("Example", "Ejemplo")}: ${example}`);
10751
10799
  return v;
@@ -10800,7 +10848,7 @@ async function dbsImport(id, db, v) {
10800
10848
  const easybitsKey = process.env.EASYBITS_API_KEY;
10801
10849
  if (!easybitsKey) throw usage(t("EASYBITS_API_KEY is not set.", "EASYBITS_API_KEY no est\xE1 definida."), t("Export your EasyBits API key (https://www.easybits.cloud/dash/developer) and run again.", "Exporta tu API key de EasyBits (https://www.easybits.cloud/dash/developer) y vuelve a correrlo."));
10802
10850
  const body = { from: "easybits", sourceDb: m[1], easybitsKey, mode: v.append ? "append" : "replace", ...v.tables ? { tables: csv(v.tables) } : {}, ...v["rehost-images"] ? { rehost: csv(v["rehost-images"]) } : {} };
10803
- const path = agentPath2(id, `/dbs/${enc6(db)}/import`);
10851
+ const path = agentPath2(id, `/dbs/${enc7(db)}/import`);
10804
10852
  const dry = await api(path, { body: { ...body, dryRun: true } });
10805
10853
  if (v["dry-run"]) return out({ ...dry, shrinking: shrinking(dry.plan).map((p) => p.table) }, () => `${dry.db} \u2190 easybits:${m[1]}
10806
10854
  ${planText(dry.plan)}${shrinkWarnings(dry.plan)}${importWarnings(dry.warnings)}${gsOnlyWarnings(dry.gsOnly)}
@@ -10819,7 +10867,7 @@ async function dbsRehost(id, db, v) {
10819
10867
  const ex = "ghosty dbs rehost <agent-id> <db> --table catalogo --column imagen_url [--dry-run]";
10820
10868
  const table2 = need2(v.table, "--table", ex);
10821
10869
  const column = need2(v.column, "--column", ex);
10822
- const path = agentPath2(id, `/dbs/${enc6(db)}/rehost`);
10870
+ const path = agentPath2(id, `/dbs/${enc7(db)}/rehost`);
10823
10871
  const dry = await api(path, { body: { table: table2, column, dryRun: true } });
10824
10872
  const head = `${dry.db} \xB7 ${table2}.${column}: ${t(`${dry.distinct} distinct link(s), ${dry.alreadyOurs} already permanent, ${dry.toCopy} to copy`, `${dry.distinct} liga(s) distintas, ${dry.alreadyOurs} ya permanentes, ${dry.toCopy} por copiar`)}`;
10825
10873
  if (v["dry-run"] || !dry.toCopy) return out(dry, () => `${head}
@@ -10849,7 +10897,7 @@ ${total.failed.map((f) => ` ${f.url} ${dim(f.error)}`).join("\n")}` : ""
10849
10897
  );
10850
10898
  }
10851
10899
  async function dbsRename(id, db, name, dryRun) {
10852
- const r = await api(agentPath2(id, `/dbs/${enc6(db)}/rename`), { body: { name, dryRun } });
10900
+ const r = await api(agentPath2(id, `/dbs/${enc7(db)}/rename`), { body: { name, dryRun } });
10853
10901
  const refs = r.references.flatMap((x) => [
10854
10902
  ` ${bold(x.agent)} ${x.where}${x.derived ? dim(t(" (derived: follows its base)", " (derivado: sigue a su base)")) : ""}`,
10855
10903
  ...x.refs.slice(0, 3).map((l) => dim(` ${x.where === "prompt" || x.where.startsWith("skill:") ? `${l.line}: ` : ""}${l.text}`)),
@@ -10866,7 +10914,7 @@ ${dim(t("Dry run: nothing changed. Without --dry-run those mentions are rewritte
10866
10914
  return out(r, () => [`${lavender("\u2713")} ${head}. ${t(`${r.rewritten.length} mention(s) rewritten.`, `${r.rewritten.length} menci\xF3n(es) reescritas.`)}`, ...failed, dim(`${t("Undo", "Deshacer")}: ghosty dbs rename ${id} ${r.to} ${r.from}`)].join("\n"));
10867
10915
  }
10868
10916
  async function dbsRm(id, db, v) {
10869
- const path = agentPath2(id, `/dbs/${enc6(db)}/delete`);
10917
+ const path = agentPath2(id, `/dbs/${enc7(db)}/delete`);
10870
10918
  const dry = await api(path, { body: { dryRun: true } });
10871
10919
  const rows = dry.tables.reduce((n, x) => n + x.rows, 0);
10872
10920
  const plan = [
@@ -10885,7 +10933,7 @@ async function dbsExport(id, db, v) {
10885
10933
  if (format !== "sqlite" && format !== "csv") throw usage(t("--format takes sqlite or csv.", "--format acepta sqlite o csv."), `ghosty dbs export ${id} ${db} --format csv --table <table>`);
10886
10934
  if (format === "csv" && !v.table) throw usage(t("A CSV is one table: add --table.", "Un CSV es de una tabla: agrega --table."), `ghosty dbs export ${id} ${db} --table <table> (ghosty dbs tables ${id} ${db})`);
10887
10935
  const q = new URLSearchParams({ format, ...v.table ? { table: v.table } : {} });
10888
- const res = await request(agentPath2(id, `/dbs/${enc6(db)}/export?${q}`));
10936
+ const res = await request(agentPath2(id, `/dbs/${enc7(db)}/export?${q}`));
10889
10937
  const suggested = /filename="([^"]+)"/.exec(res.headers.get("content-disposition") ?? "")?.[1] ?? (format === "csv" ? `${db}-${v.table}.csv` : `${db}.sqlite`);
10890
10938
  const file = v.out ?? suggested;
10891
10939
  const bytes = Buffer.from(await res.arrayBuffer());
@@ -10915,7 +10963,7 @@ ${dim(`${t("Delete one", "Borrar una")}: ghosty dbs rm ${id} <db> --dry-run`)}`
10915
10963
  }
10916
10964
  case "tables": {
10917
10965
  db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs tables <agent-id> <db>");
10918
- const r = await api(agentPath2(id, `/dbs/${enc6(db)}`));
10966
+ const r = await api(agentPath2(id, `/dbs/${enc7(db)}`));
10919
10967
  const ext = new Set(r.externalWriteTables);
10920
10968
  const own = r.externalRowScope ?? {};
10921
10969
  const rows = r.tables.map((tb) => ({ table: tb.name, rows: tb.rows, "customer writes": ext.has(tb.name) ? "yes" : "", "customer sees": own[tb.name] ? `own rows (${own[tb.name].join(", ")})` : "", columns: tb.columns.map((c) => `${c.name}${c.pk ? "*" : ""}`).join(", ") }));
@@ -10934,7 +10982,7 @@ ${table(rows, ["table", "rows", "customer writes", "customer sees", "columns"])}
10934
10982
  case "sql": {
10935
10983
  db = need2(db, t("database (id or name)", "la base (id o nombre)"), 'ghosty dbs query <agent-id> <db> "SELECT \u2026"');
10936
10984
  const sql = need2(rest.join(" ").trim() || void 0, t("SQL", "el SQL"), 'ghosty dbs query <agent-id> <db> "SELECT * FROM t LIMIT 10"');
10937
- const r = await api(agentPath2(id, `/dbs/${enc6(db)}/query`), { body: { sql, args: v.arg ?? [], write: v.write === true } }).catch((e) => {
10985
+ const r = await api(agentPath2(id, `/dbs/${enc7(db)}/query`), { body: { sql, args: v.arg ?? [], write: v.write === true } }).catch((e) => {
10938
10986
  if (e instanceof CliError && !v.write && /lectura|SELECT|escribir/i.test(e.message)) throw new CliError(e.message, EXIT.API, t("To write on purpose, add --write.", "Para escribir a prop\xF3sito, agrega --write."));
10939
10987
  throw e;
10940
10988
  });
@@ -10951,7 +10999,7 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
10951
10999
  if (v.table) q.set("table", v.table);
10952
11000
  if (v.since) q.set("since", v.since);
10953
11001
  if (v.limit) q.set("limit", v.limit);
10954
- const r = await api(agentPath2(id, `/dbs/${enc6(db)}/origin${q.size ? `?${q}` : ""}`));
11002
+ const r = await api(agentPath2(id, `/dbs/${enc7(db)}/origin${q.size ? `?${q}` : ""}`));
10955
11003
  const rows = r.writes.map((w) => ({ when: w.created_at, agent: w.agent_id.slice(0, 10), channel: w.canal ?? "", what: `${w.op} ${w.table_name}${w.row_id != null ? ` #${w.row_id}` : ""}`, customer: w.phone10 ?? "", why: w.reason ?? "" }));
10956
11004
  return out(r, () => rows.length ? table(rows, ["when", "agent", "channel", "what", "customer", "why"]) : dim(t("No agent writes recorded yet in this database.", "Todav\xEDa no hay escrituras de agentes registradas en esta base.")));
10957
11005
  }
@@ -10967,7 +11015,7 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
10967
11015
  ...v["external-write"] !== void 0 ? { externalWriteTables: csv(v["external-write"]) } : {},
10968
11016
  ...rowScope !== void 0 ? { externalRowScope: rowScope } : {}
10969
11017
  };
10970
- const r = await api(agentPath2(id, `/dbs/${enc6(db)}`), { method: "PUT", body });
11018
+ const r = await api(agentPath2(id, `/dbs/${enc7(db)}`), { method: "PUT", body });
10971
11019
  const own = describeRowScope(r.externalRowScope);
10972
11020
  return out(r, () => `${lavender("\u2713")} ${r.name} ${t("granted.", "concedida.")} ${dim(t(`From customer channels it writes to: ${r.externalWriteTables.join(", ") || "nothing (read only)"}.${own ? ` Each customer sees only their own rows of: ${own}.` : ""}`, `Desde canales de clientes escribe en: ${r.externalWriteTables.join(", ") || "nada (s\xF3lo lectura)"}.${own ? ` Cada cliente ve s\xF3lo sus propias filas de: ${own}.` : ""}`))}`);
10973
11021
  }
@@ -10996,7 +11044,7 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
10996
11044
  }
10997
11045
  case "revoke": {
10998
11046
  db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs revoke <agent-id> <db>");
10999
- const r = await api(agentPath2(id, `/dbs/${enc6(db)}`), { method: "DELETE" });
11047
+ const r = await api(agentPath2(id, `/dbs/${enc7(db)}`), { method: "DELETE" });
11000
11048
  return out(r, () => `${lavender("\u2713")} ${r.name} ${t("revoked.", "revocada.")} ${dim(`${t("Undo", "Deshacer")}: ghosty dbs grant ${id} ${r.name}`)}`);
11001
11049
  }
11002
11050
  default:
@@ -11050,7 +11098,7 @@ async function agentDocsCmd(sub, id, docs, v) {
11050
11098
  wanted = docs.map((ref) => ({ ref, body }));
11051
11099
  }
11052
11100
  const done = [];
11053
- for (const w of wanted) done.push(await api(agentPath2(id, `/documents/${enc6(w.ref)}`), { method: "PUT", body: w.body }));
11101
+ for (const w of wanted) done.push(await api(agentPath2(id, `/documents/${enc7(w.ref)}`), { method: "PUT", body: w.body }));
11054
11102
  return out(done.length === 1 ? done[0] : { granted: done }, () => done.map((r) => `${lavender("\u2713")} ${r.name}: ${r.mode === "write" ? t("reads and replaces it", "lo lee y lo reemplaza") : t("reads it", "lo lee")}${r.deliverable ? t(", and can send it", ", y puede mandarlo") : t(", never sends it", ", nunca lo manda")}.`).join("\n"));
11055
11103
  }
11056
11104
  case "add": {
@@ -11068,7 +11116,7 @@ async function agentDocsCmd(sub, id, docs, v) {
11068
11116
  }
11069
11117
  case "revoke": {
11070
11118
  doc = need2(doc, t("document (id or exact name)", "el documento (id o nombre exacto)"), 'ghosty agents docs revoke <agent-id> "Cat\xE1logo.pdf"');
11071
- const r = await api(agentPath2(id, `/documents/${enc6(doc)}`), { method: "DELETE" });
11119
+ const r = await api(agentPath2(id, `/documents/${enc7(doc)}`), { method: "DELETE" });
11072
11120
  return out(r, () => `${lavender("\u2713")} ${r.name} ${t("revoked.", "revocado.")} ${dim(`${t("Undo", "Deshacer")}: ghosty agents docs grant ${id} "${r.name}"`)}`);
11073
11121
  }
11074
11122
  case "replace": {
@@ -11079,13 +11127,13 @@ async function agentDocsCmd(sub, id, docs, v) {
11079
11127
  ...v.url ? { url: v.url } : { contentBase64: (await readFile6(v.file)).toString("base64"), ...mimeField(v.file) },
11080
11128
  ...v.note ? { note: v.note } : {}
11081
11129
  };
11082
- const r = await api(agentPath2(id, `/documents/${enc6(doc)}/versions`), { method: "POST", body });
11130
+ const r = await api(agentPath2(id, `/documents/${enc7(doc)}/versions`), { method: "POST", body });
11083
11131
  return out(r, () => `${lavender("\u2713")} ${r.name}: ${t("version", "versi\xF3n")} ${r.version?.n ?? "?"} ${dim(`(${r.size} bytes)`)}. ${dim(`${t("Undo", "Deshacer")}: ghosty agents docs restore ${id} "${r.name}" ${(r.version?.n ?? 2) - 1}`)}`);
11084
11132
  }
11085
11133
  case "history":
11086
11134
  case "versions": {
11087
11135
  doc = need2(doc, t("document (id or exact name)", "el documento (id o nombre exacto)"), 'ghosty agents docs history <agent-id> "Cat\xE1logo.pdf"');
11088
- const r = await api(agentPath2(id, `/documents/${enc6(doc)}/versions`));
11136
+ const r = await api(agentPath2(id, `/documents/${enc7(doc)}/versions`));
11089
11137
  const rows = r.versions.map((x) => ({ v: `${x.n}${x.current ? "*" : ""}`, when: x.createdAt.slice(0, 16).replace("T", " "), by: x.agent ?? x.via ?? x.source, size: String(x.size), note: x.note ?? "" }));
11090
11138
  const used = r.usedBy.map((u) => `${u.agent} (${u.mode === "write" ? t("updates it", "lo actualiza") + (u.writableFrom.length ? `: ${u.writableFrom.join(", ")}` : "") : t("reads it", "lo lee")})`).join(" \xB7 ");
11091
11139
  return out(r, () => `${r.document.name}
@@ -11097,7 +11145,7 @@ ${dim(t("Used by", "Lo usan"))}: ${used}` : ""}`);
11097
11145
  doc = need2(doc, t("document (id or exact name)", "el documento (id o nombre exacto)"), ex);
11098
11146
  const n = Number(need2(docs[1], t("version number", "el n\xFAmero de versi\xF3n"), ex));
11099
11147
  if (!Number.isInteger(n) || n < 1) throw usage(t("The version is a whole number (see docs history).", "La versi\xF3n es un n\xFAmero entero (mira docs history)."), ex);
11100
- const r = await api(agentPath2(id, `/documents/${enc6(doc)}/versions/${n}/restore`), { method: "POST", body: v.note ? { note: v.note } : {} });
11148
+ const r = await api(agentPath2(id, `/documents/${enc7(doc)}/versions/${n}/restore`), { method: "POST", body: v.note ? { note: v.note } : {} });
11101
11149
  return out(r, () => `${lavender("\u2713")} ${r.name}: ${t(`restored from v${n} as version`, `restaurado de la v${n} como versi\xF3n`)} ${r.version?.n ?? "?"}.`);
11102
11150
  }
11103
11151
  default:
@@ -11136,7 +11184,7 @@ async function turnsCancel(id, sid) {
11136
11184
  const ex = "ghosty turns cancel <agent-id> <conversation-id>";
11137
11185
  id = need2(id, t("agent id", "el id del agente"), ex);
11138
11186
  sid = need2(sid, t("conversation id", "el id de la conversaci\xF3n"), ex);
11139
- const r = await api(`/api/v2/me/agents/${enc6(id)}/conversations/${enc6(sid)}/cancel`, { method: "POST", body: {} });
11187
+ const r = await api(`/api/v2/me/agents/${enc7(id)}/conversations/${enc7(sid)}/cancel`, { method: "POST", body: {} });
11140
11188
  const b = r.background ?? { videos: 0, subagents: 0, wakes: 0 };
11141
11189
  const bg = b.videos + b.subagents + b.wakes;
11142
11190
  out(
@@ -11223,7 +11271,7 @@ function mimeField(path) {
11223
11271
  }
11224
11272
  async function turnShow(id, turnId, full = false) {
11225
11273
  id = need2(id, t("agent id", "el id del agente"), "ghosty turns show <agent-id> [turn-id|last] [--full]");
11226
- const r = await api(agentPath2(id, `/turns/${enc6(turnId ?? "last")}`));
11274
+ const r = await api(agentPath2(id, `/turns/${enc7(turnId ?? "last")}`));
11227
11275
  out(r, () => {
11228
11276
  const tu = r.turn;
11229
11277
  const clip = (s2) => full || s2.length <= 600 ? s2 : `${s2.slice(0, 600)}\u2026 ${dim(t(`(+${s2.length - 600} chars, --full)`, `(+${s2.length - 600} caracteres, --full)`))}`;
@@ -11290,7 +11338,7 @@ async function dbsPhotosCmd(verb, id, db, v) {
11290
11338
  const dir = need2(v.dir, "--dir", ex);
11291
11339
  let column = v.column;
11292
11340
  if (!column) {
11293
- const r = await api(agentPath2(id, `/dbs/${enc6(db)}`));
11341
+ const r = await api(agentPath2(id, `/dbs/${enc7(db)}`));
11294
11342
  const tb = r.tables.find((x) => x.name === tableName);
11295
11343
  if (!tb) throw usage(t(`${db} has no table ${tableName}.`, `${db} no tiene la tabla ${tableName}.`));
11296
11344
  column = tb.columns.find((c) => c.name === "imagen_url")?.name;
@@ -11298,7 +11346,7 @@ async function dbsPhotosCmd(verb, id, db, v) {
11298
11346
  }
11299
11347
  const files = photoFiles(await readdir2(dir));
11300
11348
  if (!files.length) throw usage(t(`No .jpg/.png/.webp files in ${dir}.`, `No hay archivos .jpg/.png/.webp en ${dir}.`), t("Name each photo after its key: SKU-123.jpg", "Nombra cada foto con su clave: SKU-123.jpg"));
11301
- const path = agentPath2(id, `/dbs/${enc6(db)}/photos`);
11349
+ const path = agentPath2(id, `/dbs/${enc7(db)}/photos`);
11302
11350
  const results = [];
11303
11351
  for (let i = 0; i < files.length; i += 8) {
11304
11352
  const batch = files.slice(i, i + 8);
@@ -11566,7 +11614,7 @@ async function readMultiFileAuth(dir) {
11566
11614
  }
11567
11615
 
11568
11616
  // src/commands/channels.ts
11569
- var enc7 = encodeURIComponent;
11617
+ var enc8 = encodeURIComponent;
11570
11618
  var digits = (s2) => s2.replace(/\D/g, "");
11571
11619
  function find2(nums, ref) {
11572
11620
  const d = digits(ref);
@@ -11588,7 +11636,7 @@ async function channelsCmd(kind, sub, id, ref, v) {
11588
11636
  if (sub === "integrations") return integrationsCmd(id, v.email);
11589
11637
  if (sub === "import" || sub === "status" || sub === "pause" || sub === "pair") return waCmd2(sub, id, v);
11590
11638
  if (sub === "enable" && !v["dry-run"]) requireYesIfHeadless(v.yes, `ghosty channels whatsapp enable ${id} ${ref ?? "<number>"}`);
11591
- const path = `/api/v2/agents/${enc7(id)}/whatsapp`;
11639
+ const path = `/api/v2/agents/${enc8(id)}/whatsapp`;
11592
11640
  const listing = !sub || sub === "ls" || sub === "list";
11593
11641
  const cur = await api(listing ? `${path}?formmy=1` : path);
11594
11642
  if (listing) {
@@ -11632,7 +11680,7 @@ async function groupsCmd(args, v) {
11632
11680
  if (rawSub === "disable") v = { ...v, off: true };
11633
11681
  const ex = 'ghosty channels wa groups ls <agent-id> | set <agent-id> <group> [--role equipo|cliente] [--wake mention|all] [--on|--off] [--name "New name"] | create <agent-id> "Name" | inject <agent-id> <group> --text "\u2026" [--sender NAME]';
11634
11682
  if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), `${t("Example", "Ejemplo")}: ${ex}`);
11635
- const path = `/api/v2/agents/${enc7(id)}/wa/groups`;
11683
+ const path = `/api/v2/agents/${enc8(id)}/wa/groups`;
11636
11684
  if (v.role !== void 0 && v.role !== "equipo" && v.role !== "cliente") throw usage(t("--role takes equipo or cliente.", "--role acepta equipo o cliente."));
11637
11685
  if (v.wake !== void 0 && v.wake !== "mention" && v.wake !== "all") throw usage(t("--wake takes mention or all.", "--wake acepta mention o all."));
11638
11686
  if (!sub || sub === "ls" || sub === "list") {
@@ -11652,7 +11700,7 @@ async function groupsCmd(args, v) {
11652
11700
  const text = v.text?.trim();
11653
11701
  if (!ref2 || !text) throw usage(t("Missing group or --text.", "Falta el grupo o --text."), t("Example", "Ejemplo") + `: ghosty channels wa groups inject ${id} "Equipo" --text "lo que dec\xEDa el audio" --sender Brenda`);
11654
11702
  const g2 = findGroup((await api(`${path}?live=0`)).groups, ref2);
11655
- const r2 = await api(`/api/v2/agents/${enc7(id)}/wa/inject`, { method: "POST", body: { jid: g2.jid, text, ...v.sender ? { senderName: v.sender } : {} } });
11703
+ const r2 = await api(`/api/v2/agents/${enc8(id)}/wa/inject`, { method: "POST", body: { jid: g2.jid, text, ...v.sender ? { senderName: v.sender } : {} } });
11656
11704
  return out(r2, () => `${lavender("\u2713")} ${t(`Injected into ${g2.name}: the agent answers there.`, `Reinyectado en ${g2.name}: el agente contesta ah\xED.`)}`);
11657
11705
  }
11658
11706
  if (sub !== "set") throw usage(t(`Unknown groups subcommand: ${sub}.`, `Subcomando de groups desconocido: ${sub}.`), ex);
@@ -11676,7 +11724,7 @@ ${t(`The agent will start answering in this group as ${role}.`, `El agente empez
11676
11724
  const now = r.groups.find((x) => x.jid === g.jid);
11677
11725
  return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${now?.name ?? g.name}: ${now?.enabled ? "on" : "off"} ${t("as", "como")} ${now?.role ?? role} \xB7 wake ${now?.wake ?? wake ?? "all"}`);
11678
11726
  }
11679
- var EB_EXPORT = (agentId) => `${(process.env.EASYBITS_URL ?? "https://www.easybits.cloud").replace(/\/+$/, "")}/api/v2/fleet-agents/${enc7(agentId)}/baileys/export`;
11727
+ var EB_EXPORT = (agentId) => `${(process.env.EASYBITS_URL ?? "https://www.easybits.cloud").replace(/\/+$/, "")}/api/v2/fleet-agents/${enc8(agentId)}/baileys/export`;
11680
11728
  async function easybitsSession(agentId, keys) {
11681
11729
  const key = process.env.EASYBITS_API_KEY;
11682
11730
  if (!key) throw usage(t("EASYBITS_API_KEY is not set.", "EASYBITS_API_KEY no est\xE1 definida."), t("Export the EasyBits API key of the agent's owner and run again.", "Exporta la API key de EasyBits del due\xF1o del agente y vuelve a correrlo."));
@@ -11703,7 +11751,7 @@ function originText(o) {
11703
11751
  }
11704
11752
  async function waCmd2(sub, id, v) {
11705
11753
  if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + `: ghosty channels wa ${sub} <agent-id>`);
11706
- const base = `/api/v2/agents/${enc7(id)}/wa`;
11754
+ const base = `/api/v2/agents/${enc8(id)}/wa`;
11707
11755
  if (sub === "status") {
11708
11756
  const s2 = await api(base);
11709
11757
  return out(s2, () => `${s2.status === "connected" ? lavender(t("\u25CF connected", "\u25CF conectado")) : dim(`\u25CB ${s2.status}`)} ${s2.phone ? `+${s2.phone}` : dim(t("(no number)", "(sin n\xFAmero)"))}${s2.live ? "" : dim(t(" \xB7 no socket in gs", " \xB7 sin socket en gs"))}${s2.hasCreds ? "" : dim(t(" \xB7 no saved session", " \xB7 sin sesi\xF3n guardada"))}${s2.reason ? `
@@ -11767,13 +11815,13 @@ async function linkCmd(sub, id, v) {
11767
11815
  const board = v.board === void 0 ? void 0 : /^(agent|agente)$/i.test(v.board) ? null : v.board;
11768
11816
  const boardFlag = v.board !== void 0 ? ` --board ${JSON.stringify(v.board)}` : "";
11769
11817
  if (sub === "link" && (adsOnly !== void 0 || board !== void 0) && v.answer === void 0) {
11770
- const path2 = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
11818
+ const path2 = `/api/v2/agents/${enc8(id)}/whatsapp/link`;
11771
11819
  const r2 = await api(path2, { method: "POST", body: { integrationId: v.integration, ...adsOnly !== void 0 ? { adsOnly } : {}, ...board !== void 0 ? { board } : {}, ...v["dry-run"] ? { dryRun: true } : {} } });
11772
11820
  return out(r2, () => `${r2.plan.map((x) => serverText(x)).join("\n")}${v["dry-run"] ? `
11773
11821
  ${dim(applyHint(`ghosty channels whatsapp link ${id} --integration ${v.integration}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}${boardFlag}`))}` : ""}`);
11774
11822
  }
11775
11823
  if (sub === "link" && v.answer !== "on" && v.answer !== "off") throw usage(t("Missing --answer on|off.", "Falta --answer on|off."), t("on = this agent answers real customers \xB7 off = it only records (add --forward so gs still sees the conversations)", "on = este agente contesta a clientes reales \xB7 off = s\xF3lo registra (agrega --forward para que gs siga viendo las conversaciones)"));
11776
- const path = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
11824
+ const path = `/api/v2/agents/${enc8(id)}/whatsapp/link`;
11777
11825
  if (v["partner-agent"] && !v["partner-tenant"]) throw usage(t("--partner-agent needs --partner-tenant <externalId>.", "--partner-agent necesita --partner-tenant <externalId>."));
11778
11826
  const partner = v["partner-tenant"] ? { partnerExternalId: v["partner-tenant"], ...v["partner-agent"] ? { partnerAgentSlug: v["partner-agent"] } : {} } : {};
11779
11827
  const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {}, ...adsOnly !== void 0 ? { adsOnly } : {}, ...board !== void 0 ? { board } : {}, ...partner } };
@@ -11820,7 +11868,7 @@ async function pageWithRetry(fetchPage, where, waitMs = (n) => 5e3 * n, tries =
11820
11868
  }
11821
11869
  var lastCell = (l) => l ? `${l.agentName ?? l.agentId} ${dim(l.at.slice(0, 16).replace("T", " "))}` : dim("\u2014");
11822
11870
  async function linksCmd(id, kind, orphans) {
11823
- const r = await api(`/api/v2/agents/${enc7(id)}/channels/links?kind=${kind}${orphans ? "&orphans=1" : ""}`);
11871
+ const r = await api(`/api/v2/agents/${enc8(id)}/channels/links?kind=${kind}${orphans ? "&orphans=1" : ""}`);
11824
11872
  const now = (l) => l.answersNow ? l.answersNow.agentName ?? l.answersNow.agentId : l.recordsOnly ? t("nobody (records only)", "nadie (s\xF3lo registra)") : l.via === "easybits" ? "EasyBits" : l.via === "partner" ? "partner" : lavender(t("nobody", "nadie"));
11825
11873
  const rows = r.links.map((l) => ({ [kind === "waba" ? "number" : "page"]: l.label, "answers now": now(l), "last answered by": lastCell(l.lastAnswer), turns: l.lastAnswer?.turns ?? 0, id: l.id }));
11826
11874
  const first = r.links.find((l) => l.nobody && l.lastAnswer);
@@ -11832,7 +11880,7 @@ ${dim(`${t("Give it back", "Regresarlo")}: ${kind === "waba" ? `ghosty channels
11832
11880
  );
11833
11881
  }
11834
11882
  async function integrationsCmd(id, email) {
11835
- const r = await api(`/api/v2/agents/${enc7(id)}/whatsapp/integrations${email ? `?email=${enc7(email)}` : ""}`);
11883
+ const r = await api(`/api/v2/agents/${enc8(id)}/whatsapp/integrations${email ? `?email=${enc8(email)}` : ""}`);
11836
11884
  const who2 = { gs: "gs", easybits: "EasyBits", droplet: "droplet", formmy: "Formmy", other: t("other", "otro") };
11837
11885
  const rows = r.integrations.map((i) => ({
11838
11886
  number: i.phoneNumber ? `+${i.phoneNumber}` : dim("?"),
@@ -11853,7 +11901,7 @@ async function historyCmd(id, v) {
11853
11901
  if (!v.integration) throw usage(t("Missing --integration.", "Falta --integration."), t("Example", "Ejemplo") + `: ghosty channels whatsapp history ${id} --integration <formmy-integration-id> --days 7 --dry-run`);
11854
11902
  const days = Number(v.days ?? 7);
11855
11903
  if (!Number.isInteger(days) || days < 1 || days > 30) throw usage(t("--days takes 1 to 30.", "--days acepta de 1 a 30."));
11856
- const path = `/api/v2/agents/${enc7(id)}/whatsapp/history`;
11904
+ const path = `/api/v2/agents/${enc8(id)}/whatsapp/history`;
11857
11905
  const media = v.media !== void 0;
11858
11906
  const filters = { ...v["only-ads"] ? { onlyAds: true } : {}, ...v.phones ? { phones: v.phones.split(",").map((x) => x.trim()).filter(Boolean) } : {}, ...v.column ? { column: v.column } : {} };
11859
11907
  const cmd = `ghosty channels whatsapp history ${id} --integration ${v.integration} --days ${days}${v["only-ads"] ? " --only-ads" : ""}${v.phones ? ` --phones ${v.phones}` : ""}${v.column ? ` --column "${v.column}"` : ""}`;
@@ -12061,176 +12109,576 @@ ${t(`Archive ${plan.matched} card(s) on \xAB${plan.board}\xBB? (reversible: a ne
12061
12109
  out(r, () => `${lavender("\u2713")} ${t(`Archived ${r.archived} card(s) on \xAB${r.board}\xBB.`, `Se archivaron ${r.archived} tarjeta(s) en \xAB${r.board}\xBB.`)}`);
12062
12110
  }
12063
12111
 
12064
- // src/commands/boards.ts
12065
- var enc8 = encodeURIComponent;
12066
- async function boardsCmd(sub, a1, a2, v, context) {
12067
- if (context && sub !== "assign") {
12068
- if ((sub === "ls" || sub === "list" || !sub || sub === "move-cards") && !a1) a1 = context;
12069
- else if ((sub === "create" || sub === "rm") && a1 && !a2) [a1, a2] = [context, a1];
12112
+ // src/commands/spaces.ts
12113
+ var enc9 = encodeURIComponent;
12114
+ var wsPath = (slug, rest = "") => `/api/v2/workspaces/${enc9(slug)}${rest}`;
12115
+ var needSpace = (slug, example) => {
12116
+ if (!slug) throw usage(t("Which workspace?", "\xBFQu\xE9 espacio?"), `${t("Example", "Ejemplo")}: ${example} ${t("(or ghosty use <workspace>)", "(o ghosty use <espacio>)")}`);
12117
+ return slug;
12118
+ };
12119
+ function parseTokens(raw) {
12120
+ const m = /^\s*(\d+(?:\.\d+)?)\s*([kKmM]?)\s*$/.exec(raw);
12121
+ if (!m) throw usage(t(`Bad token amount: ${raw}.`, `Cantidad de tokens inv\xE1lida: ${raw}.`), t("Use 20M, 500k or a plain number.", "Usa 20M, 500k o un n\xFAmero."));
12122
+ const n = Number(m[1]) * (m[2].toLowerCase() === "m" ? 1e6 : m[2].toLowerCase() === "k" ? 1e3 : 1);
12123
+ return Math.round(n);
12124
+ }
12125
+ function fmtTokens(n) {
12126
+ if (n >= 1e6) return `${+(n / 1e6).toFixed(n % 1e6 === 0 ? 0 : 1)}M`;
12127
+ if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
12128
+ return String(n);
12129
+ }
12130
+ function parseSwitch(flag, raw) {
12131
+ if (raw === void 0) return void 0;
12132
+ if (/^(on|true|yes|s[ií])$/i.test(raw)) return true;
12133
+ if (/^(off|false|no)$/i.test(raw)) return false;
12134
+ throw usage(t(`${flag} takes on or off.`, `${flag} acepta on u off.`));
12135
+ }
12136
+ async function enginesCmd(sub, v) {
12137
+ if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown engines subcommand: ${sub}.`, `Subcomando de engines desconocido: ${sub}.`), "ghosty engines ls [--models]");
12138
+ const r = await api("/api/v2/engines");
12139
+ out(r, () => {
12140
+ if (!v.models) {
12141
+ const rows = r.engines.map((e) => ({ engine: e.id, provider: e.provider, default: e.defaultModel ?? "", models: e.models.map((m) => m.id).join(", ") }));
12142
+ return `${table(rows, ["engine", "provider", "default"])}
12143
+ ${dim(t("Models per engine: ghosty engines ls --models", "Modelos de cada motor: ghosty engines ls --models"))}`;
12144
+ }
12145
+ return r.engines.map((e) => {
12146
+ const rows = e.models.map((m) => ({ model: m.id + (m.id === e.defaultModel ? " *" : ""), label: m.label, cost: `${m.multiplier}\xD7`, note: m.ownKeyOnly ? t("your own key only", "s\xF3lo con llave propia") : "" }));
12147
+ return `${bold(e.id)} ${dim(`${e.provider} \xB7 ${e.protocol}${e.vision ? t(" \xB7 sees images", " \xB7 ve im\xE1genes") : ""}`)}
12148
+ ${table(rows, ["model", "label", "cost", "note"])}`;
12149
+ }).join("\n\n") + `
12150
+ ${dim(t("* default \xB7 cost = balance multiplier per turn", "* por default \xB7 costo = multiplicador de saldo por turno"))}`;
12151
+ });
12152
+ }
12153
+ function bagsText(r) {
12154
+ const rows = r.bags.map((b) => ({
12155
+ bag: b.name,
12156
+ tokens: fmtTokens(b.tokens),
12157
+ used: `${fmtTokens(b.used)} (${Math.round(b.ratio * 100)}%)`,
12158
+ kind: b.recurring ? t("monthly", "mensual") : t(`one-time since ${b.since?.slice(0, 10)}`, `carga \xFAnica desde ${b.since?.slice(0, 10)}`),
12159
+ cap: b.hardCap ? t("cuts", "corta") : t("warns", "avisa"),
12160
+ agents: b.agents.map((a) => a.name ?? a.id).join(", ") || "\u2014"
12161
+ }));
12162
+ const free = r.unbagged.length ? `
12163
+ ${dim(t(`Workspace balance (no bag): ${r.unbagged.map((a) => a.name ?? a.id).join(", ")}`, `Saldo del espacio (sin bolsa): ${r.unbagged.map((a) => a.name ?? a.id).join(", ")}`))}` : "";
12164
+ return `${bold(r.workspace)}
12165
+ ${r.bags.length ? table(rows, ["bag", "tokens", "used", "kind", "cap", "agents"]) : dim(t("No bags: every agent uses the workspace balance.", "Sin bolsas: todos los agentes usan el saldo del espacio."))}${free}`;
12166
+ }
12167
+ async function bagsCmd(sub, slug, name, v, context) {
12168
+ const ex = 'ghosty bags ls <workspace> \xB7 create <workspace> "<bag>" --tokens 20M [--recurring off] \xB7 set <workspace> "<bag>" --tokens 30M \xB7 rm <workspace> "<bag>"';
12169
+ if (sub && !["ls", "list", "create", "new", "set", "rm", "delete"].includes(sub)) {
12170
+ name = slug;
12171
+ slug = sub;
12172
+ sub = "ls";
12070
12173
  }
12174
+ slug = needSpace(slug ?? context, ex);
12175
+ const recurring = parseSwitch("--recurring", v.recurring);
12176
+ const hardCap = parseSwitch("--hard-cap", v["hard-cap"]);
12071
12177
  switch (sub ?? "ls") {
12072
12178
  case "ls":
12073
12179
  case "list": {
12074
- if (!a1) throw usage(t("Missing workspace.", "Falta el espacio."), t("Example", "Ejemplo") + ": ghosty boards ls <workspace-slug>");
12075
- const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`);
12076
- const rows = r.boards.map((b) => ({ name: b.main ? `${b.name} (${t("main", "principal")})` : b.name, cards: b.cards, agents: b.agents.map((x) => x.name ?? x.id).join(", "), columns: b.columns.length, id: b.id }));
12077
- return out(r, () => table(rows, ["name", "cards", "agents", "columns", "id"]));
12078
- }
12079
- case "create": {
12080
- if (!a1 || !a2) throw usage(t("Missing workspace or name.", "Falta el espacio o el nombre."), t("Example", "Ejemplo") + ': ghosty boards create <workspace> "Siiqtec - Ixmiquilpan" --columns-from main');
12081
- const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`, { method: "POST", body: { name: a2, ...v["columns-from"] ? { columnsFrom: v["columns-from"] } : {} } });
12082
- return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.name}\xBB created${r.columnsFrom ? ` with the ${r.columns} column(s) of \xAB${r.columnsFrom}\xBB` : ""}.`, `Tablero \xAB${r.name}\xBB creado${r.columnsFrom ? ` con las ${r.columns} columna(s) de \xAB${r.columnsFrom}\xBB` : ""}.`)} ${dim(`${t("Send an agent's cards there", "Manda ah\xED las tarjetas de un agente")}: ghosty boards assign <agent-id> "${r.name}"`)}${r.warning ? `
12083
- ${lavender(`\u26A0\uFE0F ${serverText(r.warning)}`)}` : ""}`);
12180
+ const r = await api(wsPath(slug, "/bags"));
12181
+ return out(r, () => bagsText(r));
12084
12182
  }
12085
- case "rm": {
12086
- if (!a1 || !a2) throw usage(t("Missing workspace or board.", "Falta el espacio o el tablero."), t("Example", "Ejemplo") + ': ghosty boards rm <workspace> "Prueba"');
12087
- requireYesIfHeadless(v.yes, `ghosty boards rm ${a1} ${JSON.stringify(a2)}`);
12088
- await confirm(t(`Delete the board \xAB${a2}\xBB of ${a1}? (only an empty one; its agents go back to the main board)`, `\xBFBorrar el tablero \xAB${a2}\xBB de ${a1}? (s\xF3lo si est\xE1 vac\xEDo; sus agentes regresan al tablero principal)`), { yes: v.yes, retry: `ghosty boards rm ${a1} ${JSON.stringify(a2)}` });
12089
- const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`, { method: "DELETE", body: { board: a2 } });
12090
- return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.deleted}\xBB deleted.`, `Tablero \xAB${r.deleted}\xBB borrado.`)}`);
12183
+ case "create":
12184
+ case "new": {
12185
+ if (!name) throw usage(t("Missing the bag's name.", "Falta el nombre de la bolsa."), ex);
12186
+ if (!v.tokens) throw usage(t("Missing --tokens.", "Falta --tokens."), `ghosty bags create ${slug} "${name}" --tokens 20M`);
12187
+ const r = await api(wsPath(slug, "/bags"), { method: "POST", body: { name, tokens: parseTokens(v.tokens), ...recurring !== void 0 ? { recurring } : {}, ...hardCap !== void 0 ? { hardCap } : {}, ...v.note ? { note: v.note } : {} } });
12188
+ return out(r, () => `${lavender("\u2713")} ${t(`Bag \xAB${name}\xBB created.`, `Bolsa \xAB${name}\xBB creada.`)} ${dim(t(`Give it agents: ghosty agents set <agent> --bag "${name}"`, `Cu\xE9lgale agentes: ghosty agents set <agente> --bag "${name}"`))}
12189
+ ${bagsText(r)}`);
12091
12190
  }
12092
- case "assign": {
12093
- if (!a1 || !a2) throw usage(t("Missing agent or board.", "Falta el agente o el tablero."), t("Example", "Ejemplo") + ': ghosty boards assign <agent-id> "Siiqtec - Ixmiquilpan" (' + t("or main", "o main") + ")");
12094
- const r = await api(`/api/v2/agents/${enc8(a1)}`, { method: "PATCH", body: { board: a2 } });
12095
- return out(r, () => `${lavender("\u2713")} ${t(`New cards of ${a1} go to \xAB${r.board}\xBB.`, `Las tarjetas nuevas de ${a1} van a \xAB${r.board}\xBB.`)} ${dim(`${t("Existing ones stay where they are", "Las que ya existen se quedan donde est\xE1n")}: ghosty boards move-cards ${r.workspace} --from \u2026 --to "${r.board}" \u2026`)}`);
12191
+ case "set": {
12192
+ if (!name) throw usage(t("Missing the bag's name.", "Falta el nombre de la bolsa."), ex);
12193
+ const body = { ...v.tokens ? { tokens: parseTokens(v.tokens) } : {}, ...recurring !== void 0 ? { recurring } : {}, ...hardCap !== void 0 ? { hardCap } : {}, ...v.note !== void 0 ? { note: v.note } : {}, ...v.name ? { name: v.name } : {} };
12194
+ if (!Object.keys(body).length) throw usage(t("Nothing to change.", "Nada que cambiar."), "--tokens 30M | --recurring on|off | --hard-cap on|off | --note TEXT | --name NEW");
12195
+ const r = await api(wsPath(slug, `/bags/${enc9(name)}`), { method: "PATCH", body });
12196
+ return out(r, () => `${lavender("\u2713")} ${t(`Bag \xAB${r.updated}\xBB updated.`, `Bolsa \xAB${r.updated}\xBB actualizada.`)}
12197
+ ${bagsText(r)}`);
12096
12198
  }
12097
- case "move-cards": {
12098
- if (!a1 || !v.from || !v.to) throw usage(t("Missing workspace, --from or --to.", "Falta el espacio, --from o --to."), t("Example", "Ejemplo") + ': ghosty boards move-cards <workspace> --from main --to "Siiqtec - Ixmiquilpan" --integration 6a3b02ad\u2026 --dry-run');
12099
- if (!v.integration && !v.phone && !v.all) throw usage(t("Add a filter: --integration ID, --phone N or --all.", "Agrega un filtro: --integration ID, --phone N o --all."), t("A filter-less move would empty the board.", "Mover sin filtro vaciar\xEDa el tablero."));
12100
- const path = `/api/v2/workspaces/${enc8(a1)}/boards/move-cards`;
12101
- const body = { from: v.from, to: v.to, ...v.integration ? { integration: v.integration } : {}, ...v.phone ? { phone: v.phone } : {}, ...v.all ? { all: true } : {} };
12102
- const retry = `ghosty boards move-cards ${a1} --from ${JSON.stringify(v.from)} --to ${JSON.stringify(v.to)}${v.integration ? ` --integration ${v.integration}` : ""}${v.phone ? ` --phone ${v.phone}` : ""}${v.all ? " --all" : ""}`;
12103
- if (!v["dry-run"]) requireYesIfHeadless(v.yes, retry);
12104
- const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
12105
- const sample = table(plan.sample.map((c) => ({ folio: c.folio ? `#${c.folio}` : "", name: c.name ?? "", phone: c.phone })), ["folio", "name", "phone"]);
12106
- const text = `${sample}
12107
- ${t(`${plan.moved} of ${plan.matched} card(s)`, `${plan.moved} de ${plan.matched} tarjeta(s)`)} \xAB${plan.from}\xBB \u2192 \xAB${plan.to}\xBB${plan.skipped.length ? dim(t(` \xB7 ${plan.skipped.length} already in \xAB${plan.to}\xBB, skipped`, ` \xB7 ${plan.skipped.length} ya en \xAB${plan.to}\xBB, se omiten`)) : ""}`;
12108
- if (v["dry-run"] || !plan.moved) return out(plan, () => `${text}
12109
- ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
12110
- await confirm(`${text}
12111
- ${t("Move them with their threads and orders? (they get a new folio there)", "\xBFMoverlas con sus hilos y pedidos? (all\xE1 reciben folio nuevo)")}`, { yes: v.yes, retry });
12112
- const r = await api(path, { method: "POST", body });
12113
- return out(r, () => `${lavender("\u2713")} ${t(`Moved ${r.moved} card(s) (${r.messages} message(s), ${r.orders} order(s)) to \xAB${r.to}\xBB.`, `Se movieron ${r.moved} tarjeta(s) (${r.messages} mensaje(s), ${r.orders} pedido(s)) a \xAB${r.to}\xBB.`)}${r.skipped.length ? dim(t(` Skipped ${r.skipped.length}.`, ` Se omitieron ${r.skipped.length}.`)) : ""}`);
12199
+ case "rm":
12200
+ case "delete": {
12201
+ if (!name) throw usage(t("Missing the bag's name.", "Falta el nombre de la bolsa."), ex);
12202
+ requireYesIfHeadless(v.yes, `ghosty bags rm ${slug} "${name}"`);
12203
+ await confirm(t(`Delete bag \xAB${name}\xBB from ${slug}? (only if no agent uses it)`, `\xBFBorrar la bolsa \xAB${name}\xBB de ${slug}? (s\xF3lo si ning\xFAn agente la usa)`), { yes: v.yes, retry: `ghosty bags rm ${slug} "${name}"` });
12204
+ const r = await api(wsPath(slug, `/bags/${enc9(name)}`), { method: "DELETE" });
12205
+ return out(r, () => `${lavender("\u2713")} ${t(`Bag \xAB${r.deleted}\xBB deleted.`, `Bolsa \xAB${r.deleted}\xBB borrada.`)}
12206
+ ${bagsText(r)}`);
12114
12207
  }
12115
- default:
12116
- throw usage(t(`Unknown boards subcommand: ${sub}.`, `Subcomando de boards desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty boards ls | create | assign | move-cards | rm");
12117
12208
  }
12118
12209
  }
12119
-
12120
- // src/help.es.ts
12121
- var HELPS_ES = {
12122
- agents: {
12123
- summary: "Crea, revisa, cambia, diagnostica y borra agentes.",
12124
- replace: [
12125
- ["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
12126
- ["# its look", "# su apariencia"],
12127
- ["# its theme", "# su tema"],
12128
- ["# export the theme as JSON (edit it, apply it back with --style-file)", "# exporta el tema en JSON (ed\xEDtalo y vuelve a aplicarlo con --style-file)"],
12129
- ["--style-file theme.json", "--style-file tema.json"],
12130
- ["# what it says while it works", "# lo que dice mientras trabaja"],
12131
- ["--copy-file texts.json", "--copy-file textos.json"],
12132
- ["# who is @build in Teams (that workspace, or all of yours)", "# qui\xE9n es @build en Teams (en ese espacio, o en todos los tuyos)"],
12133
- ["# change one piece of the prompt, on the server's current one", "# cambia un pedazo del prompt, sobre el actual del servidor"],
12134
- ["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
12135
- ["# staff: their agents, latest turns, plan and boxes", "# staff: sus agentes, \xFAltimos turnos, plan y cajas"],
12136
- ["# what it really has: effective toolsets, connectors connected + granted", "# lo que de verdad tiene: toolsets efectivos, conectores conectados + concedidos"],
12137
- ["# Teams workspaces you administer: where it answers, is paused, and with which @handle", "# los equipos de Teams que administras: d\xF3nde contesta, d\xF3nde est\xE1 en pausa y con qu\xE9 @handle"],
12138
- ["# turn it on there (or change its @handle)", "# lo prende ah\xED (o le cambia el @handle)"],
12139
- ["# pause it there; it keeps its @handle", "# lo pausa ah\xED; conserva su @handle"],
12140
- ["# archive: stops answering everywhere, hidden, deleted after 7 days unless restored", "# archivar: deja de contestar en todos lados, se oculta y se borra a los 7 d\xEDas si no lo restauras"],
12141
- ["# archived agents and when they get deleted", "# los archivados y cu\xE1ndo se borran"],
12142
- ["# --json: model and workspace on every row", "# --json: modelo y espacio en cada fila"],
12143
- ["# also: model, effort, native subagents, answers /c, box size (staff)", "# tambi\xE9n: modelo, effort, subagentes nativos, si atiende /c, tama\xF1o de caja (staff)"],
12144
- ["# remove it from there; the agent stays", "# lo quita de ah\xED; el agente sigue"],
12145
- ["# web widget: chat bubble on your site, only on those domains", "# widget web: burbuja de chat en tu sitio, s\xF3lo en esos dominios"],
12146
- ["# state + the <script> to paste", "# su estado + el <script> para pegar"]
12147
- ],
12148
- flags: [
12149
- ["--fields a,b", "get: regresa s\xF3lo estos campos"],
12150
- ["--prompt-out FILE", "get: escribe el prompt en FILE (ed\xEDtalo y vuelve con set --prompt-file)"],
12151
- ["--like AGENT", "create: copia la configuraci\xF3n de ese agente (skills y sus switches, toolsets, conectores, voz, bases de datos, documentos, tablero, compartidos) \u2014 no su prompt, env ni canales. docs grant: los mismos documentos"],
12152
- ["--copy-dbs", "create --like: duplica sus bases de datos con sus filas en vez de conceder las mismas"],
12153
- ["--board B", "create --like: same (el tablero del origen, default) \xB7 main \xB7 new (un tablero con el nombre del agente y las columnas del origen: un tablero por n\xFAmero) \xB7 o un tablero existente"],
12154
- ["--no-notify", "create --like: lo comparte con las mismas personas SIN mandarles correo"],
12155
- ["--workspace SLUG", "create: nace en ese espacio (del que eres due\xF1o o admin), a nombre de su due\xF1o y activo en su Teams"],
12156
- ["--channels k=on|off,\u2026", "create/set: prende/apaga canales: teams, web, whatsapp, messenger, wa (WhatsApp personal)"],
12157
- ["--engine E --dry-run", "set: qu\xE9 hace cambiar de motor (sesiones que se olvidan, caja que se levanta y cu\xE1l se destruye, si est\xE1 trabajando) sin cambiarlo; cada hilo conserva su conversaci\xF3n"],
12158
- ["--toolsets LIST", "set: grupos de tools de gs que recibe (crm, sales\u2026): una lista reemplaza; +x / -x agrega o quita"],
12159
- ["--prompt-base ID", "set: el prompt de este agente = el prompt de ese agente + --prompt-overlay; se regenera cuando cambia la base. none = desliga (conserva el prompt de hoy)"],
12160
- ["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, aplicados en orden; cada uno debe encontrar su lugar o no cambia nada. {"edits":[]} = espejo exacto: un solo prompt y una sola memoria para los dos'],
12161
- ["--prompt-patch FILE", 'set: el mismo formato {"edits":[\u2026]}, aplicado UNA vez sobre el prompt actual del agente en el servidor (no sobre una copia local: nunca pisa el cambio de alguien m\xE1s). Si una edici\xF3n no encuentra su lugar, no cambia nada. --dry-run imprime el diff'],
12162
- ["--prompt-append FILE", "set: agrega ese texto al final del prompt (con --prompt-patch tambi\xE9n: primero las ediciones, luego el texto)"],
12163
- ["--voice E:ID", "set: voz de las notas de voz: elevenlabs:<voiceId> o kokoro:em_santa|em_alex|ef_dora"],
12164
- ["--voice-replies M", "set: auto = contesta con nota de voz cuando el cliente manda o pide audio; off = siempre texto"],
12165
- ["--auto-stage on|off", "set: mueve solo las tarjetas del tablero tras cada turno del cliente (una llamada extra al modelo por turno)"],
12166
- ["--quote k=v,\u2026", "set: config de la tool de casa `cotizar` \u2014 iva (0-100), iva-incluido=true (los precios del cat\xE1logo ya traen IVA), iva.CATEGORIA=16 (IVA de esa categor\xEDa del cat\xE1logo; iva.CATEGORIA= la quita), solo-factura=true (IVA s\xF3lo si el cliente pide factura), vigencia (d\xEDas), prefix (folio), moneda, empresa, rfc, telefono, correo, direccion, notas, color. Vac\xEDo lo borra (rfc=). El contador de folio no se toca"],
12167
- ["--catalog FILE", "set: el cat\xE1logo de la tool de casa `cotizar` desde un CSV (sku,nombre,precio[,unidad,categoria,descripcion]) o una lista JSON; reemplaza al anterior. Su `categoria` decide las reglas de IVA"],
12168
- ["--verbose", "doctor: lista tambi\xE9n las revisiones que pasaron"],
12169
- ["--user EMAIL", "doctor (staff de Ghosty): los agentes de esa persona \u2014 doctor, \xFAltimos turnos (estado, segundos, error y \xABfetched\xBB: si su app pidi\xF3 el hilo despu\xE9s de que termin\xF3 el turno), plan/bolsa y cajas (marca duplicadas); queda en bit\xE1cora"],
12170
- ["--turns N", "doctor --user: cu\xE1ntos turnos recientes por agente (default 8, m\xE1x. 50)"],
12171
- ["--write", "docs grant: el agente puede reemplazar el documento (default: s\xF3lo lectura)"],
12172
- ["--no-deliver", "docs grant/add: puede leerlo pero nunca mand\xE1rselo a quien escribe"],
12173
- ["--url URL | --file PATH", "docs add: de d\xF3nde sale el documento (URL p\xFAblica, hasta 25 MB, o un archivo local)"],
12174
- ["--grant read|write|no", "docs add: acceso de este agente (default read; no = s\xF3lo subirlo a la cuenta)"],
12175
- ["--note TEXT", "docs replace/restore: por qu\xE9 cambi\xF3 (sale en docs history)"],
12176
- ["--role R", "share: editor (lo configura y lo usa) o admin (adem\xE1s lo comparte); repetirlo sobre un correo ya compartido cambia el rol"],
12177
- ["--handle NAME", "teams on: su @handle en ese equipo (default: sacado de su nombre, con sufijo si est\xE1 ocupado; @ghosty est\xE1 reservado). Sobre un equipo donde ya est\xE1, le cambia el handle. ls: qui\xE9n contesta a ese @handle"],
12178
- ["--factory ROLES", "teams on: le pasa esos roles de la Software Factory del equipo (plan, build, check, eval; separados por comas) \u2014 lo mismo que \xABEquipo\xBB en /factory. Cada rol conserva su nombre y avatar; su siguiente turno ya corre en \xE9l. La f\xE1brica tiene que estar instalada ah\xED"],
12179
- ["--yes", "rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
12180
- ["--slug NAME", "demo: nombre de la liga; se le agrega un c\xF3digo de 6 caracteres (/p/NAME-CODE)"],
12181
- ["--expires DATE|no", 'demo: caducidad fija en la hora de tu m\xE1quina ("2026-10-15T20:00"); no = nunca'],
12182
- ["--turns N", "demo: mensajes por visitante (default 150)"],
12183
- ["--per-ip N", "demo: mensajes por IP al d\xEDa (default 30)"],
12184
- ["--welcome TEXT", "demo: saludo del chat (vac\xEDo = el de siempre)"],
12185
- ["--starter TEXT", 'demo: primera sugerencia destacada ("" la quita)'],
12186
- ['--chips "a|b|c"', "demo: sugerencias, separadas por |"],
12187
- ["--sponsor on|off", "demo: las copias que la gente se queda corren con TU llave del motor"],
12188
- ["--rotate", "demo: c\xF3digo nuevo; la liga vieja deja de servir"],
12189
- ["--domains a.com,*.b.com", "embed: d\xF3nde puede cargarse el widget (vac\xEDo = en ning\xFAn lado)"],
12190
- ["--welcome TEXT", "embed: el primer mensaje que ve el visitante (vac\xEDo = \xAB\xA1Hola! \xBFEn qu\xE9 te ayudo?\xBB)"],
12191
- ["--title T / --subtitle T", "embed: nombre en el encabezado del widget (default: el del agente) y un rengl\xF3n chico debajo (\xABResponde en minutos\xBB)"],
12192
- ["--avatar URL|none", "embed: tu imagen (https JPEG/PNG/WebP, se rehospeda en img.ghosty.studio); none = el fantasma del agente"],
12193
- ["--icon chat|avatar|ghost", "embed: qu\xE9 lleva el bot\xF3n flotante"],
12194
- ["--placeholder T", "embed: el texto gris de la caja para escribir"],
12195
- ["--position right|left / --offset X[,Y]", "embed: esquina de la p\xE1gina y separaci\xF3n en px del lado y de abajo (0-200, default 20)"],
12196
- ["--theme light|dark|auto", "embed: tema del panel del chat (auto = el del visitante)"],
12197
- ["--bubble-text T", "embed: etiqueta junto al bot\xF3n (\xAB\xBFDudas?\xBB)"],
12198
- ["--auto-open S|off", "embed: se abre solo a los S segundos (1-120), una vez por visita, nunca en el tel\xE9fono"],
12199
- ['--starters "a|b|c"', 'embed: hasta 4 sugerencias TUYAS ("" las quita; nunca salen las de Ghosty)'],
12200
- ["--style '{json}'|none", "embed: estilo fino, s\xF3lo llaves conocidas: fontFamily, radius, headerBg, headerText, titleUppercase, statusDot, headerAvatar, panelBg, userBubbleBg/Text, agentBubbleBg/Text, inputBg, sendBg, sendIcon, sendArrow (up|right), launcherBg, launcherIcon, typing (bounce|flow|flashing|shimmer), typingColor. PATCH por llave (null resetea una); none resetea todo"],
12201
- ["--font NAME / --radius 0-24", "embed: atajos de style.fontFamily (lista de Google Fonts: Barlow, Inter, Poppins\u2026) y style.radius"],
12202
- ["--typing STYLE / --typing-color #rrggbb|none", "embed: el indicador de \xABescribiendo\xBB mientras responde: bounce (el de siempre), flow, flashing o shimmer (\xABPensando\u2026\xBB con brillo), y su color (por defecto, el de la marca)"],
12203
- ["--theme-source custom|teams|site", "embed: importa el tema de la marca de tu espacio de Ghosty Teams (--workspace SLUG para elegir) o de tu sitio (--site-url); queda editable"],
12204
- ["--site-url URL", "embed: el sitio del que se sacan los colores (implica --theme-source site)"],
12205
- ["--style-file FILE", "embed: aplica un tema exportado con `agents embed <id> theme` (o s\xF3lo el objeto style)"],
12206
- ["--copy '{json}'|none", "embed: las frases mientras el agente trabaja, texto plano \u2264 80 car.: waking, connecting, thinking (lista de 1 a 8 que rotan), stillWorking, cantStart, interrupted, retryLabel, showDuration, showCopyButton (true|false). PATCH por llave (null = la frase de Ghosty); none las regresa todas"],
12207
- ["--copy-file FILE", "embed: aplica el objeto copy desde un archivo (o el `copy` de un tema exportado)"],
12208
- ["--out FILE.tsx|.jsx [--force]", "embed: escribe en tu proyecto un componente de React COMPLETO y editable (sin depender de ning\xFAn paquete) con tu token y tu tema horneados arriba; no pisa un archivo que ya existe sin --force"],
12209
- ["--check PATH", "box: este archivo debe existir en la m\xE1quina (repetible), p. ej. un archivo que trae la imagen nueva"],
12210
- ["--contains PATH=TEXT", "box: ese archivo debe contener TEXT (un cambio que no agrega archivos)"],
12211
- ["--recycle", "box: reemplaza sus m\xE1quinas (nunca a media respuesta); pool: la nueva nace en el siguiente turno; ACP (goose, lite) igual, con el historial del \xFAltimo respaldo"],
12212
- ["--start", "box: levanta (o despierta) su caja ya, sin gastar un turno; dentro de las cajas del plan (503 si el espacio est\xE1 lleno)"],
12213
- ["--reenv / --force", "box --recycle en ACP: --reenv s\xF3lo re-hornea el env en la misma caja; --force la recrea aunque est\xE9 ocupada (due\xF1o o staff)"],
12214
- ["--unset-env K1,K2", "set: borra esas variables del env (--env K= tambi\xE9n borra K)"],
12215
- ["--tool [ch:]tool=on|off", "set: prende/apaga una tool en un canal (wa:actualizar_identidad=off) o en todos (crm_buscar=off); separadas por comas"],
12216
- ["--bag NAME|none", "set: de qu\xE9 bolsa de tokens de su espacio gasta; none = el saldo del espacio (s\xF3lo staff)"],
12217
- ["--box MB[:N]", "set: RAM de su caja (1024, 2048, 4096, 8192) y conversaciones a la vez, p. ej. 4096:10; recicla sus cajas (s\xF3lo staff)"],
12218
- ["--avatar URL|FILE", "set: su cara en Teams: una liga https o un JPEG/PNG/WebP local (se guarda en img.ghosty.studio)"],
12219
- ["--from-workspace", "rm: lo desactiva en cada Teams donde est\xE9 y luego lo borra (due\xF1o o staff)"],
12220
- ["--prompt-mode M", "set: append (default: su prompt + la persona de Ghosty) o replace (s\xF3lo su prompt)"],
12221
- ["--sponsor/--owner EMAIL", "ls: agentes de CUALQUIER cuenta que paga / de quien es ese correo (staff de Ghosty); luego se operan por id"],
12222
- ["--ids", "ls: s\xF3lo los ids, uno por rengl\xF3n (para xargs)"],
12223
- ["--user EMAIL --app A", "ls (staff de Ghosty): la lista de esa persona tal como la recibe su app (tono, espacio, \xFAltimo uso) y sus tel\xE9fonos con la build de la app; A = all|android|ios|desktop"]
12224
- ],
12225
- notes: [
12226
- "doctor sale con 1 cuando encuentra un problema, as\xED puede frenar un script o un CI.",
12227
- "Un prompt derivado no se edita directo (409): cambia la base o el overlay, o desenl\xE1zalo con --prompt-base none.",
12228
- "demo sin flags s\xF3lo imprime su estado: liga, caducidad, topes y n\xFAmeros."
12229
- ]
12230
- },
12231
- skills: {
12232
- summary: "Skills de un agente (formato agentskills.io: una carpeta con SKILL.md).",
12233
- replace: [
12210
+ async function spacesCmd(sub, slug, arg, v, context) {
12211
+ const ex = "ghosty spaces ls [--user EMAIL] \xB7 create <slug> [--combo teams|sales|both|none] [--dry-run] \xB7 join|leave <workspace> \xB7 set <workspace> --combo teams|sales|both|none | --model-cap 0.25|1|4|none | --house-claude on|off \xB7 renew <workspace> --until 2026-11-01 [--note TEXT] \xB7 rename <old> <new> [--dry-run] \xB7 archive|restore <workspace> [--dry-run] \xB7 transfer <workspace> --to EMAIL [--keep-as admin|member|none] [--copy-keys] [--dry-run] \xB7 log <workspace> [--since 30d] [--action workspace]";
12212
+ if (!sub || sub === "ls" || sub === "list") {
12213
+ const r = await api(`/api/v2/workspaces${v.user ? `?user=${enc9(v.user)}` : ""}`);
12214
+ const rows = r.workspaces.map((w) => ({ workspace: w.slug, role: w.role ?? "", plan: w.plan, combo: w.combo, owner: w.owner ?? "", paid: w.paidUntil ?? "" }));
12215
+ return out(r, () => rows.length ? `${bold(r.user)}
12216
+ ${table(rows, ["workspace", "role", "plan", "combo", "owner", "paid"])}` : dim(t(`${r.user} has no workspaces.`, `${r.user} no tiene espacios.`)));
12217
+ }
12218
+ if (sub === "create") {
12219
+ slug = needSpace(slug, "ghosty spaces create <slug> [--combo teams|sales|both|none] [--dry-run]");
12220
+ const r = await api("/api/v2/workspaces", { method: "POST", body: { slug, ...v.combo ? { combo: v.combo } : {}, ...v["dry-run"] ? { dryRun: true } : {} } });
12221
+ const line2 = `${r.workspace} \xB7 ${r.owner} \xB7 ${r.plan} \xB7 ${r.combo}`;
12222
+ if (!r.created) return out(r, () => `${line2}
12223
+ ${dim(t("Dry run: nothing created.", "Simulaci\xF3n: no se cre\xF3 nada."))}`);
12224
+ return out(r, () => `${lavender("\u2713")} ${line2}
12225
+ ${dim(t(`Next: ghosty use ${r.workspace} \xB7 ghosty partner enable ${r.workspace}`, `Sigue: ghosty use ${r.workspace} \xB7 ghosty partner enable ${r.workspace}`))}`);
12226
+ }
12227
+ if (sub === "log") {
12228
+ slug = needSpace(slug, "ghosty spaces log <workspace> [--since 30d] [--action workspace]");
12229
+ const q = new URLSearchParams({ limit: v.limit ?? "50" });
12230
+ if (v.since) q.set("since", v.since);
12231
+ if (v.action) q.set("action", v.action);
12232
+ const r = await api(wsPath(slug, `/log?${q}`));
12233
+ const rows = r.events.map((e) => ({ at: e.at.slice(0, 16).replace("T", " "), actor: e.actor, action: e.action, detail: serverText(e.detail ? JSON.stringify(e.detail) : "").slice(0, 110) }));
12234
+ return out(r, () => rows.length ? table(rows, ["at", "actor", "action", "detail"]) : dim(t(`No events in ${r.workspace}${v.since ? ` since ${v.since}` : ""}.`, `Sin eventos en ${r.workspace}${v.since ? ` desde ${v.since}` : ""}.`)));
12235
+ }
12236
+ if (sub === "join" || sub === "leave") {
12237
+ slug = needSpace(slug, `ghosty spaces ${sub} <workspace>`);
12238
+ const r = await api(wsPath(slug, "/staff"), { method: "POST", body: { join: sub === "join" } });
12239
+ return out(r, () => `${lavender("\u2713")} ${r.joined ? t(`You're in ${r.workspace} as staff (no seat; logged). Leave: ghosty spaces leave ${r.workspace}`, `Entraste a ${r.workspace} como staff (sin asiento; queda en bit\xE1cora). Salir: ghosty spaces leave ${r.workspace}`) : t(`You left ${r.workspace}.`, `Saliste de ${r.workspace}.`)}`);
12240
+ }
12241
+ if (sub === "set") {
12242
+ slug = needSpace(slug ?? context, "ghosty spaces set <workspace> --combo sales | --model-cap 4");
12243
+ const cap = v["model-cap"];
12244
+ const house = parseSwitch("--house-claude", v["house-claude"]);
12245
+ if (!v.combo && cap === void 0 && house === void 0) throw usage(t("Nothing to change.", "Nada que cambiar."), "--combo teams|sales|both|none \xB7 --model-cap 0.25|1|4|none \xB7 --house-claude on|off");
12246
+ const steps = [];
12247
+ if (v.combo) steps.push({ label: "combo", path: wsPath(slug, "/combo"), body: { combo: v.combo } });
12248
+ if (cap !== void 0) steps.push({ label: "model-cap", path: wsPath(slug, "/model-cap"), body: { cap } });
12249
+ if (house !== void 0) steps.push({ label: "house-claude", path: wsPath(slug, "/house-claude"), body: { on: house } });
12250
+ const show2 = (x) => x === null ? t("plan's", "el del plan") : x === true ? "on" : x === false ? "off" : String(x);
12251
+ const plans = await Promise.all(steps.map(async (st) => ({ ...st, plan: await api(st.path, { method: "POST", body: { ...st.body, dryRun: true } }) })));
12252
+ const lines = plans.map((p) => `${p.plan.workspace}: ${p.label} ${show2(p.plan.from)} \u2192 ${show2(p.plan.to)}`);
12253
+ const pending = plans.filter((p) => p.plan.changed);
12254
+ if (!pending.length) return out(plans.map((p) => p.plan), () => dim(t(`Nothing to do: ${slug} already has that.`, `Nada que hacer: ${slug} ya lo tiene.`)));
12255
+ if (v["dry-run"]) return out(plans.map((p) => p.plan), () => `${lines.join("\n")}
12256
+ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
12257
+ const retry = `ghosty spaces set ${slug}${v.combo ? ` --combo ${v.combo}` : ""}${cap !== void 0 ? ` --model-cap ${cap}` : ""}${house !== void 0 ? ` --house-claude ${house ? "on" : "off"}` : ""}`;
12258
+ requireYesIfHeadless(v.yes, retry);
12259
+ await confirm(`${lines.join("\n")}
12260
+ ${t("Change it?", "\xBFCambiarlo?")}`, { yes: v.yes, retry });
12261
+ const done = [];
12262
+ for (const p of pending) done.push(await api(p.path, { method: "POST", body: p.body }));
12263
+ return out(done, () => done.map((r, i) => `${lavender("\u2713")} ${r.workspace}: ${pending[i].label} ${show2(r.to)}`).join("\n"));
12264
+ }
12265
+ if (sub === "renew") {
12266
+ slug = needSpace(slug ?? context, ex);
12267
+ if (!v.until) throw usage(t("Missing --until YYYY-MM-DD.", "Falta --until AAAA-MM-DD."), `ghosty spaces renew ${slug} --until 2026-11-01`);
12268
+ const path = wsPath(slug, "/renew");
12269
+ const plan = await api(path, { method: "POST", body: { until: v.until, dryRun: true } });
12270
+ const line2 = t(`${plan.workspace} (${plan.plan}): paid until ${plan.from ?? "\u2014"} \u2192 ${plan.to}`, `${plan.workspace} (${plan.plan}): pagado hasta ${plan.from ?? "\u2014"} \u2192 ${plan.to}`);
12271
+ if (v["dry-run"]) return out(plan, () => `${line2}
12272
+ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
12273
+ requireYesIfHeadless(v.yes, `ghosty spaces renew ${slug} --until ${v.until}`);
12274
+ await confirm(`${line2}
12275
+ ${t("Renew?", "\xBFRenovar?")}`, { yes: v.yes, retry: `ghosty spaces renew ${slug} --until ${v.until}` });
12276
+ const r = await api(path, { method: "POST", body: { until: v.until, ...v.note ? { note: v.note } : {} } });
12277
+ return out(r, () => `${lavender("\u2713")} ${line2} ${dim(t("(logged in the workspace's audit)", "(queda en la bit\xE1cora del espacio)"))}`);
12278
+ }
12279
+ if (sub === "rename") {
12280
+ slug = needSpace(slug, ex);
12281
+ if (!arg) throw usage(t("Missing the new name.", "Falta el nombre nuevo."), `ghosty spaces rename ${slug} <new> --dry-run`);
12282
+ const path = wsPath(slug, "/rename");
12283
+ const plan = await api(path, { method: "POST", body: { to: arg, dryRun: true } });
12284
+ const text = `${plan.from} \u2192 ${bold(plan.to)}
12285
+ ${plan.warnings.map((w) => dim(` \xB7 ${serverText(w)}`)).join("\n")}`;
12286
+ if (v["dry-run"]) return out(plan, () => `${text}
12287
+ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
12288
+ requireYesIfHeadless(v.yes, `ghosty spaces rename ${slug} ${arg}`);
12289
+ await confirm(`${text}
12290
+ ${t("Rename?", "\xBFRenombrar?")}`, { yes: v.yes, expect: plan.from, retry: `ghosty spaces rename ${slug} ${arg}` });
12291
+ const r = await api(path, { method: "POST", body: { to: arg } });
12292
+ return out(r, () => `${lavender("\u2713")} ${t(`Renamed: ${r.from} \u2192 ${r.to}. Undo: ghosty spaces rename ${r.to} ${r.from}`, `Renombrado: ${r.from} \u2192 ${r.to}. Deshacer: ghosty spaces rename ${r.to} ${r.from}`)}`);
12293
+ }
12294
+ if (sub === "archive" || sub === "restore") {
12295
+ slug = needSpace(slug, `ghosty spaces ${sub} <workspace> --dry-run`);
12296
+ const archive = sub === "archive";
12297
+ const path = wsPath(slug, "/archive");
12298
+ const plan = await api(path, { method: "POST", body: { archive, dryRun: true } });
12299
+ const line2 = `${plan.slug}: ${plan.from} \u2192 ${bold(plan.to)} ${dim(t(`(${plan.agents} agent(s); they and their data stay as they are)`, `(${plan.agents} agente(s); ellos y sus datos se quedan igual)`))}`;
12300
+ if (!plan.changed) return out(plan, () => dim(t(`Nothing to do: ${plan.slug} is already ${plan.from}.`, `Nada que hacer: ${plan.slug} ya est\xE1 ${plan.from}.`)));
12301
+ if (v["dry-run"]) return out(plan, () => `${line2}
12302
+ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
12303
+ const retry = `ghosty spaces ${sub} ${slug}`;
12304
+ requireYesIfHeadless(v.yes, retry);
12305
+ await confirm(`${line2}
12306
+ ${archive ? t("Archive it?", "\xBFArchivarlo?") : t("Restore it?", "\xBFRestaurarlo?")}`, { yes: v.yes, retry, ...archive ? { expect: plan.slug } : {} });
12307
+ const r = await api(path, { method: "POST", body: { archive } });
12308
+ return out(r, () => archive ? `${lavender("\u2713")} ${t(`Archived: ${r.slug}. Undo: ghosty spaces restore ${r.slug}`, `Archivado: ${r.slug}. Deshacer: ghosty spaces restore ${r.slug}`)}` : `${lavender("\u2713")} ${t(`Restored: ${r.slug}.`, `Restaurado: ${r.slug}.`)}`);
12309
+ }
12310
+ if (sub === "transfer") {
12311
+ slug = needSpace(slug ?? context, "ghosty spaces transfer <workspace> --to EMAIL --dry-run");
12312
+ const toEmail = v.to ?? arg;
12313
+ if (!toEmail) throw usage(t("Missing --to EMAIL.", "Falta --to CORREO."), `ghosty spaces transfer ${slug} --to new@owner.com --keep-as admin --copy-keys --dry-run`);
12314
+ const body = { to: toEmail, ...v["keep-as"] ? { keepAs: v["keep-as"] } : {}, ...v["copy-keys"] ? { copyKeys: true } : {} };
12315
+ const path = wsPath(slug, "/transfer");
12316
+ const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
12317
+ const text = [
12318
+ `${plan.workspace}: ${plan.from ?? "\u2014"} \u2192 ${bold(plan.to)} ${dim(t(`(previous owner stays as ${plan.keepAs})`, `(el anterior queda como ${plan.keepAs})`))}`,
12319
+ `${t("agents", "agentes")}: ${plan.agents.join(", ") || "\u2014"}`,
12320
+ `${t("keys", "llaves")}: ${plan.keys.copy.length ? t(`copies ${plan.keys.copy.join(", ")}`, `copia ${plan.keys.copy.join(", ")}`) : t("copies none", "no copia")}${plan.keys.alreadyHas.length ? dim(t(` \xB7 already has ${plan.keys.alreadyHas.join(", ")}`, ` \xB7 ya tiene ${plan.keys.alreadyHas.join(", ")}`)) : ""}`,
12321
+ ...plan.warnings.map((w) => red(` \xB7 ${serverText(w)}`))
12322
+ ].join("\n");
12323
+ if (v["dry-run"]) return out(plan, () => `${text}
12324
+ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
12325
+ const retry = `ghosty spaces transfer ${slug} --to ${toEmail}${v["keep-as"] ? ` --keep-as ${v["keep-as"]}` : ""}${v["copy-keys"] ? " --copy-keys" : ""}`;
12326
+ requireYesIfHeadless(v.yes, retry);
12327
+ await confirm(`${text}
12328
+ ${t("Transfer it? Its agents' boxes are recycled (a turn in flight finishes first).", "\xBFTraspasarlo? Se reciclan las cajas de sus agentes (un turno en vuelo termina antes).")}`, { yes: v.yes, expect: plan.workspace, retry });
12329
+ const r = await api(path, { method: "POST", body });
12330
+ return out(r, () => `${lavender("\u2713")} ${t(`${r.workspace} now belongs to ${r.to}; ${r.from ?? "\u2014"} stays as ${r.keepAs}. ${r.recycled} agent(s) recycled.`, `${r.workspace} ya es de ${r.to}; ${r.from ?? "\u2014"} queda como ${r.keepAs}. ${r.recycled} agente(s) reciclado(s).`)}${r.warnings.length ? `
12331
+ ${r.warnings.map((w) => red(` \xB7 ${serverText(w)}`)).join("\n")}` : ""}`);
12332
+ }
12333
+ throw usage(t(`Unknown spaces subcommand: ${sub ?? "(none)"}.`, `Subcomando de spaces desconocido: ${sub ?? "(ninguno)"}.`), ex);
12334
+ }
12335
+ async function workspaceUsage(slug, v) {
12336
+ slug = needSpace(slug, "ghosty usage --workspace <slug> --by agent,day --days 14");
12337
+ const r = await api(wsPath(slug, `/usage?days=${enc9(v.days ?? "14")}`));
12338
+ const cdmx2 = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
12339
+ if (v["quota-hits"]) {
12340
+ const rows2 = r.quotaHits.map((q) => ({ at: cdmx2(q.at), agent: q.agent, channel: q.channel, from: q.phone ?? q.conversationId.slice(0, 24), message: (q.preview ?? "").slice(0, 60) }));
12341
+ const total2 = r.agents.reduce((n, a) => n + a.noQuota, 0);
12342
+ return out(r.quotaHits, () => rows2.length ? `${table(rows2, ["at", "agent", "channel", "from", "message"])}
12343
+ ${dim(t(`${total2} message(s) unanswered for lack of balance in ${r.days} day(s) (CDMX time)`, `${total2} mensaje(s) sin atender por saldo en ${r.days} d\xEDa(s) (hora CDMX)`))}` : dim(t(`No message ran out of balance in ${r.days} day(s).`, `Ning\xFAn mensaje se qued\xF3 sin saldo en ${r.days} d\xEDa(s).`)));
12344
+ }
12345
+ const by = new Set((v.by ?? "agent").split(",").map((x) => x.trim()));
12346
+ if ([...by].some((b) => b !== "agent" && b !== "day")) throw usage(t("--by takes agent, day or agent,day.", "--by acepta agent, day o agent,day."));
12347
+ let rows;
12348
+ const fmt2 = (x) => ({ turns: x.turns, failed: x.failed ? red(String(x.failed)) : 0, tokens: fmtTokens(x.billable), "no balance": x.noQuota || "" });
12349
+ if (by.has("day") && by.has("agent")) rows = r.rows.map((x) => ({ day: x.day, agent: x.agent, ...fmt2(x) }));
12350
+ else if (by.has("day")) {
12351
+ const days = /* @__PURE__ */ new Map();
12352
+ for (const x of r.rows) {
12353
+ const d = days.get(x.day) ?? { agentId: "", agent: "", turns: 0, failed: 0, billable: 0, outputTokens: 0, cacheRead: 0, noQuota: 0 };
12354
+ d.turns += x.turns;
12355
+ d.failed += x.failed;
12356
+ d.billable += x.billable;
12357
+ d.noQuota += x.noQuota;
12358
+ days.set(x.day, d);
12359
+ }
12360
+ rows = [...days].map(([day3, x]) => ({ day: day3, ...fmt2(x) }));
12361
+ } else rows = r.agents.map((x) => ({ agent: x.agent, ...fmt2(x) }));
12362
+ const cols = [...by.has("day") ? ["day"] : [], ...by.has("agent") ? ["agent"] : [], "turns", "failed", "tokens", "no balance"];
12363
+ const total = r.agents.reduce((n, a) => n + a.billable, 0);
12364
+ out(r, () => `${bold(r.workspace)} ${dim(t(`last ${r.days} day(s), CDMX days`, `\xFAltimos ${r.days} d\xEDa(s), d\xEDas en hora CDMX`))}
12365
+ ${table(rows, cols)}
12366
+ ${dim(t(`${fmtTokens(total)} billable tokens \xB7 failed = error or stopped \xB7 no balance: ghosty turns ls --workspace ${r.workspace} --quota-hits`, `${fmtTokens(total)} tokens facturables \xB7 failed = error o detenido \xB7 sin saldo: ghosty turns ls --workspace ${r.workspace} --quota-hits`))}`);
12367
+ }
12368
+ var USAGE_DIMS = ["model", "day", "canal"];
12369
+ function groupAgentUsage(rows, by) {
12370
+ const groups = /* @__PURE__ */ new Map();
12371
+ for (const r of rows) {
12372
+ const k = by.map((d) => r[d]).join("|");
12373
+ const g = groups.get(k) ?? { day: by.includes("day") ? r.day : "", model: by.includes("model") ? r.model : "", canal: by.includes("canal") ? r.canal : "", turns: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheCreation: 0, billable: 0, usd: 0 };
12374
+ g.turns += r.turns;
12375
+ g.inputTokens += r.inputTokens;
12376
+ g.outputTokens += r.outputTokens;
12377
+ g.cacheRead += r.cacheRead;
12378
+ g.cacheCreation += r.cacheCreation;
12379
+ g.billable += r.billable;
12380
+ g.usd += r.usd;
12381
+ groups.set(k, g);
12382
+ }
12383
+ return [...groups.values()].sort((a, b) => a.day.localeCompare(b.day) || b.usd - a.usd);
12384
+ }
12385
+ async function agentUsage(id, v) {
12386
+ if (v.by?.trim() === "visitor") return anonVisitorUsage(id, v);
12387
+ const by = (v.by ?? "model").split(",").map((x) => x.trim()).filter(Boolean);
12388
+ if (!by.length || by.some((b) => !USAGE_DIMS.includes(b))) throw usage(t("With --agent, --by takes model, day and/or canal (or visitor alone).", "Con --agent, --by acepta model, day y/o canal (o visitor solo)."), "ghosty usage --agent <id> --by model,day --cost");
12389
+ const r = await api(`/api/v2/agents/${enc9(id)}/usage?days=${enc9(v.days ?? "14")}`);
12390
+ const groups = groupAgentUsage(r.rows, by);
12391
+ const rows = groups.map((g) => ({ ...Object.fromEntries(by.map((d) => [d, g[d]])), turns: g.turns, tokens: fmtTokens(g.billable), "cache read": fmtTokens(g.cacheRead), out: fmtTokens(g.outputTokens), ...v.cost ? { usd: `$${g.usd.toFixed(2)}` } : {} }));
12392
+ const cols = [...by, "turns", "tokens", "cache read", "out", ...v.cost ? ["usd"] : []];
12393
+ const turns = r.rows.reduce((n, x) => n + x.turns, 0);
12394
+ const foot = v.cost ? t(`$${r.totalUsd.toFixed(2)} USD at provider list price (cache writes included) \xB7 ${turns} turn(s)`, `$${r.totalUsd.toFixed(2)} USD a precio de lista del proveedor (con escritura de cach\xE9) \xB7 ${turns} turno(s)`) : t(`${turns} turn(s) \xB7 USD per row: --cost`, `${turns} turno(s) \xB7 USD por fila: --cost`);
12395
+ out({ ...r, groups }, () => rows.length ? `${bold(r.agent)} ${dim(t(`last ${r.days} day(s), CDMX days`, `\xFAltimos ${r.days} d\xEDa(s), d\xEDas en hora CDMX`))}
12396
+ ${table(rows, cols)}
12397
+ ${dim(foot)}` : dim(t(`No usage in ${r.days} day(s).`, `Sin uso en ${r.days} d\xEDa(s).`)));
12398
+ }
12399
+ async function anonVisitorUsage(id, v) {
12400
+ const r = await api(`/api/v2/agents/${enc9(id)}/usage?days=${enc9(v.days ?? "14")}&visitors=1`);
12401
+ const vs = r.visitors ?? { list: [], total: { visitors: 0, accounts: 0, usd: 0 } };
12402
+ const yes = t("yes", "s\xED");
12403
+ const rows = vs.list.map((x) => ({
12404
+ visitor: x.visitor.slice(0, 8),
12405
+ first: x.firstAt ? x.firstAt.slice(0, 16).replace("T", " ") : "",
12406
+ msgs: x.messages,
12407
+ usd: `$${x.usd.toFixed(3)}`,
12408
+ page: x.page ? yes : "",
12409
+ pdf: x.pdfs || "",
12410
+ account: x.accountAt ? x.accountAt.slice(0, 10) : ""
12411
+ }));
12412
+ const { visitors, accounts, usd } = vs.total;
12413
+ const perVisitor = visitors ? usd / visitors : 0;
12414
+ const perAccount = accounts ? usd / accounts : null;
12415
+ const foot = t(
12416
+ `${visitors} visitor(s) \xB7 ${accounts} created an account (${visitors ? Math.round(accounts / visitors * 100) : 0}%) \xB7 $${usd.toFixed(2)} USD \xB7 $${perVisitor.toFixed(3)} per visitor${perAccount !== null ? ` \xB7 $${perAccount.toFixed(2)} per new account` : ""} (provider list price; times in UTC)`,
12417
+ `${visitors} visitante(s) \xB7 ${accounts} crearon cuenta (${visitors ? Math.round(accounts / visitors * 100) : 0}%) \xB7 $${usd.toFixed(2)} USD \xB7 $${perVisitor.toFixed(3)} por visitante${perAccount !== null ? ` \xB7 $${perAccount.toFixed(2)} por cuenta nueva` : ""} (precio de lista del proveedor; horas en UTC)`
12418
+ );
12419
+ out(r, () => rows.length ? `${bold(r.agent)} ${dim(t(`/c without an account, last ${r.days} day(s)`, `/c sin cuenta, \xFAltimos ${r.days} d\xEDa(s)`))}
12420
+ ${table(rows, ["visitor", "first", "msgs", "usd", "page", "pdf", "account"])}
12421
+ ${dim(foot)}` : dim(t(`No visitors in ${r.days} day(s).`, `Sin visitantes en ${r.days} d\xEDa(s).`)));
12422
+ }
12423
+ function fmtUntil(iso) {
12424
+ if (!iso) return "";
12425
+ const d = new Date(new Date(iso).getTime() - 6 * 36e5);
12426
+ return d.toISOString().slice(0, 16).replace("T", " ");
12427
+ }
12428
+ async function spacesMembers(action, slug, email, v, context) {
12429
+ if (context && slug?.includes("@") && !email) [slug, email] = [context, slug];
12430
+ if (action && !["ls", "list", "add", "rm", "remove"].includes(action) && !slug) [action, slug] = ["ls", action];
12431
+ const ex = "ghosty spaces members ls <workspace> \xB7 add <workspace> <email>[,email\u2026] [--role member|admin] [--until 2026-10-28|14d] \xB7 rm <workspace> <email>";
12432
+ slug = needSpace(slug ?? context, ex);
12433
+ switch (action ?? "ls") {
12434
+ case "ls":
12435
+ case "list": {
12436
+ const r = await api(wsPath(slug, "/members"));
12437
+ const rows = r.members.map((m) => ({ email: m.email, role: m.role.toLowerCase(), status: m.status, until: fmtUntil(m.until) }));
12438
+ return out(r, () => `${table(rows, ["email", "role", "status", "until"])}${r.error ? `
12439
+ ${lavender(`\u26A0\uFE0F ${serverText(r.error)}`)}` : ""}`);
12440
+ }
12441
+ case "add": {
12442
+ if (!email) throw usage(t("Missing email.", "Falta el correo."), ex);
12443
+ const emails = email.split(",").map((e) => e.trim()).filter(Boolean);
12444
+ const r = await api(wsPath(slug, "/members"), { method: "POST", body: { emails, role: v.role ?? "member", ...v.until ? { until: v.until } : {} } });
12445
+ const lines = [
12446
+ ...r.invited.map((e) => `${lavender("\u2713")} ${e} ${t(`added to ${r.workspace}`, `agregada a ${r.workspace}`)}${r.until ? dim(t(` until ${fmtUntil(r.until)} (CDMX), then removed`, ` hasta ${fmtUntil(r.until)} (CDMX), luego se retira sola`)) : ""}`),
12447
+ ...r.extended.map((e) => `${lavender("\u2713")} ${e} ${t("was already in; expires", "ya estaba; vence")} ${fmtUntil(r.until)} (CDMX)`),
12448
+ ...r.failed.map((f) => `${red("\u2717")} ${f.email}: ${serverText(f.error)}`),
12449
+ ...r.mailFailed.map((e) => dim(t(`The invite email to ${e} failed; access works anyway.`, `El correo de invitaci\xF3n a ${e} fall\xF3; el acceso funciona igual.`)))
12450
+ ];
12451
+ return out(r, () => lines.join("\n"));
12452
+ }
12453
+ case "rm":
12454
+ case "remove": {
12455
+ if (!email) throw usage(t("Missing email.", "Falta el correo."), ex);
12456
+ requireYesIfHeadless(v.yes, `ghosty spaces members rm ${slug} ${email}`);
12457
+ await confirm(t(`Remove ${email} from ${slug}? (loses the seat, the boards and the agents of the workspace)`, `\xBFQuitar a ${email} de ${slug}? (pierde el asiento, los tableros y los agentes del espacio)`), { yes: v.yes, retry: `ghosty spaces members rm ${slug} ${email}` });
12458
+ const r = await api(wsPath(slug, "/members"), { method: "DELETE", body: { email } });
12459
+ return out(r, () => `${lavender("\u2713")} ${r.result === "removed" ? t(`${r.email} removed from ${r.workspace}.`, `${r.email} fuera de ${r.workspace}.`) : t(`Invite to ${r.email} canceled.`, `Invitaci\xF3n a ${r.email} cancelada.`)}`);
12460
+ }
12461
+ default:
12462
+ throw usage(t(`Unknown members subcommand: ${action}.`, `Subcomando de members desconocido: ${action}.`), ex);
12463
+ }
12464
+ }
12465
+
12466
+ // src/commands/boards.ts
12467
+ var enc10 = encodeURIComponent;
12468
+ async function boardsCmd(sub, a1, a2, v, context) {
12469
+ if (context && sub !== "assign") {
12470
+ if ((sub === "ls" || sub === "list" || !sub || sub === "move-cards") && !a1) a1 = context;
12471
+ else if ((sub === "create" || sub === "rm") && a1 && !a2) [a1, a2] = [context, a1];
12472
+ else if (sub === "access" && !a2) [a1, a2] = [context, a1 ?? "main"];
12473
+ }
12474
+ switch (sub ?? "ls") {
12475
+ case "ls":
12476
+ case "list": {
12477
+ if (!a1) throw usage(t("Missing workspace.", "Falta el espacio."), t("Example", "Ejemplo") + ": ghosty boards ls <workspace-slug>");
12478
+ const r = await api(`/api/v2/workspaces/${enc10(a1)}/boards`);
12479
+ const rows = r.boards.map((b) => ({ name: b.main ? `${b.name} (${t("main", "principal")})` : b.name, cards: b.cards, agents: b.agents.map((x) => x.name ?? x.id).join(", "), columns: b.columns.length, id: b.id }));
12480
+ return out(r, () => table(rows, ["name", "cards", "agents", "columns", "id"]));
12481
+ }
12482
+ case "create": {
12483
+ if (!a1 || !a2) throw usage(t("Missing workspace or name.", "Falta el espacio o el nombre."), t("Example", "Ejemplo") + ': ghosty boards create <workspace> "Siiqtec - Ixmiquilpan" --columns-from main');
12484
+ const r = await api(`/api/v2/workspaces/${enc10(a1)}/boards`, { method: "POST", body: { name: a2, ...v["columns-from"] ? { columnsFrom: v["columns-from"] } : {} } });
12485
+ return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.name}\xBB created${r.columnsFrom ? ` with the ${r.columns} column(s) of \xAB${r.columnsFrom}\xBB` : ""}.`, `Tablero \xAB${r.name}\xBB creado${r.columnsFrom ? ` con las ${r.columns} columna(s) de \xAB${r.columnsFrom}\xBB` : ""}.`)} ${dim(`${t("Send an agent's cards there", "Manda ah\xED las tarjetas de un agente")}: ghosty boards assign <agent-id> "${r.name}"`)}${r.warning ? `
12486
+ ${lavender(`\u26A0\uFE0F ${serverText(r.warning)}`)}` : ""}`);
12487
+ }
12488
+ case "rm": {
12489
+ if (!a1 || !a2) throw usage(t("Missing workspace or board.", "Falta el espacio o el tablero."), t("Example", "Ejemplo") + ': ghosty boards rm <workspace> "Prueba"');
12490
+ requireYesIfHeadless(v.yes, `ghosty boards rm ${a1} ${JSON.stringify(a2)}`);
12491
+ await confirm(t(`Delete the board \xAB${a2}\xBB of ${a1}? (only an empty one; its agents go back to the main board)`, `\xBFBorrar el tablero \xAB${a2}\xBB de ${a1}? (s\xF3lo si est\xE1 vac\xEDo; sus agentes regresan al tablero principal)`), { yes: v.yes, retry: `ghosty boards rm ${a1} ${JSON.stringify(a2)}` });
12492
+ const r = await api(`/api/v2/workspaces/${enc10(a1)}/boards`, { method: "DELETE", body: { board: a2 } });
12493
+ return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.deleted}\xBB deleted.`, `Tablero \xAB${r.deleted}\xBB borrado.`)}`);
12494
+ }
12495
+ case "assign": {
12496
+ if (!a1 || !a2) throw usage(t("Missing agent or board.", "Falta el agente o el tablero."), t("Example", "Ejemplo") + ': ghosty boards assign <agent-id> "Siiqtec - Ixmiquilpan" (' + t("or main", "o main") + ")");
12497
+ const r = await api(`/api/v2/agents/${enc10(a1)}`, { method: "PATCH", body: { board: a2 } });
12498
+ return out(r, () => `${lavender("\u2713")} ${t(`New cards of ${a1} go to \xAB${r.board}\xBB.`, `Las tarjetas nuevas de ${a1} van a \xAB${r.board}\xBB.`)} ${dim(`${t("Existing ones stay where they are", "Las que ya existen se quedan donde est\xE1n")}: ghosty boards move-cards ${r.workspace} --from \u2026 --to "${r.board}" \u2026`)}`);
12499
+ }
12500
+ case "move-cards": {
12501
+ if (!a1 || !v.from || !v.to) throw usage(t("Missing workspace, --from or --to.", "Falta el espacio, --from o --to."), t("Example", "Ejemplo") + ': ghosty boards move-cards <workspace> --from main --to "Siiqtec - Ixmiquilpan" --integration 6a3b02ad\u2026 --dry-run');
12502
+ if (!v.integration && !v.phone && !v.all) throw usage(t("Add a filter: --integration ID, --phone N or --all.", "Agrega un filtro: --integration ID, --phone N o --all."), t("A filter-less move would empty the board.", "Mover sin filtro vaciar\xEDa el tablero."));
12503
+ const path = `/api/v2/workspaces/${enc10(a1)}/boards/move-cards`;
12504
+ const body = { from: v.from, to: v.to, ...v.integration ? { integration: v.integration } : {}, ...v.phone ? { phone: v.phone } : {}, ...v.all ? { all: true } : {} };
12505
+ const retry = `ghosty boards move-cards ${a1} --from ${JSON.stringify(v.from)} --to ${JSON.stringify(v.to)}${v.integration ? ` --integration ${v.integration}` : ""}${v.phone ? ` --phone ${v.phone}` : ""}${v.all ? " --all" : ""}`;
12506
+ if (!v["dry-run"]) requireYesIfHeadless(v.yes, retry);
12507
+ const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
12508
+ const sample = table(plan.sample.map((c) => ({ folio: c.folio ? `#${c.folio}` : "", name: c.name ?? "", phone: c.phone })), ["folio", "name", "phone"]);
12509
+ const text = `${sample}
12510
+ ${t(`${plan.moved} of ${plan.matched} card(s)`, `${plan.moved} de ${plan.matched} tarjeta(s)`)} \xAB${plan.from}\xBB \u2192 \xAB${plan.to}\xBB${plan.skipped.length ? dim(t(` \xB7 ${plan.skipped.length} already in \xAB${plan.to}\xBB, skipped`, ` \xB7 ${plan.skipped.length} ya en \xAB${plan.to}\xBB, se omiten`)) : ""}`;
12511
+ if (v["dry-run"] || !plan.moved) return out(plan, () => `${text}
12512
+ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
12513
+ await confirm(`${text}
12514
+ ${t("Move them with their threads and orders? (they get a new folio there)", "\xBFMoverlas con sus hilos y pedidos? (all\xE1 reciben folio nuevo)")}`, { yes: v.yes, retry });
12515
+ const r = await api(path, { method: "POST", body });
12516
+ return out(r, () => `${lavender("\u2713")} ${t(`Moved ${r.moved} card(s) (${r.messages} message(s), ${r.orders} order(s)) to \xAB${r.to}\xBB.`, `Se movieron ${r.moved} tarjeta(s) (${r.messages} mensaje(s), ${r.orders} pedido(s)) a \xAB${r.to}\xBB.`)}${r.skipped.length ? dim(t(` Skipped ${r.skipped.length}.`, ` Se omitieron ${r.skipped.length}.`)) : ""}`);
12517
+ }
12518
+ // Quién ve un tablero: lista «sólo estas personas» o todo el espacio (feedback del revisor de Meta).
12519
+ case "access": {
12520
+ if (!a1) throw usage(t("Missing workspace.", "Falta el espacio."), t("Example", "Ejemplo") + ': ghosty boards access <workspace> "Siiqtec - Ixmiquilpan" --add ana@x.com --until 14d');
12521
+ const board = a2 ?? "main";
12522
+ const split = (xs) => (xs ?? []).flatMap((x) => x.split(",")).map((x) => x.trim()).filter(Boolean);
12523
+ const add = split(v.add);
12524
+ const remove = split(v.remove);
12525
+ const edit = add.length || remove.length || v.everyone;
12526
+ const r = edit ? await api(`/api/v2/workspaces/${enc10(a1)}/boards/access`, { method: "POST", body: { board, add, remove, ...v.everyone ? { everyone: true } : {}, ...v.restrict ? { restrict: true } : {}, ...v.until ? { until: v.until } : {} } }) : await api(`/api/v2/workspaces/${enc10(a1)}/boards/access?board=${enc10(board)}`);
12527
+ return out(r, () => {
12528
+ const head = `\xAB${r.board}\xBB (${r.workspace}): ${r.everyone ? t("the whole workspace sees it", "lo ve todo el espacio") : t("only these people (plus owner and staff)", "s\xF3lo estas personas (m\xE1s due\xF1o y staff)")}`;
12529
+ const people = r.everyone ? "" : `
12530
+ ${table(r.people.map((p) => ({ email: p.email, until: fmtUntil(p.until) })), ["email", "until"])}`;
12531
+ const changes = [
12532
+ ...(r.added ?? []).map((e) => `${lavender("+")} ${e}`),
12533
+ ...(r.removed ?? []).map((e) => `${lavender("\u2212")} ${e}`),
12534
+ ...(r.skipped ?? []).map((s2) => dim(`\xB7 ${s2.email}: ${serverText(s2.reason)}`))
12535
+ ];
12536
+ return [head + people, ...changes].join("\n");
12537
+ });
12538
+ }
12539
+ default:
12540
+ throw usage(t(`Unknown boards subcommand: ${sub}.`, `Subcomando de boards desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty boards ls | create | assign | move-cards | access | rm");
12541
+ }
12542
+ }
12543
+
12544
+ // src/commands/access.ts
12545
+ async function accessCmd(sub, email, agent, v, context) {
12546
+ const ex = "ghosty access why <email> <agent-id> \xB7 ghosty access why <email> --workspace <ws> [--board <board|main>]";
12547
+ if (sub !== "why") throw usage(t(`Unknown access subcommand: ${sub ?? "(none)"}.`, `Subcomando de access desconocido: ${sub ?? "(ninguno)"}.`), ex);
12548
+ if (!email) throw usage(t("Missing email.", "Falta el correo."), ex);
12549
+ const q = new URLSearchParams({ email });
12550
+ if (agent) q.set("agent", agent);
12551
+ else {
12552
+ const ws = v.workspace ?? context;
12553
+ if (!ws) throw usage(t("Missing agent or --workspace.", "Falta el agente o --workspace."), ex);
12554
+ q.set("workspace", ws);
12555
+ q.set("board", v.board ?? "main");
12556
+ }
12557
+ const r = await api(`/api/v2/access/why?${q}`);
12558
+ const what = r.target.kind === "agent" ? `${r.target.name ?? r.target.id}` : `${t("board", "tablero")} \xAB${r.target.name}\xBB (${r.target.workspace})`;
12559
+ return out(r, () => {
12560
+ const head = `${bold(r.email)} \u2192 ${what}: ${r.access ? lavender(r.access) : t("no access", "sin acceso")}`;
12561
+ if (!r.lines.length) return `${head}
12562
+ ${dim(serverText(r.note ?? t("No reason found.", "Ninguna raz\xF3n.")))}`;
12563
+ return `${head}
12564
+ ${table(r.lines.map((l) => ({ via: l.via, detail: serverText(l.detail), until: fmtUntil(l.until) })), ["via", "detail", "until"])}`;
12565
+ });
12566
+ }
12567
+
12568
+ // src/help.es.ts
12569
+ var HELPS_ES = {
12570
+ agents: {
12571
+ summary: "Crea, revisa, cambia, diagnostica y borra agentes.",
12572
+ replace: [
12573
+ ["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
12574
+ ["# its look", "# su apariencia"],
12575
+ ["# its theme", "# su tema"],
12576
+ ["# export the theme as JSON (edit it, apply it back with --style-file)", "# exporta el tema en JSON (ed\xEDtalo y vuelve a aplicarlo con --style-file)"],
12577
+ ["--style-file theme.json", "--style-file tema.json"],
12578
+ ["# what it says while it works", "# lo que dice mientras trabaja"],
12579
+ ["--copy-file texts.json", "--copy-file textos.json"],
12580
+ ["# who is @build in Teams (that workspace, or all of yours)", "# qui\xE9n es @build en Teams (en ese espacio, o en todos los tuyos)"],
12581
+ ["# change one piece of the prompt, on the server's current one", "# cambia un pedazo del prompt, sobre el actual del servidor"],
12582
+ ["# your account: merge copies with the same content", "# tu cuenta: une las copias con el mismo contenido"],
12583
+ ["# staff: their agents, latest turns, plan and boxes", "# staff: sus agentes, \xFAltimos turnos, plan y cajas"],
12584
+ ["# what it really has: effective toolsets, connectors connected + granted", "# lo que de verdad tiene: toolsets efectivos, conectores conectados + concedidos"],
12585
+ ["# Teams workspaces you administer: where it answers, is paused, and with which @handle", "# los equipos de Teams que administras: d\xF3nde contesta, d\xF3nde est\xE1 en pausa y con qu\xE9 @handle"],
12586
+ ["# turn it on there (or change its @handle)", "# lo prende ah\xED (o le cambia el @handle)"],
12587
+ ["# pause it there; it keeps its @handle", "# lo pausa ah\xED; conserva su @handle"],
12588
+ ["# archive: stops answering everywhere, hidden, deleted after 7 days unless restored", "# archivar: deja de contestar en todos lados, se oculta y se borra a los 7 d\xEDas si no lo restauras"],
12589
+ ["# archived agents and when they get deleted", "# los archivados y cu\xE1ndo se borran"],
12590
+ ["# --json: model and workspace on every row", "# --json: modelo y espacio en cada fila"],
12591
+ ["# also: model, effort, native subagents, answers /c, box size (staff)", "# tambi\xE9n: modelo, effort, subagentes nativos, si atiende /c, tama\xF1o de caja (staff)"],
12592
+ ["# remove it from there; the agent stays", "# lo quita de ah\xED; el agente sigue"],
12593
+ ["# web widget: chat bubble on your site, only on those domains", "# widget web: burbuja de chat en tu sitio, s\xF3lo en esos dominios"],
12594
+ ["# state + the <script> to paste", "# su estado + el <script> para pegar"]
12595
+ ],
12596
+ flags: [
12597
+ ["--fields a,b", "get: regresa s\xF3lo estos campos"],
12598
+ ["--prompt-out FILE", "get: escribe el prompt en FILE (ed\xEDtalo y vuelve con set --prompt-file)"],
12599
+ ["--like AGENT", "create: copia la configuraci\xF3n de ese agente (skills y sus switches, toolsets, conectores, voz, bases de datos, documentos, tablero, compartidos) \u2014 no su prompt, env ni canales. docs grant: los mismos documentos"],
12600
+ ["--copy-dbs", "create --like: duplica sus bases de datos con sus filas en vez de conceder las mismas"],
12601
+ ["--board B", "create --like: same (el tablero del origen, default) \xB7 main \xB7 new (un tablero con el nombre del agente y las columnas del origen: un tablero por n\xFAmero) \xB7 o un tablero existente"],
12602
+ ["--no-notify", "create --like: lo comparte con las mismas personas SIN mandarles correo"],
12603
+ ["--workspace SLUG", "create: nace en ese espacio (del que eres due\xF1o o admin), a nombre de su due\xF1o y activo en su Teams"],
12604
+ ["--channels k=on|off,\u2026", "create/set: prende/apaga canales: teams, web, whatsapp, messenger, wa (WhatsApp personal)"],
12605
+ ["--engine E --dry-run", "set: qu\xE9 hace cambiar de motor (sesiones que se olvidan, caja que se levanta y cu\xE1l se destruye, si est\xE1 trabajando) sin cambiarlo; cada hilo conserva su conversaci\xF3n"],
12606
+ ["--toolsets LIST", "set: grupos de tools de gs que recibe (crm, sales\u2026): una lista reemplaza; +x / -x agrega o quita"],
12607
+ ["--prompt-base ID", "set: el prompt de este agente = el prompt de ese agente + --prompt-overlay; se regenera cuando cambia la base. none = desliga (conserva el prompt de hoy)"],
12608
+ ["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, aplicados en orden; cada uno debe encontrar su lugar o no cambia nada. {"edits":[]} = espejo exacto: un solo prompt y una sola memoria para los dos'],
12609
+ ["--prompt-patch FILE", 'set: el mismo formato {"edits":[\u2026]}, aplicado UNA vez sobre el prompt actual del agente en el servidor (no sobre una copia local: nunca pisa el cambio de alguien m\xE1s). Si una edici\xF3n no encuentra su lugar, no cambia nada. --dry-run imprime el diff'],
12610
+ ["--prompt-append FILE", "set: agrega ese texto al final del prompt (con --prompt-patch tambi\xE9n: primero las ediciones, luego el texto)"],
12611
+ ["--voice E:ID", "set: voz de las notas de voz: elevenlabs:<voiceId> o kokoro:em_santa|em_alex|ef_dora"],
12612
+ ["--voice-replies M", "set: auto = contesta con nota de voz cuando el cliente manda o pide audio; off = siempre texto"],
12613
+ ["--auto-stage on|off", "set: mueve solo las tarjetas del tablero tras cada turno del cliente (una llamada extra al modelo por turno)"],
12614
+ ["--quote k=v,\u2026", "set: config de la tool de casa `cotizar` \u2014 iva (0-100), iva-incluido=true (los precios del cat\xE1logo ya traen IVA), iva.CATEGORIA=16 (IVA de esa categor\xEDa del cat\xE1logo; iva.CATEGORIA= la quita), solo-factura=true (IVA s\xF3lo si el cliente pide factura), vigencia (d\xEDas), prefix (folio), moneda, empresa, rfc, telefono, correo, direccion, notas, color. Vac\xEDo lo borra (rfc=). El contador de folio no se toca"],
12615
+ ["--catalog FILE", "set: el cat\xE1logo de la tool de casa `cotizar` desde un CSV (sku,nombre,precio[,unidad,categoria,descripcion]) o una lista JSON; reemplaza al anterior. Su `categoria` decide las reglas de IVA"],
12616
+ ["--verbose", "doctor: lista tambi\xE9n las revisiones que pasaron"],
12617
+ ["--user EMAIL", "doctor (staff de Ghosty): los agentes de esa persona \u2014 doctor, \xFAltimos turnos (estado, segundos, error y \xABfetched\xBB: si su app pidi\xF3 el hilo despu\xE9s de que termin\xF3 el turno), plan/bolsa y cajas (marca duplicadas); queda en bit\xE1cora"],
12618
+ ["--turns N", "doctor --user: cu\xE1ntos turnos recientes por agente (default 8, m\xE1x. 50)"],
12619
+ ["--write", "docs grant: el agente puede reemplazar el documento (default: s\xF3lo lectura)"],
12620
+ ["--no-deliver", "docs grant/add: puede leerlo pero nunca mand\xE1rselo a quien escribe"],
12621
+ ["--url URL | --file PATH", "docs add: de d\xF3nde sale el documento (URL p\xFAblica, hasta 25 MB, o un archivo local)"],
12622
+ ["--grant read|write|no", "docs add: acceso de este agente (default read; no = s\xF3lo subirlo a la cuenta)"],
12623
+ ["--note TEXT", "docs replace/restore: por qu\xE9 cambi\xF3 (sale en docs history)"],
12624
+ ["--role R", "share: editor (lo configura y lo usa) o admin (adem\xE1s lo comparte); repetirlo sobre un correo ya compartido cambia el rol"],
12625
+ ["--handle NAME", "teams on: su @handle en ese equipo (default: sacado de su nombre, con sufijo si est\xE1 ocupado; @ghosty est\xE1 reservado). Sobre un equipo donde ya est\xE1, le cambia el handle. ls: qui\xE9n contesta a ese @handle"],
12626
+ ["--factory ROLES", "teams on: le pasa esos roles de la Software Factory del equipo (plan, build, check, eval; separados por comas) \u2014 lo mismo que \xABEquipo\xBB en /factory. Cada rol conserva su nombre y avatar; su siguiente turno ya corre en \xE9l. La f\xE1brica tiene que estar instalada ah\xED"],
12627
+ ["--yes", "rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
12628
+ ["--slug NAME", "demo: nombre de la liga; se le agrega un c\xF3digo de 6 caracteres (/p/NAME-CODE)"],
12629
+ ["--expires DATE|no", 'demo: caducidad fija en la hora de tu m\xE1quina ("2026-10-15T20:00"); no = nunca'],
12630
+ ["--turns N", "demo: mensajes por visitante (default 150)"],
12631
+ ["--per-ip N", "demo: mensajes por IP al d\xEDa (default 30)"],
12632
+ ["--welcome TEXT", "demo: saludo del chat (vac\xEDo = el de siempre)"],
12633
+ ["--starter TEXT", 'demo: primera sugerencia destacada ("" la quita)'],
12634
+ ['--chips "a|b|c"', "demo: sugerencias, separadas por |"],
12635
+ ["--sponsor on|off", "demo: las copias que la gente se queda corren con TU llave del motor"],
12636
+ ["--rotate", "demo: c\xF3digo nuevo; la liga vieja deja de servir"],
12637
+ ["--domains a.com,*.b.com", "embed: d\xF3nde puede cargarse el widget (vac\xEDo = en ning\xFAn lado)"],
12638
+ ["--welcome TEXT", "embed: el primer mensaje que ve el visitante (vac\xEDo = \xAB\xA1Hola! \xBFEn qu\xE9 te ayudo?\xBB)"],
12639
+ ["--title T / --subtitle T", "embed: nombre en el encabezado del widget (default: el del agente) y un rengl\xF3n chico debajo (\xABResponde en minutos\xBB)"],
12640
+ ["--avatar URL|none", "embed: tu imagen (https JPEG/PNG/WebP, se rehospeda en img.ghosty.studio); none = el fantasma del agente"],
12641
+ ["--icon chat|avatar|ghost", "embed: qu\xE9 lleva el bot\xF3n flotante"],
12642
+ ["--placeholder T", "embed: el texto gris de la caja para escribir"],
12643
+ ["--position right|left / --offset X[,Y]", "embed: esquina de la p\xE1gina y separaci\xF3n en px del lado y de abajo (0-200, default 20)"],
12644
+ ["--theme light|dark|auto", "embed: tema del panel del chat (auto = el del visitante)"],
12645
+ ["--bubble-text T", "embed: etiqueta junto al bot\xF3n (\xAB\xBFDudas?\xBB)"],
12646
+ ["--auto-open S|off", "embed: se abre solo a los S segundos (1-120), una vez por visita, nunca en el tel\xE9fono"],
12647
+ ['--starters "a|b|c"', 'embed: hasta 4 sugerencias TUYAS ("" las quita; nunca salen las de Ghosty)'],
12648
+ ["--style '{json}'|none", "embed: estilo fino, s\xF3lo llaves conocidas: fontFamily, radius, headerBg, headerText, titleUppercase, statusDot, headerAvatar, panelBg, userBubbleBg/Text, agentBubbleBg/Text, inputBg, sendBg, sendIcon, sendArrow (up|right), launcherBg, launcherIcon, typing (bounce|flow|flashing|shimmer), typingColor. PATCH por llave (null resetea una); none resetea todo"],
12649
+ ["--font NAME / --radius 0-24", "embed: atajos de style.fontFamily (lista de Google Fonts: Barlow, Inter, Poppins\u2026) y style.radius"],
12650
+ ["--typing STYLE / --typing-color #rrggbb|none", "embed: el indicador de \xABescribiendo\xBB mientras responde: bounce (el de siempre), flow, flashing o shimmer (\xABPensando\u2026\xBB con brillo), y su color (por defecto, el de la marca)"],
12651
+ ["--theme-source custom|teams|site", "embed: importa el tema de la marca de tu espacio de Ghosty Teams (--workspace SLUG para elegir) o de tu sitio (--site-url); queda editable"],
12652
+ ["--site-url URL", "embed: el sitio del que se sacan los colores (implica --theme-source site)"],
12653
+ ["--style-file FILE", "embed: aplica un tema exportado con `agents embed <id> theme` (o s\xF3lo el objeto style)"],
12654
+ ["--copy '{json}'|none", "embed: las frases mientras el agente trabaja, texto plano \u2264 80 car.: waking, connecting, thinking (lista de 1 a 8 que rotan), stillWorking, cantStart, interrupted, retryLabel, showDuration, showCopyButton (true|false). PATCH por llave (null = la frase de Ghosty); none las regresa todas"],
12655
+ ["--copy-file FILE", "embed: aplica el objeto copy desde un archivo (o el `copy` de un tema exportado)"],
12656
+ ["--out FILE.tsx|.jsx [--force]", "embed: escribe en tu proyecto un componente de React COMPLETO y editable (sin depender de ning\xFAn paquete) con tu token y tu tema horneados arriba; no pisa un archivo que ya existe sin --force"],
12657
+ ["--check PATH", "box: este archivo debe existir en la m\xE1quina (repetible), p. ej. un archivo que trae la imagen nueva"],
12658
+ ["--contains PATH=TEXT", "box: ese archivo debe contener TEXT (un cambio que no agrega archivos)"],
12659
+ ["--recycle", "box: reemplaza sus m\xE1quinas (nunca a media respuesta); pool: la nueva nace en el siguiente turno; ACP (goose, lite) igual, con el historial del \xFAltimo respaldo"],
12660
+ ["--start", "box: levanta (o despierta) su caja ya, sin gastar un turno; dentro de las cajas del plan (503 si el espacio est\xE1 lleno)"],
12661
+ ["--reenv / --force", "box --recycle en ACP: --reenv s\xF3lo re-hornea el env en la misma caja; --force la recrea aunque est\xE9 ocupada (due\xF1o o staff)"],
12662
+ ["--unset-env K1,K2", "set: borra esas variables del env (--env K= tambi\xE9n borra K)"],
12663
+ ["--tool [ch:]tool=on|off", "set: prende/apaga una tool en un canal (wa:actualizar_identidad=off) o en todos (crm_buscar=off); separadas por comas"],
12664
+ ["--bag NAME|none", "set: de qu\xE9 bolsa de tokens de su espacio gasta; none = el saldo del espacio (s\xF3lo staff)"],
12665
+ ["--box MB[:N]", "set: RAM de su caja (1024, 2048, 4096, 8192) y conversaciones a la vez, p. ej. 4096:10; recicla sus cajas (s\xF3lo staff)"],
12666
+ ["--avatar URL|FILE", "set: su cara en Teams: una liga https o un JPEG/PNG/WebP local (se guarda en img.ghosty.studio)"],
12667
+ ["--from-workspace", "rm: lo desactiva en cada Teams donde est\xE9 y luego lo borra (due\xF1o o staff)"],
12668
+ ["--prompt-mode M", "set: append (default: su prompt + la persona de Ghosty) o replace (s\xF3lo su prompt)"],
12669
+ ["--sponsor/--owner EMAIL", "ls: agentes de CUALQUIER cuenta que paga / de quien es ese correo (staff de Ghosty); luego se operan por id"],
12670
+ ["--ids", "ls: s\xF3lo los ids, uno por rengl\xF3n (para xargs)"],
12671
+ ["--user EMAIL --app A", "ls (staff de Ghosty): la lista de esa persona tal como la recibe su app (tono, espacio, \xFAltimo uso) y sus tel\xE9fonos con la build de la app; A = all|android|ios|desktop"]
12672
+ ],
12673
+ notes: [
12674
+ "doctor sale con 1 cuando encuentra un problema, as\xED puede frenar un script o un CI.",
12675
+ "Un prompt derivado no se edita directo (409): cambia la base o el overlay, o desenl\xE1zalo con --prompt-base none.",
12676
+ "demo sin flags s\xF3lo imprime su estado: liga, caducidad, topes y n\xFAmeros."
12677
+ ]
12678
+ },
12679
+ skills: {
12680
+ summary: "Skills de un agente (formato agentskills.io: una carpeta con SKILL.md).",
12681
+ replace: [
12234
12682
  ["# from the community catalog", "# del cat\xE1logo de la comunidad"],
12235
12683
  ["# keeps a local copy first", "# antes guarda una copia local"]
12236
12684
  ],
@@ -12340,6 +12788,11 @@ var HELPS_ES = {
12340
12788
  replace: [['"what to do"', '"qu\xE9 hacer"'], ["# answers in that thread as if mentioned", "# contesta en ese hilo como si lo mencionaran"]],
12341
12789
  notes: ["--teams: el agente tiene que estar activo en el Teams de ese espacio y t\xFA ser miembro. El id del hilo es el del mensaje con que empieza.", "--conversation waba:\u2026 | wa:<jid> | messenger:\u2026: la respuesta sale por ese canal (WABA s\xF3lo dentro de 24 h; WhatsApp personal si el n\xFAmero est\xE1 conectado). \xABOK\xBB nunca se manda."]
12342
12790
  },
12791
+ teams: {
12792
+ summary: "Escribe en un room o hilo de Teams a tu nombre; el @agente que menciones contesta, como en el composer.",
12793
+ replace: [['"message"', '"mensaje"'], ["# new thread", "# hilo nuevo"], ["make Deploy wait for ci with needs", "que el Deploy espere al ci con needs"], ["how is it going?", "\xBFen qu\xE9 va?"]],
12794
+ notes: ["Tienes que ser miembro del Teams de ese espacio (o staff que entr\xF3). Tu sesi\xF3n, nunca el token de un agente (gat_).", "Sin hilo abre uno nuevo, como el bot\xF3n \xABPedir\xBB. El id del hilo es el del mensaje con que empieza (en su liga: ?thread=).", "--wait pregunta hasta que cada agente mencionado termina e imprime sus respuestas; sale con 1 si se acaba --timeout (segundos).", "Hasta 30 mensajes por hora por persona. Cada uno queda en la bit\xE1cora: ghosty audit ls --action teams.post"]
12795
+ },
12343
12796
  browser: {
12344
12797
  summary: "Ghosty para Chrome: cerrar la sesi\xF3n del navegador.",
12345
12798
  notes: ["Revoca todos los tokens de Ghosty para Chrome que tengan tus agentes de terminal (Claude Code, CLI, @ghostystudio/browser-mcp) y desconecta la extensi\xF3n. Para volver, copia un comando nuevo del panel de la extensi\xF3n."]
@@ -12474,6 +12927,10 @@ var HELPS_ES = {
12474
12927
  summary: "Los tableros de ventas de un ESPACIO: listarlos, crear uno (copiando las columnas de otro), elegir a d\xF3nde manda sus tarjetas un agente y mover tarjetas entre tableros.",
12475
12928
  replace: [['"Name"', '"Nombre"']],
12476
12929
  flags: [
12930
+ ["--add / --remove EMAIL", "access: agrega o quita de la lista \xABs\xF3lo estas personas\xBB del tablero (repetible o separado por comas; s\xF3lo miembros del espacio)"],
12931
+ ["--restrict", "access: vuelve de lista un tablero que ve todo el espacio (con quienes agregues con --add)"],
12932
+ ["--everyone", "access: regresa a \xABlo ve todo el espacio\xBB"],
12933
+ ["--until D", "access: temporal \u2014 se retira solo en esa fecha (fin del d\xEDa, CDMX) o tras 14d / 12h"],
12477
12934
  ["--columns-from B", "create: copia las columnas de ese tablero (main, un nombre o un id) en vez de las de siempre"],
12478
12935
  ["--from / --to B", "move-cards: tableros por nombre, id o main"],
12479
12936
  ["--integration ID | --phone N | --all", "move-cards: qu\xE9 tarjetas (el filtro es obligatorio)"],
@@ -12483,7 +12940,8 @@ var HELPS_ES = {
12483
12940
  notes: [
12484
12941
  "assign cambia a d\xF3nde van las tarjetas NUEVAS; move-cards trae las que ya existen (hilo, pedidos, actividad) con folio nuevo. Una tarjeta cuya conversaci\xF3n ya est\xE1 en el tablero destino se salta.",
12485
12942
  "Leer requiere ser miembro; cambiar requiere ser due\xF1o o staff del espacio.",
12486
- "rm s\xF3lo borra un tablero vac\xEDo que no sea el principal."
12943
+ "rm s\xF3lo borra un tablero vac\xEDo que no sea el principal.",
12944
+ "access sin flags dice qui\xE9n ve el tablero; due\xF1o y staff ven todos. Por qu\xE9 alguien lo ve: ghosty access why <correo> --workspace <espacio> --board <tablero>."
12487
12945
  ]
12488
12946
  },
12489
12947
  cards: {
@@ -12592,11 +13050,21 @@ var HELPS_ES = {
12592
13050
  "Errores de slug: slug_invalid, slug_taken, slug_unsupported (exit 1, no cambia nada)."
12593
13051
  ]
12594
13052
  },
13053
+ access: {
13054
+ summary: "Por qu\xE9 alguien ve un agente o un tablero: todas las razones, no s\xF3lo la que gana (due\xF1o, asiento en el espacio, compartido, staff asignado, la lista del tablero) y cu\xE1ndo vence lo temporal.",
13055
+ flags: [
13056
+ ["--workspace W", "el espacio del tablero (o ghosty use <espacio>)"],
13057
+ ["--board B", "tablero por nombre, id o main (default)"]
13058
+ ],
13059
+ notes: ["Lo pregunta quien administra lo preguntado: el due\xF1o del agente o del espacio, un admin del espacio, o staff.", "Dar o quitar acceso: ghosty spaces members add|rm, ghosty boards access."]
13060
+ },
12595
13061
  spaces: {
12596
13062
  summary: "Espacios: crea uno a tu nombre (prueba), lista los tuyos (o, staff, los de cualquiera) y operaciones del staff de Ghosty \u2014 entrar/salir del espacio de un cliente, cambiar su combo, mover \xABpagado hasta\xBB, renombrar su slug, cambiar de due\xF1o \u2014 y, due\xF1o o staff, archivarlo o restaurarlo. Cada cambio queda en la bit\xE1cora del espacio.",
12597
13063
  flags: [
12598
13064
  ["--user EMAIL", "ls (staff): los espacios de esa persona"],
12599
- ["--combo C", "set (staff): teams | sales | both \u2014 sin Ventas sus tableros existen pero nadie los ve; create: distinto de teams, s\xF3lo staff"],
13065
+ ["--role R", "members add: member (default: tableros + editor de los agentes del espacio, sin llaves, cobro ni traspasos) | admin (adem\xE1s invita y quita gente)"],
13066
+ ["--until D", "members add: asiento temporal que se retira solo en esa fecha (fin del d\xEDa, CDMX) o tras 14d / 12h \u2014 cuentas de revisor (Meta, Google, Apple). A quien ya est\xE1, le pone o mueve la fecha"],
13067
+ ["--combo C", "set (staff): teams | sales | both | none (ni Teams ni Ventas) \u2014 sin Ventas sus tableros existen pero nadie los ve; create: distinto de teams, s\xF3lo staff"],
12600
13068
  ["--model-cap X", "set (staff): el modelo m\xE1s caro que puede elegir el espacio \u2014 0.25 econ\xF3micos, 1 est\xE1ndar (Sonnet), 4 todos; none = el de su plan"],
12601
13069
  ["--house-claude on|off", "set (staff): sus agentes corren con la Claude de Ghosty si el due\xF1o no trae llave; recicla sus cajas de Claude"],
12602
13070
  ["--until DATE", "renew: el nuevo \xABpagado hasta\xBB (AAAA-MM-DD)"],
@@ -13017,6 +13485,13 @@ var HELPS = {
13017
13485
  notes: ["--teams: the agent must be active in that workspace's Teams and you must be a member. The thread id is the id of the message that starts it.", "--conversation waba:\u2026 | wa:<jid> | messenger:\u2026: the answer goes out through that channel (WABA only within 24 h; personal WhatsApp if the number is connected). \xABOK\xBB is never sent."],
13018
13486
  learn: `${REF}#programar-turnos`
13019
13487
  },
13488
+ teams: {
13489
+ summary: "Write in a Teams room or thread as yourself; any @agent you mention answers, like in the composer.",
13490
+ usage: ['ghosty teams post <workspace>#<room> "message"', 'ghosty teams post <workspace>#<room>/<thread-id> "message" [--wait [--timeout 600]]'],
13491
+ examples: ['ghosty teams post business#mailmask "@plan make Deploy wait for ci with needs" # new thread', 'ghosty teams post business#mailmask/5612 "@plan how is it going?" --wait'],
13492
+ notes: ["You must be a member of that workspace's Teams (or staff who joined). Your session, never an agent token (gat_).", "No thread = a new thread, like the \xABPedir\xBB button. The thread id is the id of the message that starts it (in its link: ?thread=).", "--wait polls until every mentioned agent finished and prints their answers; exit 1 if --timeout (seconds) runs out.", "Up to 30 messages per hour per person. Each one is in the audit log: ghosty audit ls --action teams.post"],
13493
+ learn: `${REF}#teams`
13494
+ },
13020
13495
  browser: {
13021
13496
  summary: "Ghosty for Chrome: sign the browser out.",
13022
13497
  usage: ["ghosty browser logout [--yes]"],
@@ -13215,9 +13690,14 @@ HELPS.boards = {
13215
13690
  'ghosty boards create <workspace> "Name" [--columns-from main|<board>]',
13216
13691
  "ghosty boards assign <agent-id> <board|main>",
13217
13692
  "ghosty boards move-cards <workspace> --from <board> --to <board> [--integration ID | --phone N | --all] [--dry-run] [--yes]",
13218
- 'ghosty boards rm <workspace> "Name" [--yes]'
13693
+ 'ghosty boards rm <workspace> "Name" [--yes]',
13694
+ "ghosty boards access <workspace> [board|main] [--add EMAIL] [--remove EMAIL] [--restrict] [--everyone] [--until 2026-10-28|14d]"
13219
13695
  ],
13220
13696
  flags: [
13697
+ ["--add / --remove EMAIL", "access: add to or take off the board's \xABonly these people\xBB list (repeatable or comma-separated; only workspace members)"],
13698
+ ["--restrict", "access: turn a whole-workspace board into a list (with the people you --add)"],
13699
+ ["--everyone", "access: back to \xABthe whole workspace sees it\xBB"],
13700
+ ["--until D", "access: temporary \u2014 removed automatically on that date (end of day, CDMX) or after 14d / 12h"],
13221
13701
  ["--columns-from B", "create: copy the columns of that board (main, a name or an id) instead of the default ones"],
13222
13702
  ["--from / --to B", "move-cards: boards by name, id or main"],
13223
13703
  ["--integration ID | --phone N | --all", "move-cards: which cards (a filter is required)"],
@@ -13225,7 +13705,7 @@ HELPS.boards = {
13225
13705
  ["--yes", "move-cards: skip the confirmation (required without a terminal)"]
13226
13706
  ],
13227
13707
  examples: ["ghosty boards ls siiqtec", 'ghosty boards create siiqtec "Siiqtec - Ixmiquilpan" --columns-from main', 'ghosty boards assign <agent-id> "Siiqtec - Ixmiquilpan"', 'ghosty boards move-cards siiqtec --from main --to "Siiqtec - Ixmiquilpan" --integration 6a3b02ad\u2026 --dry-run'],
13228
- notes: ["assign changes where NEW cards go; move-cards brings the existing ones (thread, orders, activity) with a new folio. A card whose conversation is already on the target board is skipped.", "Reading needs membership; changing needs owner or staff of the workspace.", "rm only deletes an empty board that is not the main one."],
13708
+ notes: ["assign changes where NEW cards go; move-cards brings the existing ones (thread, orders, activity) with a new folio. A card whose conversation is already on the target board is skipped.", "Reading needs membership; changing needs owner or staff of the workspace.", "rm only deletes an empty board that is not the main one.", "access without flags shows who sees the board; owner and staff always see every board. Why someone sees it: ghosty access why <email> --workspace <ws> --board <board>."],
13229
13709
  learn: `${REF}#tablero`
13230
13710
  };
13231
13711
  HELPS.cards = {
@@ -13363,10 +13843,12 @@ HELPS.novedades = {
13363
13843
  };
13364
13844
  HELPS.spaces = {
13365
13845
  summary: "Workspaces: create one of your own (trial), list yours (or, staff, anyone's), and Ghosty staff operations \u2014 join/leave a client's workspace, change its combo, move \xABpaid until\xBB, rename its slug, change its owner \u2014 and, owner or staff, archive or restore it. Each change is logged in the workspace's audit.",
13366
- usage: ["ghosty spaces ls [--user EMAIL]", "ghosty spaces create <slug> [--combo teams|sales|both] [--dry-run]", "ghosty spaces join|leave <workspace>", "ghosty spaces set <workspace> --combo teams|sales|both | --model-cap 0.25|1|4|none | --house-claude on|off [--dry-run]", "ghosty spaces renew <workspace> --until YYYY-MM-DD [--note TEXT] [--dry-run] [--yes]", "ghosty spaces rename <old> <new> [--dry-run] [--yes]", "ghosty spaces archive|restore <workspace> [--dry-run] [--yes]", "ghosty spaces transfer <workspace> --to EMAIL [--keep-as admin|member|none] [--copy-keys] [--dry-run] [--yes]", "ghosty spaces log <workspace> [--since 30d] [--action workspace] [--limit N]"],
13846
+ usage: ["ghosty spaces ls [--user EMAIL]", "ghosty spaces create <slug> [--combo teams|sales|both|none] [--dry-run]", "ghosty spaces join|leave <workspace>", "ghosty spaces set <workspace> --combo teams|sales|both|none | --model-cap 0.25|1|4|none | --house-claude on|off [--dry-run]", "ghosty spaces renew <workspace> --until YYYY-MM-DD [--note TEXT] [--dry-run] [--yes]", "ghosty spaces rename <old> <new> [--dry-run] [--yes]", "ghosty spaces archive|restore <workspace> [--dry-run] [--yes]", "ghosty spaces transfer <workspace> --to EMAIL [--keep-as admin|member|none] [--copy-keys] [--dry-run] [--yes]", "ghosty spaces log <workspace> [--since 30d] [--action workspace] [--limit N]", "ghosty spaces members ls|add|rm <workspace> [email,\u2026] [--role member|admin] [--until 2026-10-28|14d] [--yes]"],
13367
13847
  flags: [
13368
13848
  ["--user EMAIL", "ls (staff): that person's workspaces"],
13369
- ["--combo C", "set (staff): teams | sales | both \u2014 without Sales its boards exist but nobody sees them; create: other than teams, staff only"],
13849
+ ["--role R", "members add: member (default: boards + editor of the workspace's agents, no keys, billing or transfers) | admin (also invites and removes people)"],
13850
+ ["--until D", "members add: temporary seat, removed automatically on that date (end of day, CDMX) or after 14d / 12h \u2014 reviewer accounts (Meta, Google, Apple). On someone already in, it sets or moves the date"],
13851
+ ["--combo C", "set (staff): teams | sales | both | none (neither Teams nor Sales) \u2014 without Sales its boards exist but nobody sees them; create: other than teams, staff only"],
13370
13852
  ["--model-cap X", "set (staff): the most expensive model the workspace can pick \u2014 0.25 economy, 1 standard (Sonnet), 4 everything; none = its plan's"],
13371
13853
  ["--house-claude on|off", "set (staff): its agents run on Ghosty's Claude when the owner brings no key; recycles their Claude boxes"],
13372
13854
  ["--until DATE", "renew: the new \xABpaid until\xBB (YYYY-MM-DD)"],
@@ -13378,11 +13860,22 @@ HELPS.spaces = {
13378
13860
  ["-n, --dry-run", "show what changes, change nothing"],
13379
13861
  ["--yes", "skip the confirmation (required without a terminal)"]
13380
13862
  ],
13381
- examples: ["ghosty spaces ls", "ghosty spaces create mailmask --dry-run", "ghosty spaces ls --user brenda@siiqtec.com", "ghosty spaces join denik", "ghosty spaces set denik --combo sales --dry-run", "ghosty spaces set mailmask --model-cap 4 --house-claude on", 'ghosty spaces renew descti --until 2026-11-01 --note "pag\xF3 octubre"', "ghosty spaces rename totequim siiqtec --dry-run", "ghosty spaces archive box-xfer-test --dry-run", "ghosty spaces transfer siiqtec --to siiqtec@gmail.com --keep-as admin --copy-keys --dry-run", "ghosty spaces log siiqtec --since 30d"],
13863
+ examples: ["ghosty spaces ls", "ghosty spaces create mailmask --dry-run", "ghosty spaces ls --user brenda@siiqtec.com", "ghosty spaces join denik", "ghosty spaces set denik --combo sales --dry-run", "ghosty spaces set mailmask --model-cap 4 --house-claude on", 'ghosty spaces renew descti --until 2026-11-01 --note "pag\xF3 octubre"', "ghosty spaces rename totequim siiqtec --dry-run", "ghosty spaces archive box-xfer-test --dry-run", "ghosty spaces transfer siiqtec --to siiqtec@gmail.com --keep-as admin --copy-keys --dry-run", "ghosty spaces log siiqtec --since 30d", "ghosty spaces members add business chrome-review@ghosty.studio --until 14d", "ghosty spaces members rm business chrome-review@ghosty.studio --yes"],
13382
13864
  notes: ["create follows the same rules as the web: you own it, it starts as a 30-day trial, your email must be confirmed, and your plan caps how many you own. Next: ghosty partner enable <slug>.", "With a Stripe subscription renew refuses: the paid invoice moves the date.", "rename changes only the slug (id and namespace stay, so board, Stripe and WhatsApp keep working); old <slug>.sales links stop working.", "archive does what Settings \u2192 Archive does: the workspace leaves /app and spaces ls, its agents and data stay; it asks you to type the slug. Undo: ghosty spaces restore <slug>. Logged as workspace.archive / workspace.restore.", "transfer moves the agents with the workspace: ACP boxes move to the new owner with their /data, pool boxes are recycled (the owner is baked into the box); without --copy-keys the dry run lists which keys the new owner lacks \u2014 those agents would stop answering."],
13383
13865
  learn: `${REF}#espacios`
13384
13866
  };
13385
13867
  HELPS.workspaces = HELPS.spaces;
13868
+ HELPS.access = {
13869
+ summary: "Why someone sees an agent or a board: every reason, not only the winning one (owner, workspace seat, share, assigned staff, the board's list) and when temporary access expires.",
13870
+ usage: ["ghosty access why <email> <agent-id>", "ghosty access why <email> --workspace <workspace> [--board <board|main>]"],
13871
+ flags: [
13872
+ ["--workspace W", "the board's workspace (or ghosty use <workspace>)"],
13873
+ ["--board B", "board by name, id or main (default)"]
13874
+ ],
13875
+ examples: ["ghosty access why chrome-review@ghosty.studio cmu7pgbyr0001gee3ruk172mm", 'ghosty access why ana@siiqtec.com --workspace siiqtec --board "Siiqtec - Ixmiquilpan"'],
13876
+ notes: ["Asked by whoever administers the target: the agent's or workspace's owner, a workspace admin, or staff.", "Give or take access: ghosty spaces members add|rm, ghosty boards access."],
13877
+ learn: `${REF}#acceso`
13878
+ };
13386
13879
  HELPS.sites = {
13387
13880
  summary: "Sites your agents publish at <slug>.ghosty.page. On the Free plan a site PAUSES after 30 days without changes (nothing is deleted; it comes back as-is when pinned). \xABPinned sites\xBB ($49 USD/yr or $5 USD/mo \u2014 $990 / $99 MXN \u2014 includes up to 3 sites; add 3 more for the same price) never pause and carry no \xABMade with Ghosty\xBB banner. Staff also publish Ghosty's own house sites and change any site's slug.",
13388
13881
  usage: [
@@ -13503,689 +13996,379 @@ function helpText(cmd) {
13503
13996
  h.summary,
13504
13997
  "",
13505
13998
  ...section(es ? "USO" : "USAGE", h.usage.map(fix)),
13506
- ...h.flags?.length ? section(es ? "OPCIONES" : "FLAGS", h.flags.map(([f, d]) => `${f.padEnd(w)}${d}`)) : [],
13507
- ...section(es ? "EJEMPLOS" : "EXAMPLES", h.examples.map((e) => `$ ${fix(e)}`)),
13508
- ...h.notes?.length ? section(es ? "NOTAS" : "NOTES", h.notes) : [],
13509
- bold(es ? "M\xC1S INFORMACI\xD3N" : "LEARN MORE"),
13510
- es ? ` Agrega --json para salida para m\xE1quinas. C\xF3digos de salida: 0 bien \xB7 1 error de la API \xB7 2 uso \xB7 3 sin sesi\xF3n.` : ` Add --json for machine output. Exit codes: 0 ok \xB7 1 API error \xB7 2 usage \xB7 3 not signed in.`,
13511
- ` ${dim(h.learn ?? DOCS)}`
13512
- ].join("\n");
13513
- }
13514
-
13515
- // src/update.ts
13516
- import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
13517
- import { homedir as homedir3 } from "node:os";
13518
- import { dirname as dirname3, join as join7 } from "node:path";
13519
- var FILE = join7(process.env.XDG_CONFIG_HOME || join7(homedir3(), ".config"), "ghosty", "update.json");
13520
- var TTL = 60 * 60 * 1e3;
13521
- var UPGRADE = "npm i -g @ghostystudio/cli@latest";
13522
- function newer(a, b) {
13523
- const pa = a.split(".").map(Number);
13524
- const pb = b.split(".").map(Number);
13525
- for (let i = 0; i < 3; i++) if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) > (pb[i] ?? 0);
13526
- return false;
13527
- }
13528
- async function latestVersion(current2) {
13529
- if (process.env.GHOSTY_NO_UPDATE_CHECK || process.env.CI) return null;
13530
- let cache2 = {};
13531
- try {
13532
- cache2 = JSON.parse(await readFile9(FILE, "utf8"));
13533
- } catch {
13534
- }
13535
- if (current2 && cache2.latest && newer(current2, cache2.latest)) cache2.checkedAt = 0;
13536
- if (!cache2.checkedAt || Date.now() - cache2.checkedAt > TTL) {
13537
- try {
13538
- const r = await fetch("https://registry.npmjs.org/@ghostystudio/cli/latest", { signal: AbortSignal.timeout(1500) });
13539
- const latest = (await r.json()).version;
13540
- if (latest) cache2 = { latest, checkedAt: Date.now() };
13541
- await mkdir5(dirname3(FILE), { recursive: true });
13542
- await writeFile7(FILE, JSON.stringify(cache2));
13543
- } catch {
13544
- }
13545
- }
13546
- return cache2.latest ?? null;
13547
- }
13548
- async function updateNotice(current2) {
13549
- const latest = await latestVersion(current2);
13550
- return latest && newer(latest, current2) ? t(`Update available: ${current2} \u2192 ${latest}. Run: ${UPGRADE}`, `Hay versi\xF3n nueva: ${current2} \u2192 ${latest}. Corre: ${UPGRADE}`) : null;
13551
- }
13552
-
13553
- // src/resolve.ts
13554
- var looksLikeId = (s2) => /^c[a-z0-9]{20,}$/.test(s2) || /^[0-9a-f]{24}$/.test(s2);
13555
- function pickAgent(list, ref, workspace) {
13556
- const want = ref.trim().toLowerCase();
13557
- let hits = list.filter((a) => a.nombre.trim().toLowerCase() === want);
13558
- if (hits.length > 1 && workspace) {
13559
- const here = hits.filter((a) => a.espacio?.nombre === workspace);
13560
- if (here.length) hits = here;
13561
- }
13562
- if (hits.length === 1) return hits[0].id;
13563
- if (!hits.length) {
13564
- const near = list.filter((a) => a.nombre.toLowerCase().includes(want)).slice(0, 5);
13565
- const names2 = near.map((a) => `${a.nombre} (${a.id})`).join(", ");
13566
- throw new CliError(t(`No agent named "${ref}".`, `No hay ning\xFAn agente llamado "${ref}".`), EXIT.USAGE, near.length ? t(`Did you mean: ${names2}?`, `\xBFQuisiste decir: ${names2}?`) : t("List them: ghosty agents ls", "L\xEDstalos: ghosty agents ls"));
13567
- }
13568
- const where = (a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : a.espacio?.tipo ?? "?";
13569
- const ids = hits.map((a) => ` ${a.id} ${a.motor ?? ""} ${where(a)}`).join("\n");
13570
- throw new CliError(t(`${hits.length} agents are named "${ref}".`, `${hits.length} agentes se llaman "${ref}".`), EXIT.USAGE, t(`Use the id:
13571
- ${ids}`, `Usa el id:
13572
- ${ids}`));
13573
- }
13574
- var cache = null;
13575
- function listAgents() {
13576
- cache ??= api("/api/v2/me/agents?client=cli").then((r) => r.agentes);
13577
- return cache;
13578
- }
13579
- async function resolveAgent(ref, workspace) {
13580
- if (!ref || looksLikeId(ref)) return ref;
13581
- return pickAgent(await listAgents(), ref, workspace);
13582
- }
13583
- function agentArgIndex(pos) {
13584
- const [cmd, sub] = pos;
13585
- switch (cmd) {
13586
- case "agents":
13587
- if (sub === "docs" || sub === "documents" || sub === "teams") return 3;
13588
- return sub && !["ls", "list", "create", "new"].includes(sub) ? 2 : null;
13589
- case "files":
13590
- case "skills":
13591
- case "mcp":
13592
- case "dbs":
13593
- case "db":
13594
- return sub === "photos" ? 3 : 2;
13595
- case "turns":
13596
- return pos[1] === "stats" ? null : 2;
13597
- case "board":
13598
- case "conversations":
13599
- case "schedule":
13600
- return 2;
13601
- case "try":
13602
- case "chat":
13603
- return 1;
13604
- case "channels":
13605
- return pos[2] === "groups" ? 4 : 3;
13606
- case "boards":
13607
- return sub === "assign" ? 2 : null;
13608
- case "demos":
13609
- case "demo":
13610
- return sub === "history" ? 2 : null;
13611
- default:
13612
- return null;
13613
- }
13614
- }
13615
-
13616
- // src/completion.ts
13617
- import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
13618
- import { homedir as homedir4 } from "node:os";
13619
- import { join as join8 } from "node:path";
13620
- var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
13621
- var AGENT_FLAGS = /* @__PURE__ */ new Set(["--like", "--agent", "--prompt-base"]);
13622
- var WORKSPACE_FLAGS = /* @__PURE__ */ new Set(["--workspace"]);
13623
- function subcommandsOf(cmd) {
13624
- const subs = /* @__PURE__ */ new Set();
13625
- for (const u of HELPS[cmd]?.usage ?? []) {
13626
- const m = new RegExp(`^ghosty ${cmd} ([a-z][a-z|-]*)`).exec(u);
13627
- if (m) {
13628
- for (const s2 of m[1].split("|")) if (!s2.includes("-") || /^[a-z]+-[a-z]+$/.test(s2)) subs.add(s2);
13629
- }
13630
- }
13631
- return [...subs];
13632
- }
13633
- function flagsOf(cmd) {
13634
- const own = /* @__PURE__ */ new Set();
13635
- for (const [f] of HELPS[cmd]?.flags ?? []) for (const m of f.matchAll(/--[a-z][a-z-]*/g)) own.add(m[0]);
13636
- for (const u of HELPS[cmd]?.usage ?? []) for (const m of u.matchAll(/--[a-z][a-z-]*/g)) own.add(m[0]);
13637
- return [...own, ...GLOBAL_FLAGS];
13638
- }
13639
- function completionFor(words, current2, commands) {
13640
- const prev = words[words.length - 1];
13641
- if (prev && AGENT_FLAGS.has(prev)) return { kind: "agents" };
13642
- if (prev && WORKSPACE_FLAGS.has(prev)) return { kind: "workspaces" };
13643
- if (!words.length) return { kind: "words", words: commands };
13644
- const [cmd, sub] = words;
13645
- if (current2.startsWith("-")) return { kind: "words", words: flagsOf(cmd) };
13646
- const pos = words.filter((w) => !w.startsWith("-"));
13647
- if (cmd === "use" && pos.length === 1) return { kind: "workspaces" };
13648
- if (cmd === "boards" && pos.length === 2 && sub !== "assign") return { kind: "workspaces" };
13649
- if (cmd === "help" && pos.length === 1) return { kind: "words", words: commands };
13650
- if (pos.length === 1 && cmd !== "try" && cmd !== "chat") {
13651
- const subs = subcommandsOf(cmd);
13652
- if (subs.length) return { kind: "words", words: subs };
13653
- }
13654
- if (agentArgIndex([...pos, current2]) === pos.length) return { kind: "agents" };
13655
- return { kind: "words", words: [] };
13656
- }
13657
- var cacheFile = () => join8(process.env.XDG_CACHE_HOME || join8(homedir4(), ".cache"), "ghosty", `complete${process.env.GHOSTY_PROFILE ? `.${process.env.GHOSTY_PROFILE}` : ""}.json`);
13658
- async function names() {
13659
- try {
13660
- const c = JSON.parse(await readFile10(cacheFile(), "utf8"));
13661
- if (Date.now() - c.at < 5 * 6e4) return c;
13662
- } catch {
13663
- }
13664
- const list = await listAgents();
13665
- const agents = [...new Set(list.map((a) => a.nombre))];
13666
- const workspaces = [...new Set(list.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
13667
- await mkdir6(join8(cacheFile(), ".."), { recursive: true }).catch(() => {
13668
- });
13669
- await writeFile8(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
13670
- });
13671
- return { agents, workspaces };
13672
- }
13673
- async function completeCmd(args, commands) {
13674
- const current2 = args.length ? args[args.length - 1] : "";
13675
- const words = args.slice(0, -1);
13676
- const want = completionFor(words, current2, commands);
13677
- let out2 = [];
13678
- try {
13679
- if (want.kind === "words") out2 = want.words;
13680
- else out2 = (await names())[want.kind];
13681
- } catch {
13682
- out2 = [];
13683
- }
13684
- process.stdout.write(out2.filter((w) => w.toLowerCase().startsWith(current2.toLowerCase())).join("\n") + "\n");
13685
- }
13686
- function completionScript(shell) {
13687
- if (shell === "bash") {
13688
- return `# ghosty \u2014 bash completion: eval "$(ghosty completion bash)" en ~/.bashrc
13689
- _ghosty() {
13690
- # Las palabras ANTES de cambiar IFS: el bash 3.2 de macOS junta \${arr[@]:1:n} con un IFS propio.
13691
- local words=("\${COMP_WORDS[@]:1:COMP_CWORD-1}") cur="\${COMP_WORDS[COMP_CWORD]}"
13692
- local IFS=$'\\n'
13693
- COMPREPLY=( $(ghosty __complete -- "\${words[@]}" "$cur" 2>/dev/null) )
13694
- }
13695
- complete -o default -F _ghosty ghosty
13696
- `;
13697
- }
13698
- if (shell === "zsh") {
13699
- return `#compdef ghosty
13700
- # ghosty \u2014 zsh completion: source <(ghosty completion zsh) en ~/.zshrc (despu\xE9s de compinit)
13701
- _ghosty() {
13702
- local -a opts
13703
- opts=("\${(@f)$(ghosty __complete -- "\${(@)words[2,CURRENT-1]}" "\${words[CURRENT]}" 2>/dev/null)}")
13704
- compadd -a opts
13705
- }
13706
- compdef _ghosty ghosty
13707
- `;
13708
- }
13709
- if (shell === "fish") {
13710
- return `# ghosty \u2014 fish completion: ghosty completion fish > ~/.config/fish/completions/ghosty.fish
13711
- complete -c ghosty -f -a "(ghosty __complete -- (commandline -opc)[2..-1] (commandline -ct) 2>/dev/null)"
13712
- `;
13713
- }
13714
- return null;
13999
+ ...h.flags?.length ? section(es ? "OPCIONES" : "FLAGS", h.flags.map(([f, d]) => `${f.padEnd(w)}${d}`)) : [],
14000
+ ...section(es ? "EJEMPLOS" : "EXAMPLES", h.examples.map((e) => `$ ${fix(e)}`)),
14001
+ ...h.notes?.length ? section(es ? "NOTAS" : "NOTES", h.notes) : [],
14002
+ bold(es ? "M\xC1S INFORMACI\xD3N" : "LEARN MORE"),
14003
+ es ? ` Agrega --json para salida para m\xE1quinas. C\xF3digos de salida: 0 bien \xB7 1 error de la API \xB7 2 uso \xB7 3 sin sesi\xF3n.` : ` Add --json for machine output. Exit codes: 0 ok \xB7 1 API error \xB7 2 usage \xB7 3 not signed in.`,
14004
+ ` ${dim(h.learn ?? DOCS)}`
14005
+ ].join("\n");
13715
14006
  }
13716
14007
 
13717
- // src/aliases.ts
13718
- function normalizeArgs(pos, v) {
13719
- const notes = [];
13720
- const [cmd, sub] = pos;
13721
- if (cmd === "cards") pos[0] = "board";
13722
- else if (cmd === "board") notes.push(t("`ghosty board` is now `ghosty cards` (same subcommands).", "`ghosty board` ahora es `ghosty cards` (mismos subcomandos)."));
13723
- if (cmd === "skills") {
13724
- if (sub === "enable") pos[1] = "on";
13725
- else if (sub === "disable") pos[1] = "off";
13726
- else if (sub === "on" || sub === "off") notes.push(t(`\`skills ${sub}\` is now \`skills ${sub === "on" ? "enable" : "disable"}\`.`, `\`skills ${sub}\` ahora es \`skills ${sub === "on" ? "enable" : "disable"}\`.`));
13727
- }
13728
- if (cmd === "agents" && sub === "demo") {
13729
- if (pos[2] === "enable" || pos[2] === "disable") {
13730
- v[pos[2] === "enable" ? "on" : "off"] = true;
13731
- pos.splice(2, 1);
13732
- } else if (v.on || v.off) notes.push(t(`\`agents demo <agent> --${v.on ? "on" : "off"}\` is now \`agents demo ${v.on ? "enable" : "disable"} <agent>\`.`, `\`agents demo <agente> --${v.on ? "on" : "off"}\` ahora es \`agents demo ${v.on ? "enable" : "disable"} <agente>\`.`));
13733
- if (v.expires !== void 0) v.vence = v.expires;
13734
- else if (v.vence !== void 0) notes.push(t("`--vence` is now `--expires`.", "`--vence` ahora es `--expires`."));
13735
- }
13736
- if (cmd === "agents" && (sub === "embed" || sub === "widget") && (pos[2] === "enable" || pos[2] === "disable")) {
13737
- v[pos[2] === "enable" ? "on" : "off"] = true;
13738
- pos.splice(2, 1);
13739
- }
13740
- if (cmd === "channels" && pos[2] === "groups") {
13741
- if (pos[3] === "enable" || pos[3] === "disable") {
13742
- v[pos[3] === "enable" ? "on" : "off"] = true;
13743
- pos[3] = "set";
13744
- } else if (pos[3] === "set" && (v.on || v.off)) notes.push(t(`\`groups set \u2026 --${v.on ? "on" : "off"}\` is now \`groups ${v.on ? "enable" : "disable"} <agent> <group>\` (set keeps --role).`, `\`groups set \u2026 --${v.on ? "on" : "off"}\` ahora es \`groups ${v.on ? "enable" : "disable"} <agente> <grupo>\` (set se queda para --role).`));
13745
- }
13746
- return notes;
14008
+ // src/update.ts
14009
+ import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
14010
+ import { homedir as homedir3 } from "node:os";
14011
+ import { dirname as dirname3, join as join7 } from "node:path";
14012
+ var FILE = join7(process.env.XDG_CONFIG_HOME || join7(homedir3(), ".config"), "ghosty", "update.json");
14013
+ var TTL = 60 * 60 * 1e3;
14014
+ var UPGRADE = "npm i -g @ghostystudio/cli@latest";
14015
+ function newer(a, b) {
14016
+ const pa = a.split(".").map(Number);
14017
+ const pb = b.split(".").map(Number);
14018
+ for (let i = 0; i < 3; i++) if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) > (pb[i] ?? 0);
14019
+ return false;
13747
14020
  }
13748
-
13749
- // src/commands/spec.ts
13750
- var import_yaml = __toESM(require_dist(), 1);
13751
- import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as writeFile9 } from "node:fs/promises";
13752
- import { join as join9, resolve as resolve2 } from "node:path";
13753
- var enc9 = encodeURIComponent;
13754
- async function agentsExport(id, v) {
13755
- if (!id) throw usage(t("Missing agent.", "Falta el agente."), t("Example", "Ejemplo") + ": ghosty agents export pia-0 > pia.yaml \xB7 ghosty agents export pia-0 --out ./pia");
13756
- const yaml = await (await request(`/api/v2/agents/${enc9(id)}/export`)).text();
13757
- if (!v.out) {
13758
- if (globals.json) return out((0, import_yaml.parse)(yaml));
13759
- return void process.stdout.write(yaml);
13760
- }
13761
- const dir = resolve2(v.out);
13762
- await mkdir7(dir, { recursive: true });
13763
- await writeFile9(join9(dir, "agent.yaml"), yaml);
13764
- const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
13765
- for (const slug of own) {
13766
- const sk = await api(`/api/v2/agents/${enc9(id)}/skills/${enc9(slug)}`);
13767
- await writeSkill(join9(dir, "skills", slug), sk);
14021
+ async function latestVersion(current2) {
14022
+ if (process.env.GHOSTY_NO_UPDATE_CHECK || process.env.CI) return null;
14023
+ let cache2 = {};
14024
+ try {
14025
+ cache2 = JSON.parse(await readFile9(FILE, "utf8"));
14026
+ } catch {
13768
14027
  }
13769
- out({ saved: dir, skills: own }, () => `${lavender("\u2713")} ${dir}/agent.yaml${own.length ? ` + skills/ (${own.join(", ")})` : ""}
13770
- ${dim(t(`Edit it, then: ghosty apply ${v.out} --dry-run`, `Ed\xEDtalo y luego: ghosty apply ${v.out} --dry-run`))}`);
13771
- }
13772
- function fillSecrets(file) {
13773
- const missing = [];
13774
- const fill = (o) => {
13775
- for (const [k, val] of Object.entries(o ?? {})) {
13776
- const m = typeof val === "string" ? /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(val) : null;
13777
- if (!m) continue;
13778
- if (process.env[m[1]] !== void 0) o[k] = process.env[m[1]];
13779
- else missing.push(m[1]);
14028
+ if (current2 && cache2.latest && newer(current2, cache2.latest)) cache2.checkedAt = 0;
14029
+ if (!cache2.checkedAt || Date.now() - cache2.checkedAt > TTL) {
14030
+ try {
14031
+ const r = await fetch("https://registry.npmjs.org/@ghostystudio/cli/latest", { signal: AbortSignal.timeout(1500) });
14032
+ const latest = (await r.json()).version;
14033
+ if (latest) cache2 = { latest, checkedAt: Date.now() };
14034
+ await mkdir5(dirname3(FILE), { recursive: true });
14035
+ await writeFile7(FILE, JSON.stringify(cache2));
14036
+ } catch {
13780
14037
  }
13781
- };
13782
- const x = file["x-ghosty"];
13783
- fill(x?.env);
13784
- for (const s2 of x?.mcp ?? []) fill(s2.env), fill(s2.headers);
13785
- return missing;
13786
- }
13787
- async function readSkills(dir) {
13788
- const root = join9(dir, "skills");
13789
- const out2 = {};
13790
- const slugs = await readdir4(root, { withFileTypes: true }).catch(() => []);
13791
- for (const e of slugs) {
13792
- if (!e.isDirectory() || e.name.startsWith(".")) continue;
13793
- const { markdown, assets } = await readSkillDir(join9(root, e.name));
13794
- out2[e.name] = [{ path: "SKILL.md", contentBase64: Buffer.from(markdown).toString("base64") }, ...assets.map((a) => ({ path: a.name, contentBase64: a.contentBase64 }))];
13795
14038
  }
13796
- return out2;
14039
+ return cache2.latest ?? null;
13797
14040
  }
13798
- var line = (c) => c.op === "!" ? dim(`! ${c.what}`) : `${c.op === "+" ? lavender("+") : c.op === "-" ? red("-") : "~"} ${c.what}`;
13799
- async function applyCmd(target, v) {
13800
- if (!target) throw usage(t("Missing file or directory.", "Falta el archivo o el directorio."), t("Example: ghosty apply pia.yaml --dry-run \xB7 ghosty apply ./pia (from agents export --out)", "Ejemplo: ghosty apply pia.yaml --dry-run \xB7 ghosty apply ./pia (de agents export --out)"));
13801
- const isDir = (await stat2(target).catch(() => null))?.isDirectory() ?? false;
13802
- const text = await readFile11(isDir ? join9(target, "agent.yaml") : target, "utf8");
13803
- const file = (0, import_yaml.parse)(text);
13804
- if (!file || typeof file !== "object") throw usage(t(`${target} is not a YAML/JSON agent file.`, `${target} no es un archivo de agente YAML/JSON.`));
13805
- const missing = fillSecrets(file);
13806
- if (missing.length && !globals.json) info(dim(t(`Secrets not in your environment keep their current value: ${missing.join(", ")}`, `Los secretos que no est\xE1n en tu entorno conservan su valor actual: ${missing.join(", ")}`)));
13807
- const files = isDir ? await readSkills(target) : void 0;
13808
- let id = v.agent ?? file["x-ghosty"]?.id;
13809
- if (v.create) {
13810
- const x = file["x-ghosty"] ?? {};
13811
- const name = v.name ?? file.title;
13812
- if (!name) throw usage(t("The file has no title: pass --name.", "El archivo no tiene t\xEDtulo: pasa --name."));
13813
- if (v["dry-run"]) return out({ create: true, name, engine: x.engine, workspace: x.workspace ?? null }, () => `${lavender("+")} ${t("agent", "agente")} ${name} (${x.engine ?? t("default engine", "motor por default")}${x.workspace ? t(` in ${x.workspace}`, ` en ${x.workspace}`) : ""})
13814
- ${dim(t("Then every setting of the file is applied. Dry run: nothing created.", "Despu\xE9s se aplica cada ajuste del archivo. Simulaci\xF3n: no se cre\xF3 nada."))}`);
13815
- requireYesIfHeadless(v.yes, `ghosty apply ${target} --create`);
13816
- const created = await api("/api/v2/me/agents", { method: "POST", body: { name, ...x.engine ? { engine: x.engine } : {}, ...x.model ? { model: x.model } : {}, ...x.workspace ? { workspace: x.workspace } : {}, ...x.channels ? { channels: x.channels } : {} } });
13817
- id = created.id;
13818
- info(dim(t(`Created ${name} (${id}); applying the rest\u2026`, `${name} creado (${id}); aplicando lo dem\xE1s\u2026`)));
13819
- } else {
13820
- id = await resolveAgent(id);
13821
- if (!id) throw usage(t("The file has no agent id.", "El archivo no trae id de agente."), t("Point it at one with --agent <name|id>, or make a new one with --create.", "Ap\xFAntalo a uno con --agent <nombre|id>, o crea uno nuevo con --create."));
13822
- }
13823
- const spec = JSON.stringify(v.create ? { ...file, title: void 0 } : file);
13824
- const body = { spec, ...files ? { files } : {}, ...v.prune ? { prune: true } : {}, ...v["no-notify"] ? { notify: false } : {} };
13825
- const path = `/api/v2/agents/${enc9(id)}/apply`;
13826
- const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
13827
- const actionable = plan.plan.filter((c) => c.op !== "!");
13828
- const text2 = plan.plan.map(line).join("\n") || dim(t("No changes: the agent already matches the file.", "Sin cambios: el agente ya coincide con el archivo."));
13829
- if (v["dry-run"] || !actionable.length) return out(plan, () => `${bold(id)}
13830
- ${text2}${actionable.length ? `
13831
- ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}` : ""}`);
13832
- const retry = `ghosty apply ${target}${v.agent ? ` --agent ${v.agent}` : ""}${v.prune ? " --prune" : ""}`;
13833
- if (!v.create) requireYesIfHeadless(v.yes, retry);
13834
- if (!v.create) await confirm(`${text2}
13835
- ${t(`Apply ${actionable.length} change(s) to ${id}?`, `\xBFAplicar ${actionable.length} cambio(s) a ${id}?`)}`, { yes: v.yes, retry });
13836
- const r = await api(path, { method: "POST", body });
13837
- out(r, () => `${lavender("\u2713")} ${t(`${r.applied?.length ?? 0} change(s) applied to ${id}.`, `${r.applied?.length ?? 0} cambio(s) aplicados a ${id}.`)}${r.failed ? `
13838
- ${red(t(`Stopped at \xAB${r.failed.what}\xBB: ${serverText(r.failed.error)}`, `Se detuvo en \xAB${r.failed.what}\xBB: ${r.failed.error}`))}` : ""}`);
14041
+ async function updateNotice(current2) {
14042
+ const latest = await latestVersion(current2);
14043
+ return latest && newer(latest, current2) ? t(`Update available: ${current2} \u2192 ${latest}. Run: ${UPGRADE}`, `Hay versi\xF3n nueva: ${current2} \u2192 ${latest}. Corre: ${UPGRADE}`) : null;
13839
14044
  }
13840
14045
 
13841
- // src/commands/plan.ts
13842
- async function planCmd(sub, slug, v, context) {
13843
- if (sub !== "apply") throw usage(t(`Unknown plan subcommand: ${sub ?? "(none)"}.`, `Subcomando desconocido de plan: ${sub ?? "(ninguno)"}.`), "ghosty plan apply <workspace> [--dry-run]");
13844
- slug ??= context;
13845
- if (!slug) throw usage(t("Missing workspace.", "Falta el espacio."), "ghosty plan apply <workspace> [--dry-run] (or ghosty use <workspace>)");
13846
- const path = `/api/v2/workspaces/${encodeURIComponent(slug)}/plan/apply`;
13847
- const plan = await api(path, { method: "POST", body: { dryRun: true } });
13848
- const money2 = (n) => `$${n.toLocaleString("es-MX")} MXN`;
13849
- const text = [
13850
- `${bold(plan.workspace)} ${dim(plan.scheduledFor ? t(`scheduled for ${plan.scheduledFor.slice(0, 10)}`, `programado para el ${plan.scheduledFor.slice(0, 10)}`) : "")}`,
13851
- ...plan.changes.map((c) => ` ~ ${c.label}: ${String(c.from)} \u2192 ${String(c.to)}`),
13852
- ` ${t("monthly", "al mes")}: ${money2(plan.monthlyTotal.from)} \u2192 ${money2(plan.monthlyTotal.to)}`
13853
- ].join("\n");
13854
- if (v["dry-run"]) return out(plan, () => `${text}
13855
- ${dim(t("Dry run: nothing applied.", "Simulaci\xF3n: no se aplic\xF3 nada."))}`);
13856
- requireYesIfHeadless(v.yes, `ghosty plan apply ${slug}`);
13857
- await confirm(`${text}
13858
- ${t("Apply this plan now?", "\xBFAplicar este plan ya?")}`, { yes: v.yes, retry: `ghosty plan apply ${slug}` });
13859
- const r = await api(path, { method: "POST", body: {} });
13860
- out(r, () => `${lavender("\u2713")} ${r.applied ? t(`Plan applied to ${r.workspace}.`, `Plan aplicado a ${r.workspace}.`) : t("Nothing applied.", "No se aplic\xF3 nada.")}`);
13861
- }
13862
- async function boxesUsage(slug) {
13863
- if (!slug) throw usage(t("Which workspace?", "\xBFQu\xE9 espacio?"), "ghosty usage --boxes --workspace <slug> (or ghosty use <workspace>)");
13864
- const r = await api(`/api/v2/workspaces/${encodeURIComponent(slug)}/boxes`);
13865
- const rows = r.agents.map((a) => ({ agent: a.name ?? a.id, engine: a.engine, "per box": a.conversationsPerBox ?? "", boxes: a.boxes ?? "", on: a.boxesOn ?? "", spares: a.spares ?? "", "running now": a.activeTurns ?? "", ...a.error ? { note: a.error } : {} }));
13866
- const head = `${bold(r.workspace)} ${dim(t(`plan: ${r.plan.parallel} in parallel \xB7 up to ${r.plan.maxBoxes} box(es) \xB7 ${r.plan.hotBoxes} always-on spare(s)`, `plan: ${r.plan.parallel} en paralelo \xB7 hasta ${r.plan.maxBoxes} caja(s) \xB7 ${r.plan.hotBoxes} spare(s) siempre encendido(s)`))}`;
13867
- const space = r.space ? (r.space.used > r.space.limit ? (s2) => s2 : dim)(
13868
- t(
13869
- `${r.space.used} of ${r.space.limit} box(es) in use \xB7 ${r.space.spares} spare \xB7 ${r.space.asleep} asleep${r.space.used > r.space.limit ? " \xB7 OVER the limit" : ""}`,
13870
- `${r.space.used} de ${r.space.limit} caja(s) \xB7 ${r.space.spares} spare \xB7 ${r.space.asleep} dormida(s)${r.space.used > r.space.limit ? " \xB7 SOBRE el tope" : ""}`
13871
- )
13872
- ) : dim(t("(could not read the host)", "(no pude leer el host)"));
13873
- out(r, () => `${head}
13874
- ${space}
13875
- ${table(rows, ["agent", "engine", "per box", "boxes", "on", "spares", "running now"])}
13876
- ${dim(t(`${r.totals.activeTurns} turn(s) running`, `${r.totals.activeTurns} turno(s) corriendo`))}`);
14046
+ // src/resolve.ts
14047
+ var looksLikeId = (s2) => /^c[a-z0-9]{20,}$/.test(s2) || /^[0-9a-f]{24}$/.test(s2);
14048
+ function pickAgent(list, ref, workspace) {
14049
+ const want = ref.trim().toLowerCase();
14050
+ let hits = list.filter((a) => a.nombre.trim().toLowerCase() === want);
14051
+ if (hits.length > 1 && workspace) {
14052
+ const here = hits.filter((a) => a.espacio?.nombre === workspace);
14053
+ if (here.length) hits = here;
14054
+ }
14055
+ if (hits.length === 1) return hits[0].id;
14056
+ if (!hits.length) {
14057
+ const near = list.filter((a) => a.nombre.toLowerCase().includes(want)).slice(0, 5);
14058
+ const names2 = near.map((a) => `${a.nombre} (${a.id})`).join(", ");
14059
+ throw new CliError(t(`No agent named "${ref}".`, `No hay ning\xFAn agente llamado "${ref}".`), EXIT.USAGE, near.length ? t(`Did you mean: ${names2}?`, `\xBFQuisiste decir: ${names2}?`) : t("List them: ghosty agents ls", "L\xEDstalos: ghosty agents ls"));
14060
+ }
14061
+ const where = (a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : a.espacio?.tipo ?? "?";
14062
+ const ids = hits.map((a) => ` ${a.id} ${a.motor ?? ""} ${where(a)}`).join("\n");
14063
+ throw new CliError(t(`${hits.length} agents are named "${ref}".`, `${hits.length} agentes se llaman "${ref}".`), EXIT.USAGE, t(`Use the id:
14064
+ ${ids}`, `Usa el id:
14065
+ ${ids}`));
13877
14066
  }
13878
-
13879
- // src/commands/spaces.ts
13880
- var enc10 = encodeURIComponent;
13881
- var wsPath = (slug, rest = "") => `/api/v2/workspaces/${enc10(slug)}${rest}`;
13882
- var needSpace = (slug, example) => {
13883
- if (!slug) throw usage(t("Which workspace?", "\xBFQu\xE9 espacio?"), `${t("Example", "Ejemplo")}: ${example} ${t("(or ghosty use <workspace>)", "(o ghosty use <espacio>)")}`);
13884
- return slug;
13885
- };
13886
- function parseTokens(raw) {
13887
- const m = /^\s*(\d+(?:\.\d+)?)\s*([kKmM]?)\s*$/.exec(raw);
13888
- if (!m) throw usage(t(`Bad token amount: ${raw}.`, `Cantidad de tokens inv\xE1lida: ${raw}.`), t("Use 20M, 500k or a plain number.", "Usa 20M, 500k o un n\xFAmero."));
13889
- const n = Number(m[1]) * (m[2].toLowerCase() === "m" ? 1e6 : m[2].toLowerCase() === "k" ? 1e3 : 1);
13890
- return Math.round(n);
14067
+ var cache = null;
14068
+ function listAgents() {
14069
+ cache ??= api("/api/v2/me/agents?client=cli").then((r) => r.agentes);
14070
+ return cache;
13891
14071
  }
13892
- function fmtTokens(n) {
13893
- if (n >= 1e6) return `${+(n / 1e6).toFixed(n % 1e6 === 0 ? 0 : 1)}M`;
13894
- if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
13895
- return String(n);
14072
+ async function resolveAgent(ref, workspace) {
14073
+ if (!ref || looksLikeId(ref)) return ref;
14074
+ return pickAgent(await listAgents(), ref, workspace);
13896
14075
  }
13897
- function parseSwitch(flag, raw) {
13898
- if (raw === void 0) return void 0;
13899
- if (/^(on|true|yes|s[ií])$/i.test(raw)) return true;
13900
- if (/^(off|false|no)$/i.test(raw)) return false;
13901
- throw usage(t(`${flag} takes on or off.`, `${flag} acepta on u off.`));
14076
+ function agentArgIndex(pos) {
14077
+ const [cmd, sub] = pos;
14078
+ switch (cmd) {
14079
+ case "agents":
14080
+ if (sub === "docs" || sub === "documents" || sub === "teams") return 3;
14081
+ return sub && !["ls", "list", "create", "new"].includes(sub) ? 2 : null;
14082
+ case "files":
14083
+ case "skills":
14084
+ case "mcp":
14085
+ case "dbs":
14086
+ case "db":
14087
+ return sub === "photos" ? 3 : 2;
14088
+ case "turns":
14089
+ return pos[1] === "stats" ? null : 2;
14090
+ case "board":
14091
+ case "conversations":
14092
+ case "schedule":
14093
+ return 2;
14094
+ case "try":
14095
+ case "chat":
14096
+ return 1;
14097
+ case "channels":
14098
+ return pos[2] === "groups" ? 4 : 3;
14099
+ case "boards":
14100
+ return sub === "assign" ? 2 : null;
14101
+ case "access":
14102
+ return sub === "why" && pos[3] ? 3 : null;
14103
+ case "demos":
14104
+ case "demo":
14105
+ return sub === "history" ? 2 : null;
14106
+ default:
14107
+ return null;
14108
+ }
13902
14109
  }
13903
- async function enginesCmd(sub, v) {
13904
- if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown engines subcommand: ${sub}.`, `Subcomando de engines desconocido: ${sub}.`), "ghosty engines ls [--models]");
13905
- const r = await api("/api/v2/engines");
13906
- out(r, () => {
13907
- if (!v.models) {
13908
- const rows = r.engines.map((e) => ({ engine: e.id, provider: e.provider, default: e.defaultModel ?? "", models: e.models.map((m) => m.id).join(", ") }));
13909
- return `${table(rows, ["engine", "provider", "default"])}
13910
- ${dim(t("Models per engine: ghosty engines ls --models", "Modelos de cada motor: ghosty engines ls --models"))}`;
14110
+
14111
+ // src/completion.ts
14112
+ import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
14113
+ import { homedir as homedir4 } from "node:os";
14114
+ import { join as join8 } from "node:path";
14115
+ var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
14116
+ var AGENT_FLAGS = /* @__PURE__ */ new Set(["--like", "--agent", "--prompt-base"]);
14117
+ var WORKSPACE_FLAGS = /* @__PURE__ */ new Set(["--workspace"]);
14118
+ function subcommandsOf(cmd) {
14119
+ const subs = /* @__PURE__ */ new Set();
14120
+ for (const u of HELPS[cmd]?.usage ?? []) {
14121
+ const m = new RegExp(`^ghosty ${cmd} ([a-z][a-z|-]*)`).exec(u);
14122
+ if (m) {
14123
+ for (const s2 of m[1].split("|")) if (!s2.includes("-") || /^[a-z]+-[a-z]+$/.test(s2)) subs.add(s2);
13911
14124
  }
13912
- return r.engines.map((e) => {
13913
- const rows = e.models.map((m) => ({ model: m.id + (m.id === e.defaultModel ? " *" : ""), label: m.label, cost: `${m.multiplier}\xD7`, note: m.ownKeyOnly ? t("your own key only", "s\xF3lo con llave propia") : "" }));
13914
- return `${bold(e.id)} ${dim(`${e.provider} \xB7 ${e.protocol}${e.vision ? t(" \xB7 sees images", " \xB7 ve im\xE1genes") : ""}`)}
13915
- ${table(rows, ["model", "label", "cost", "note"])}`;
13916
- }).join("\n\n") + `
13917
- ${dim(t("* default \xB7 cost = balance multiplier per turn", "* por default \xB7 costo = multiplicador de saldo por turno"))}`;
13918
- });
14125
+ }
14126
+ return [...subs];
13919
14127
  }
13920
- function bagsText(r) {
13921
- const rows = r.bags.map((b) => ({
13922
- bag: b.name,
13923
- tokens: fmtTokens(b.tokens),
13924
- used: `${fmtTokens(b.used)} (${Math.round(b.ratio * 100)}%)`,
13925
- kind: b.recurring ? t("monthly", "mensual") : t(`one-time since ${b.since?.slice(0, 10)}`, `carga \xFAnica desde ${b.since?.slice(0, 10)}`),
13926
- cap: b.hardCap ? t("cuts", "corta") : t("warns", "avisa"),
13927
- agents: b.agents.map((a) => a.name ?? a.id).join(", ") || "\u2014"
13928
- }));
13929
- const free = r.unbagged.length ? `
13930
- ${dim(t(`Workspace balance (no bag): ${r.unbagged.map((a) => a.name ?? a.id).join(", ")}`, `Saldo del espacio (sin bolsa): ${r.unbagged.map((a) => a.name ?? a.id).join(", ")}`))}` : "";
13931
- return `${bold(r.workspace)}
13932
- ${r.bags.length ? table(rows, ["bag", "tokens", "used", "kind", "cap", "agents"]) : dim(t("No bags: every agent uses the workspace balance.", "Sin bolsas: todos los agentes usan el saldo del espacio."))}${free}`;
14128
+ function flagsOf(cmd) {
14129
+ const own = /* @__PURE__ */ new Set();
14130
+ for (const [f] of HELPS[cmd]?.flags ?? []) for (const m of f.matchAll(/--[a-z][a-z-]*/g)) own.add(m[0]);
14131
+ for (const u of HELPS[cmd]?.usage ?? []) for (const m of u.matchAll(/--[a-z][a-z-]*/g)) own.add(m[0]);
14132
+ return [...own, ...GLOBAL_FLAGS];
13933
14133
  }
13934
- async function bagsCmd(sub, slug, name, v, context) {
13935
- const ex = 'ghosty bags ls <workspace> \xB7 create <workspace> "<bag>" --tokens 20M [--recurring off] \xB7 set <workspace> "<bag>" --tokens 30M \xB7 rm <workspace> "<bag>"';
13936
- if (sub && !["ls", "list", "create", "new", "set", "rm", "delete"].includes(sub)) {
13937
- name = slug;
13938
- slug = sub;
13939
- sub = "ls";
13940
- }
13941
- slug = needSpace(slug ?? context, ex);
13942
- const recurring = parseSwitch("--recurring", v.recurring);
13943
- const hardCap = parseSwitch("--hard-cap", v["hard-cap"]);
13944
- switch (sub ?? "ls") {
13945
- case "ls":
13946
- case "list": {
13947
- const r = await api(wsPath(slug, "/bags"));
13948
- return out(r, () => bagsText(r));
13949
- }
13950
- case "create":
13951
- case "new": {
13952
- if (!name) throw usage(t("Missing the bag's name.", "Falta el nombre de la bolsa."), ex);
13953
- if (!v.tokens) throw usage(t("Missing --tokens.", "Falta --tokens."), `ghosty bags create ${slug} "${name}" --tokens 20M`);
13954
- const r = await api(wsPath(slug, "/bags"), { method: "POST", body: { name, tokens: parseTokens(v.tokens), ...recurring !== void 0 ? { recurring } : {}, ...hardCap !== void 0 ? { hardCap } : {}, ...v.note ? { note: v.note } : {} } });
13955
- return out(r, () => `${lavender("\u2713")} ${t(`Bag \xAB${name}\xBB created.`, `Bolsa \xAB${name}\xBB creada.`)} ${dim(t(`Give it agents: ghosty agents set <agent> --bag "${name}"`, `Cu\xE9lgale agentes: ghosty agents set <agente> --bag "${name}"`))}
13956
- ${bagsText(r)}`);
13957
- }
13958
- case "set": {
13959
- if (!name) throw usage(t("Missing the bag's name.", "Falta el nombre de la bolsa."), ex);
13960
- const body = { ...v.tokens ? { tokens: parseTokens(v.tokens) } : {}, ...recurring !== void 0 ? { recurring } : {}, ...hardCap !== void 0 ? { hardCap } : {}, ...v.note !== void 0 ? { note: v.note } : {}, ...v.name ? { name: v.name } : {} };
13961
- if (!Object.keys(body).length) throw usage(t("Nothing to change.", "Nada que cambiar."), "--tokens 30M | --recurring on|off | --hard-cap on|off | --note TEXT | --name NEW");
13962
- const r = await api(wsPath(slug, `/bags/${enc10(name)}`), { method: "PATCH", body });
13963
- return out(r, () => `${lavender("\u2713")} ${t(`Bag \xAB${r.updated}\xBB updated.`, `Bolsa \xAB${r.updated}\xBB actualizada.`)}
13964
- ${bagsText(r)}`);
13965
- }
13966
- case "rm":
13967
- case "delete": {
13968
- if (!name) throw usage(t("Missing the bag's name.", "Falta el nombre de la bolsa."), ex);
13969
- requireYesIfHeadless(v.yes, `ghosty bags rm ${slug} "${name}"`);
13970
- await confirm(t(`Delete bag \xAB${name}\xBB from ${slug}? (only if no agent uses it)`, `\xBFBorrar la bolsa \xAB${name}\xBB de ${slug}? (s\xF3lo si ning\xFAn agente la usa)`), { yes: v.yes, retry: `ghosty bags rm ${slug} "${name}"` });
13971
- const r = await api(wsPath(slug, `/bags/${enc10(name)}`), { method: "DELETE" });
13972
- return out(r, () => `${lavender("\u2713")} ${t(`Bag \xAB${r.deleted}\xBB deleted.`, `Bolsa \xAB${r.deleted}\xBB borrada.`)}
13973
- ${bagsText(r)}`);
13974
- }
14134
+ function completionFor(words, current2, commands) {
14135
+ const prev = words[words.length - 1];
14136
+ if (prev && AGENT_FLAGS.has(prev)) return { kind: "agents" };
14137
+ if (prev && WORKSPACE_FLAGS.has(prev)) return { kind: "workspaces" };
14138
+ if (!words.length) return { kind: "words", words: commands };
14139
+ const [cmd, sub] = words;
14140
+ if (current2.startsWith("-")) return { kind: "words", words: flagsOf(cmd) };
14141
+ const pos = words.filter((w) => !w.startsWith("-"));
14142
+ if (cmd === "use" && pos.length === 1) return { kind: "workspaces" };
14143
+ if (cmd === "boards" && pos.length === 2 && sub !== "assign") return { kind: "workspaces" };
14144
+ if (cmd === "help" && pos.length === 1) return { kind: "words", words: commands };
14145
+ if (pos.length === 1 && cmd !== "try" && cmd !== "chat") {
14146
+ const subs = subcommandsOf(cmd);
14147
+ if (subs.length) return { kind: "words", words: subs };
13975
14148
  }
14149
+ if (agentArgIndex([...pos, current2]) === pos.length) return { kind: "agents" };
14150
+ return { kind: "words", words: [] };
13976
14151
  }
13977
- async function spacesCmd(sub, slug, arg, v, context) {
13978
- const ex = "ghosty spaces ls [--user EMAIL] \xB7 create <slug> [--combo teams|sales|both] [--dry-run] \xB7 join|leave <workspace> \xB7 set <workspace> --combo teams|sales|both | --model-cap 0.25|1|4|none | --house-claude on|off \xB7 renew <workspace> --until 2026-11-01 [--note TEXT] \xB7 rename <old> <new> [--dry-run] \xB7 archive|restore <workspace> [--dry-run] \xB7 transfer <workspace> --to EMAIL [--keep-as admin|member|none] [--copy-keys] [--dry-run] \xB7 log <workspace> [--since 30d] [--action workspace]";
13979
- if (!sub || sub === "ls" || sub === "list") {
13980
- const r = await api(`/api/v2/workspaces${v.user ? `?user=${enc10(v.user)}` : ""}`);
13981
- const rows = r.workspaces.map((w) => ({ workspace: w.slug, role: w.role ?? "", plan: w.plan, combo: w.combo, owner: w.owner ?? "", paid: w.paidUntil ?? "" }));
13982
- return out(r, () => rows.length ? `${bold(r.user)}
13983
- ${table(rows, ["workspace", "role", "plan", "combo", "owner", "paid"])}` : dim(t(`${r.user} has no workspaces.`, `${r.user} no tiene espacios.`)));
14152
+ var cacheFile = () => join8(process.env.XDG_CACHE_HOME || join8(homedir4(), ".cache"), "ghosty", `complete${process.env.GHOSTY_PROFILE ? `.${process.env.GHOSTY_PROFILE}` : ""}.json`);
14153
+ async function names() {
14154
+ try {
14155
+ const c = JSON.parse(await readFile10(cacheFile(), "utf8"));
14156
+ if (Date.now() - c.at < 5 * 6e4) return c;
14157
+ } catch {
13984
14158
  }
13985
- if (sub === "create") {
13986
- slug = needSpace(slug, "ghosty spaces create <slug> [--combo teams|sales|both] [--dry-run]");
13987
- const r = await api("/api/v2/workspaces", { method: "POST", body: { slug, ...v.combo ? { combo: v.combo } : {}, ...v["dry-run"] ? { dryRun: true } : {} } });
13988
- const line2 = `${r.workspace} \xB7 ${r.owner} \xB7 ${r.plan} \xB7 ${r.combo}`;
13989
- if (!r.created) return out(r, () => `${line2}
13990
- ${dim(t("Dry run: nothing created.", "Simulaci\xF3n: no se cre\xF3 nada."))}`);
13991
- return out(r, () => `${lavender("\u2713")} ${line2}
13992
- ${dim(t(`Next: ghosty use ${r.workspace} \xB7 ghosty partner enable ${r.workspace}`, `Sigue: ghosty use ${r.workspace} \xB7 ghosty partner enable ${r.workspace}`))}`);
14159
+ const list = await listAgents();
14160
+ const agents = [...new Set(list.map((a) => a.nombre))];
14161
+ const workspaces = [...new Set(list.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
14162
+ await mkdir6(join8(cacheFile(), ".."), { recursive: true }).catch(() => {
14163
+ });
14164
+ await writeFile8(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
14165
+ });
14166
+ return { agents, workspaces };
14167
+ }
14168
+ async function completeCmd(args, commands) {
14169
+ const current2 = args.length ? args[args.length - 1] : "";
14170
+ const words = args.slice(0, -1);
14171
+ const want = completionFor(words, current2, commands);
14172
+ let out2 = [];
14173
+ try {
14174
+ if (want.kind === "words") out2 = want.words;
14175
+ else out2 = (await names())[want.kind];
14176
+ } catch {
14177
+ out2 = [];
13993
14178
  }
13994
- if (sub === "log") {
13995
- slug = needSpace(slug, "ghosty spaces log <workspace> [--since 30d] [--action workspace]");
13996
- const q = new URLSearchParams({ limit: v.limit ?? "50" });
13997
- if (v.since) q.set("since", v.since);
13998
- if (v.action) q.set("action", v.action);
13999
- const r = await api(wsPath(slug, `/log?${q}`));
14000
- const rows = r.events.map((e) => ({ at: e.at.slice(0, 16).replace("T", " "), actor: e.actor, action: e.action, detail: serverText(e.detail ? JSON.stringify(e.detail) : "").slice(0, 110) }));
14001
- return out(r, () => rows.length ? table(rows, ["at", "actor", "action", "detail"]) : dim(t(`No events in ${r.workspace}${v.since ? ` since ${v.since}` : ""}.`, `Sin eventos en ${r.workspace}${v.since ? ` desde ${v.since}` : ""}.`)));
14179
+ process.stdout.write(out2.filter((w) => w.toLowerCase().startsWith(current2.toLowerCase())).join("\n") + "\n");
14180
+ }
14181
+ function completionScript(shell) {
14182
+ if (shell === "bash") {
14183
+ return `# ghosty \u2014 bash completion: eval "$(ghosty completion bash)" en ~/.bashrc
14184
+ _ghosty() {
14185
+ # Las palabras ANTES de cambiar IFS: el bash 3.2 de macOS junta \${arr[@]:1:n} con un IFS propio.
14186
+ local words=("\${COMP_WORDS[@]:1:COMP_CWORD-1}") cur="\${COMP_WORDS[COMP_CWORD]}"
14187
+ local IFS=$'\\n'
14188
+ COMPREPLY=( $(ghosty __complete -- "\${words[@]}" "$cur" 2>/dev/null) )
14189
+ }
14190
+ complete -o default -F _ghosty ghosty
14191
+ `;
14002
14192
  }
14003
- if (sub === "join" || sub === "leave") {
14004
- slug = needSpace(slug, `ghosty spaces ${sub} <workspace>`);
14005
- const r = await api(wsPath(slug, "/staff"), { method: "POST", body: { join: sub === "join" } });
14006
- return out(r, () => `${lavender("\u2713")} ${r.joined ? t(`You're in ${r.workspace} as staff (no seat; logged). Leave: ghosty spaces leave ${r.workspace}`, `Entraste a ${r.workspace} como staff (sin asiento; queda en bit\xE1cora). Salir: ghosty spaces leave ${r.workspace}`) : t(`You left ${r.workspace}.`, `Saliste de ${r.workspace}.`)}`);
14193
+ if (shell === "zsh") {
14194
+ return `#compdef ghosty
14195
+ # ghosty \u2014 zsh completion: source <(ghosty completion zsh) en ~/.zshrc (despu\xE9s de compinit)
14196
+ _ghosty() {
14197
+ local -a opts
14198
+ opts=("\${(@f)$(ghosty __complete -- "\${(@)words[2,CURRENT-1]}" "\${words[CURRENT]}" 2>/dev/null)}")
14199
+ compadd -a opts
14200
+ }
14201
+ compdef _ghosty ghosty
14202
+ `;
14007
14203
  }
14008
- if (sub === "set") {
14009
- slug = needSpace(slug ?? context, "ghosty spaces set <workspace> --combo sales | --model-cap 4");
14010
- const cap = v["model-cap"];
14011
- const house = parseSwitch("--house-claude", v["house-claude"]);
14012
- if (!v.combo && cap === void 0 && house === void 0) throw usage(t("Nothing to change.", "Nada que cambiar."), "--combo teams|sales|both \xB7 --model-cap 0.25|1|4|none \xB7 --house-claude on|off");
14013
- const steps = [];
14014
- if (v.combo) steps.push({ label: "combo", path: wsPath(slug, "/combo"), body: { combo: v.combo } });
14015
- if (cap !== void 0) steps.push({ label: "model-cap", path: wsPath(slug, "/model-cap"), body: { cap } });
14016
- if (house !== void 0) steps.push({ label: "house-claude", path: wsPath(slug, "/house-claude"), body: { on: house } });
14017
- const show2 = (x) => x === null ? t("plan's", "el del plan") : x === true ? "on" : x === false ? "off" : String(x);
14018
- const plans = await Promise.all(steps.map(async (st) => ({ ...st, plan: await api(st.path, { method: "POST", body: { ...st.body, dryRun: true } }) })));
14019
- const lines = plans.map((p) => `${p.plan.workspace}: ${p.label} ${show2(p.plan.from)} \u2192 ${show2(p.plan.to)}`);
14020
- const pending = plans.filter((p) => p.plan.changed);
14021
- if (!pending.length) return out(plans.map((p) => p.plan), () => dim(t(`Nothing to do: ${slug} already has that.`, `Nada que hacer: ${slug} ya lo tiene.`)));
14022
- if (v["dry-run"]) return out(plans.map((p) => p.plan), () => `${lines.join("\n")}
14023
- ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
14024
- const retry = `ghosty spaces set ${slug}${v.combo ? ` --combo ${v.combo}` : ""}${cap !== void 0 ? ` --model-cap ${cap}` : ""}${house !== void 0 ? ` --house-claude ${house ? "on" : "off"}` : ""}`;
14025
- requireYesIfHeadless(v.yes, retry);
14026
- await confirm(`${lines.join("\n")}
14027
- ${t("Change it?", "\xBFCambiarlo?")}`, { yes: v.yes, retry });
14028
- const done = [];
14029
- for (const p of pending) done.push(await api(p.path, { method: "POST", body: p.body }));
14030
- return out(done, () => done.map((r, i) => `${lavender("\u2713")} ${r.workspace}: ${pending[i].label} ${show2(r.to)}`).join("\n"));
14204
+ if (shell === "fish") {
14205
+ return `# ghosty \u2014 fish completion: ghosty completion fish > ~/.config/fish/completions/ghosty.fish
14206
+ complete -c ghosty -f -a "(ghosty __complete -- (commandline -opc)[2..-1] (commandline -ct) 2>/dev/null)"
14207
+ `;
14031
14208
  }
14032
- if (sub === "renew") {
14033
- slug = needSpace(slug ?? context, ex);
14034
- if (!v.until) throw usage(t("Missing --until YYYY-MM-DD.", "Falta --until AAAA-MM-DD."), `ghosty spaces renew ${slug} --until 2026-11-01`);
14035
- const path = wsPath(slug, "/renew");
14036
- const plan = await api(path, { method: "POST", body: { until: v.until, dryRun: true } });
14037
- const line2 = t(`${plan.workspace} (${plan.plan}): paid until ${plan.from ?? "\u2014"} \u2192 ${plan.to}`, `${plan.workspace} (${plan.plan}): pagado hasta ${plan.from ?? "\u2014"} \u2192 ${plan.to}`);
14038
- if (v["dry-run"]) return out(plan, () => `${line2}
14039
- ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
14040
- requireYesIfHeadless(v.yes, `ghosty spaces renew ${slug} --until ${v.until}`);
14041
- await confirm(`${line2}
14042
- ${t("Renew?", "\xBFRenovar?")}`, { yes: v.yes, retry: `ghosty spaces renew ${slug} --until ${v.until}` });
14043
- const r = await api(path, { method: "POST", body: { until: v.until, ...v.note ? { note: v.note } : {} } });
14044
- return out(r, () => `${lavender("\u2713")} ${line2} ${dim(t("(logged in the workspace's audit)", "(queda en la bit\xE1cora del espacio)"))}`);
14209
+ return null;
14210
+ }
14211
+
14212
+ // src/aliases.ts
14213
+ function normalizeArgs(pos, v) {
14214
+ const notes = [];
14215
+ const [cmd, sub] = pos;
14216
+ if (cmd === "cards") pos[0] = "board";
14217
+ else if (cmd === "board") notes.push(t("`ghosty board` is now `ghosty cards` (same subcommands).", "`ghosty board` ahora es `ghosty cards` (mismos subcomandos)."));
14218
+ if (cmd === "skills") {
14219
+ if (sub === "enable") pos[1] = "on";
14220
+ else if (sub === "disable") pos[1] = "off";
14221
+ else if (sub === "on" || sub === "off") notes.push(t(`\`skills ${sub}\` is now \`skills ${sub === "on" ? "enable" : "disable"}\`.`, `\`skills ${sub}\` ahora es \`skills ${sub === "on" ? "enable" : "disable"}\`.`));
14045
14222
  }
14046
- if (sub === "rename") {
14047
- slug = needSpace(slug, ex);
14048
- if (!arg) throw usage(t("Missing the new name.", "Falta el nombre nuevo."), `ghosty spaces rename ${slug} <new> --dry-run`);
14049
- const path = wsPath(slug, "/rename");
14050
- const plan = await api(path, { method: "POST", body: { to: arg, dryRun: true } });
14051
- const text = `${plan.from} \u2192 ${bold(plan.to)}
14052
- ${plan.warnings.map((w) => dim(` \xB7 ${serverText(w)}`)).join("\n")}`;
14053
- if (v["dry-run"]) return out(plan, () => `${text}
14054
- ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
14055
- requireYesIfHeadless(v.yes, `ghosty spaces rename ${slug} ${arg}`);
14056
- await confirm(`${text}
14057
- ${t("Rename?", "\xBFRenombrar?")}`, { yes: v.yes, expect: plan.from, retry: `ghosty spaces rename ${slug} ${arg}` });
14058
- const r = await api(path, { method: "POST", body: { to: arg } });
14059
- return out(r, () => `${lavender("\u2713")} ${t(`Renamed: ${r.from} \u2192 ${r.to}. Undo: ghosty spaces rename ${r.to} ${r.from}`, `Renombrado: ${r.from} \u2192 ${r.to}. Deshacer: ghosty spaces rename ${r.to} ${r.from}`)}`);
14223
+ if (cmd === "agents" && sub === "demo") {
14224
+ if (pos[2] === "enable" || pos[2] === "disable") {
14225
+ v[pos[2] === "enable" ? "on" : "off"] = true;
14226
+ pos.splice(2, 1);
14227
+ } else if (v.on || v.off) notes.push(t(`\`agents demo <agent> --${v.on ? "on" : "off"}\` is now \`agents demo ${v.on ? "enable" : "disable"} <agent>\`.`, `\`agents demo <agente> --${v.on ? "on" : "off"}\` ahora es \`agents demo ${v.on ? "enable" : "disable"} <agente>\`.`));
14228
+ if (v.expires !== void 0) v.vence = v.expires;
14229
+ else if (v.vence !== void 0) notes.push(t("`--vence` is now `--expires`.", "`--vence` ahora es `--expires`."));
14060
14230
  }
14061
- if (sub === "archive" || sub === "restore") {
14062
- slug = needSpace(slug, `ghosty spaces ${sub} <workspace> --dry-run`);
14063
- const archive = sub === "archive";
14064
- const path = wsPath(slug, "/archive");
14065
- const plan = await api(path, { method: "POST", body: { archive, dryRun: true } });
14066
- const line2 = `${plan.slug}: ${plan.from} \u2192 ${bold(plan.to)} ${dim(t(`(${plan.agents} agent(s); they and their data stay as they are)`, `(${plan.agents} agente(s); ellos y sus datos se quedan igual)`))}`;
14067
- if (!plan.changed) return out(plan, () => dim(t(`Nothing to do: ${plan.slug} is already ${plan.from}.`, `Nada que hacer: ${plan.slug} ya est\xE1 ${plan.from}.`)));
14068
- if (v["dry-run"]) return out(plan, () => `${line2}
14069
- ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
14070
- const retry = `ghosty spaces ${sub} ${slug}`;
14071
- requireYesIfHeadless(v.yes, retry);
14072
- await confirm(`${line2}
14073
- ${archive ? t("Archive it?", "\xBFArchivarlo?") : t("Restore it?", "\xBFRestaurarlo?")}`, { yes: v.yes, retry, ...archive ? { expect: plan.slug } : {} });
14074
- const r = await api(path, { method: "POST", body: { archive } });
14075
- return out(r, () => archive ? `${lavender("\u2713")} ${t(`Archived: ${r.slug}. Undo: ghosty spaces restore ${r.slug}`, `Archivado: ${r.slug}. Deshacer: ghosty spaces restore ${r.slug}`)}` : `${lavender("\u2713")} ${t(`Restored: ${r.slug}.`, `Restaurado: ${r.slug}.`)}`);
14231
+ if (cmd === "agents" && (sub === "embed" || sub === "widget") && (pos[2] === "enable" || pos[2] === "disable")) {
14232
+ v[pos[2] === "enable" ? "on" : "off"] = true;
14233
+ pos.splice(2, 1);
14076
14234
  }
14077
- if (sub === "transfer") {
14078
- slug = needSpace(slug ?? context, "ghosty spaces transfer <workspace> --to EMAIL --dry-run");
14079
- const toEmail = v.to ?? arg;
14080
- if (!toEmail) throw usage(t("Missing --to EMAIL.", "Falta --to CORREO."), `ghosty spaces transfer ${slug} --to new@owner.com --keep-as admin --copy-keys --dry-run`);
14081
- const body = { to: toEmail, ...v["keep-as"] ? { keepAs: v["keep-as"] } : {}, ...v["copy-keys"] ? { copyKeys: true } : {} };
14082
- const path = wsPath(slug, "/transfer");
14083
- const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
14084
- const text = [
14085
- `${plan.workspace}: ${plan.from ?? "\u2014"} \u2192 ${bold(plan.to)} ${dim(t(`(previous owner stays as ${plan.keepAs})`, `(el anterior queda como ${plan.keepAs})`))}`,
14086
- `${t("agents", "agentes")}: ${plan.agents.join(", ") || "\u2014"}`,
14087
- `${t("keys", "llaves")}: ${plan.keys.copy.length ? t(`copies ${plan.keys.copy.join(", ")}`, `copia ${plan.keys.copy.join(", ")}`) : t("copies none", "no copia")}${plan.keys.alreadyHas.length ? dim(t(` \xB7 already has ${plan.keys.alreadyHas.join(", ")}`, ` \xB7 ya tiene ${plan.keys.alreadyHas.join(", ")}`)) : ""}`,
14088
- ...plan.warnings.map((w) => red(` \xB7 ${serverText(w)}`))
14089
- ].join("\n");
14090
- if (v["dry-run"]) return out(plan, () => `${text}
14091
- ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
14092
- const retry = `ghosty spaces transfer ${slug} --to ${toEmail}${v["keep-as"] ? ` --keep-as ${v["keep-as"]}` : ""}${v["copy-keys"] ? " --copy-keys" : ""}`;
14093
- requireYesIfHeadless(v.yes, retry);
14094
- await confirm(`${text}
14095
- ${t("Transfer it? Its agents' boxes are recycled (a turn in flight finishes first).", "\xBFTraspasarlo? Se reciclan las cajas de sus agentes (un turno en vuelo termina antes).")}`, { yes: v.yes, expect: plan.workspace, retry });
14096
- const r = await api(path, { method: "POST", body });
14097
- return out(r, () => `${lavender("\u2713")} ${t(`${r.workspace} now belongs to ${r.to}; ${r.from ?? "\u2014"} stays as ${r.keepAs}. ${r.recycled} agent(s) recycled.`, `${r.workspace} ya es de ${r.to}; ${r.from ?? "\u2014"} queda como ${r.keepAs}. ${r.recycled} agente(s) reciclado(s).`)}${r.warnings.length ? `
14098
- ${r.warnings.map((w) => red(` \xB7 ${serverText(w)}`)).join("\n")}` : ""}`);
14235
+ if (cmd === "channels" && pos[2] === "groups") {
14236
+ if (pos[3] === "enable" || pos[3] === "disable") {
14237
+ v[pos[3] === "enable" ? "on" : "off"] = true;
14238
+ pos[3] = "set";
14239
+ } else if (pos[3] === "set" && (v.on || v.off)) notes.push(t(`\`groups set \u2026 --${v.on ? "on" : "off"}\` is now \`groups ${v.on ? "enable" : "disable"} <agent> <group>\` (set keeps --role).`, `\`groups set \u2026 --${v.on ? "on" : "off"}\` ahora es \`groups ${v.on ? "enable" : "disable"} <agente> <grupo>\` (set se queda para --role).`));
14099
14240
  }
14100
- throw usage(t(`Unknown spaces subcommand: ${sub ?? "(none)"}.`, `Subcomando de spaces desconocido: ${sub ?? "(ninguno)"}.`), ex);
14241
+ return notes;
14101
14242
  }
14102
- async function workspaceUsage(slug, v) {
14103
- slug = needSpace(slug, "ghosty usage --workspace <slug> --by agent,day --days 14");
14104
- const r = await api(wsPath(slug, `/usage?days=${enc10(v.days ?? "14")}`));
14105
- const cdmx2 = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
14106
- if (v["quota-hits"]) {
14107
- const rows2 = r.quotaHits.map((q) => ({ at: cdmx2(q.at), agent: q.agent, channel: q.channel, from: q.phone ?? q.conversationId.slice(0, 24), message: (q.preview ?? "").slice(0, 60) }));
14108
- const total2 = r.agents.reduce((n, a) => n + a.noQuota, 0);
14109
- return out(r.quotaHits, () => rows2.length ? `${table(rows2, ["at", "agent", "channel", "from", "message"])}
14110
- ${dim(t(`${total2} message(s) unanswered for lack of balance in ${r.days} day(s) (CDMX time)`, `${total2} mensaje(s) sin atender por saldo en ${r.days} d\xEDa(s) (hora CDMX)`))}` : dim(t(`No message ran out of balance in ${r.days} day(s).`, `Ning\xFAn mensaje se qued\xF3 sin saldo en ${r.days} d\xEDa(s).`)));
14243
+
14244
+ // src/commands/spec.ts
14245
+ var import_yaml = __toESM(require_dist(), 1);
14246
+ import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as writeFile9 } from "node:fs/promises";
14247
+ import { join as join9, resolve as resolve2 } from "node:path";
14248
+ var enc11 = encodeURIComponent;
14249
+ async function agentsExport(id, v) {
14250
+ if (!id) throw usage(t("Missing agent.", "Falta el agente."), t("Example", "Ejemplo") + ": ghosty agents export pia-0 > pia.yaml \xB7 ghosty agents export pia-0 --out ./pia");
14251
+ const yaml = await (await request(`/api/v2/agents/${enc11(id)}/export`)).text();
14252
+ if (!v.out) {
14253
+ if (globals.json) return out((0, import_yaml.parse)(yaml));
14254
+ return void process.stdout.write(yaml);
14111
14255
  }
14112
- const by = new Set((v.by ?? "agent").split(",").map((x) => x.trim()));
14113
- if ([...by].some((b) => b !== "agent" && b !== "day")) throw usage(t("--by takes agent, day or agent,day.", "--by acepta agent, day o agent,day."));
14114
- let rows;
14115
- const fmt2 = (x) => ({ turns: x.turns, failed: x.failed ? red(String(x.failed)) : 0, tokens: fmtTokens(x.billable), "no balance": x.noQuota || "" });
14116
- if (by.has("day") && by.has("agent")) rows = r.rows.map((x) => ({ day: x.day, agent: x.agent, ...fmt2(x) }));
14117
- else if (by.has("day")) {
14118
- const days = /* @__PURE__ */ new Map();
14119
- for (const x of r.rows) {
14120
- const d = days.get(x.day) ?? { agentId: "", agent: "", turns: 0, failed: 0, billable: 0, outputTokens: 0, cacheRead: 0, noQuota: 0 };
14121
- d.turns += x.turns;
14122
- d.failed += x.failed;
14123
- d.billable += x.billable;
14124
- d.noQuota += x.noQuota;
14125
- days.set(x.day, d);
14256
+ const dir = resolve2(v.out);
14257
+ await mkdir7(dir, { recursive: true });
14258
+ await writeFile9(join9(dir, "agent.yaml"), yaml);
14259
+ const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
14260
+ for (const slug of own) {
14261
+ const sk = await api(`/api/v2/agents/${enc11(id)}/skills/${enc11(slug)}`);
14262
+ await writeSkill(join9(dir, "skills", slug), sk);
14263
+ }
14264
+ out({ saved: dir, skills: own }, () => `${lavender("\u2713")} ${dir}/agent.yaml${own.length ? ` + skills/ (${own.join(", ")})` : ""}
14265
+ ${dim(t(`Edit it, then: ghosty apply ${v.out} --dry-run`, `Ed\xEDtalo y luego: ghosty apply ${v.out} --dry-run`))}`);
14266
+ }
14267
+ function fillSecrets(file) {
14268
+ const missing = [];
14269
+ const fill = (o) => {
14270
+ for (const [k, val] of Object.entries(o ?? {})) {
14271
+ const m = typeof val === "string" ? /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(val) : null;
14272
+ if (!m) continue;
14273
+ if (process.env[m[1]] !== void 0) o[k] = process.env[m[1]];
14274
+ else missing.push(m[1]);
14126
14275
  }
14127
- rows = [...days].map(([day3, x]) => ({ day: day3, ...fmt2(x) }));
14128
- } else rows = r.agents.map((x) => ({ agent: x.agent, ...fmt2(x) }));
14129
- const cols = [...by.has("day") ? ["day"] : [], ...by.has("agent") ? ["agent"] : [], "turns", "failed", "tokens", "no balance"];
14130
- const total = r.agents.reduce((n, a) => n + a.billable, 0);
14131
- out(r, () => `${bold(r.workspace)} ${dim(t(`last ${r.days} day(s), CDMX days`, `\xFAltimos ${r.days} d\xEDa(s), d\xEDas en hora CDMX`))}
14132
- ${table(rows, cols)}
14133
- ${dim(t(`${fmtTokens(total)} billable tokens \xB7 failed = error or stopped \xB7 no balance: ghosty turns ls --workspace ${r.workspace} --quota-hits`, `${fmtTokens(total)} tokens facturables \xB7 failed = error o detenido \xB7 sin saldo: ghosty turns ls --workspace ${r.workspace} --quota-hits`))}`);
14276
+ };
14277
+ const x = file["x-ghosty"];
14278
+ fill(x?.env);
14279
+ for (const s2 of x?.mcp ?? []) fill(s2.env), fill(s2.headers);
14280
+ return missing;
14134
14281
  }
14135
- var USAGE_DIMS = ["model", "day", "canal"];
14136
- function groupAgentUsage(rows, by) {
14137
- const groups = /* @__PURE__ */ new Map();
14138
- for (const r of rows) {
14139
- const k = by.map((d) => r[d]).join("|");
14140
- const g = groups.get(k) ?? { day: by.includes("day") ? r.day : "", model: by.includes("model") ? r.model : "", canal: by.includes("canal") ? r.canal : "", turns: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheCreation: 0, billable: 0, usd: 0 };
14141
- g.turns += r.turns;
14142
- g.inputTokens += r.inputTokens;
14143
- g.outputTokens += r.outputTokens;
14144
- g.cacheRead += r.cacheRead;
14145
- g.cacheCreation += r.cacheCreation;
14146
- g.billable += r.billable;
14147
- g.usd += r.usd;
14148
- groups.set(k, g);
14282
+ async function readSkills(dir) {
14283
+ const root = join9(dir, "skills");
14284
+ const out2 = {};
14285
+ const slugs = await readdir4(root, { withFileTypes: true }).catch(() => []);
14286
+ for (const e of slugs) {
14287
+ if (!e.isDirectory() || e.name.startsWith(".")) continue;
14288
+ const { markdown, assets } = await readSkillDir(join9(root, e.name));
14289
+ out2[e.name] = [{ path: "SKILL.md", contentBase64: Buffer.from(markdown).toString("base64") }, ...assets.map((a) => ({ path: a.name, contentBase64: a.contentBase64 }))];
14149
14290
  }
14150
- return [...groups.values()].sort((a, b) => a.day.localeCompare(b.day) || b.usd - a.usd);
14291
+ return out2;
14151
14292
  }
14152
- async function agentUsage(id, v) {
14153
- if (v.by?.trim() === "visitor") return anonVisitorUsage(id, v);
14154
- const by = (v.by ?? "model").split(",").map((x) => x.trim()).filter(Boolean);
14155
- if (!by.length || by.some((b) => !USAGE_DIMS.includes(b))) throw usage(t("With --agent, --by takes model, day and/or canal (or visitor alone).", "Con --agent, --by acepta model, day y/o canal (o visitor solo)."), "ghosty usage --agent <id> --by model,day --cost");
14156
- const r = await api(`/api/v2/agents/${enc10(id)}/usage?days=${enc10(v.days ?? "14")}`);
14157
- const groups = groupAgentUsage(r.rows, by);
14158
- const rows = groups.map((g) => ({ ...Object.fromEntries(by.map((d) => [d, g[d]])), turns: g.turns, tokens: fmtTokens(g.billable), "cache read": fmtTokens(g.cacheRead), out: fmtTokens(g.outputTokens), ...v.cost ? { usd: `$${g.usd.toFixed(2)}` } : {} }));
14159
- const cols = [...by, "turns", "tokens", "cache read", "out", ...v.cost ? ["usd"] : []];
14160
- const turns = r.rows.reduce((n, x) => n + x.turns, 0);
14161
- const foot = v.cost ? t(`$${r.totalUsd.toFixed(2)} USD at provider list price (cache writes included) \xB7 ${turns} turn(s)`, `$${r.totalUsd.toFixed(2)} USD a precio de lista del proveedor (con escritura de cach\xE9) \xB7 ${turns} turno(s)`) : t(`${turns} turn(s) \xB7 USD per row: --cost`, `${turns} turno(s) \xB7 USD por fila: --cost`);
14162
- out({ ...r, groups }, () => rows.length ? `${bold(r.agent)} ${dim(t(`last ${r.days} day(s), CDMX days`, `\xFAltimos ${r.days} d\xEDa(s), d\xEDas en hora CDMX`))}
14163
- ${table(rows, cols)}
14164
- ${dim(foot)}` : dim(t(`No usage in ${r.days} day(s).`, `Sin uso en ${r.days} d\xEDa(s).`)));
14293
+ var line = (c) => c.op === "!" ? dim(`! ${c.what}`) : `${c.op === "+" ? lavender("+") : c.op === "-" ? red("-") : "~"} ${c.what}`;
14294
+ async function applyCmd(target, v) {
14295
+ if (!target) throw usage(t("Missing file or directory.", "Falta el archivo o el directorio."), t("Example: ghosty apply pia.yaml --dry-run \xB7 ghosty apply ./pia (from agents export --out)", "Ejemplo: ghosty apply pia.yaml --dry-run \xB7 ghosty apply ./pia (de agents export --out)"));
14296
+ const isDir = (await stat2(target).catch(() => null))?.isDirectory() ?? false;
14297
+ const text = await readFile11(isDir ? join9(target, "agent.yaml") : target, "utf8");
14298
+ const file = (0, import_yaml.parse)(text);
14299
+ if (!file || typeof file !== "object") throw usage(t(`${target} is not a YAML/JSON agent file.`, `${target} no es un archivo de agente YAML/JSON.`));
14300
+ const missing = fillSecrets(file);
14301
+ if (missing.length && !globals.json) info(dim(t(`Secrets not in your environment keep their current value: ${missing.join(", ")}`, `Los secretos que no est\xE1n en tu entorno conservan su valor actual: ${missing.join(", ")}`)));
14302
+ const files = isDir ? await readSkills(target) : void 0;
14303
+ let id = v.agent ?? file["x-ghosty"]?.id;
14304
+ if (v.create) {
14305
+ const x = file["x-ghosty"] ?? {};
14306
+ const name = v.name ?? file.title;
14307
+ if (!name) throw usage(t("The file has no title: pass --name.", "El archivo no tiene t\xEDtulo: pasa --name."));
14308
+ if (v["dry-run"]) return out({ create: true, name, engine: x.engine, workspace: x.workspace ?? null }, () => `${lavender("+")} ${t("agent", "agente")} ${name} (${x.engine ?? t("default engine", "motor por default")}${x.workspace ? t(` in ${x.workspace}`, ` en ${x.workspace}`) : ""})
14309
+ ${dim(t("Then every setting of the file is applied. Dry run: nothing created.", "Despu\xE9s se aplica cada ajuste del archivo. Simulaci\xF3n: no se cre\xF3 nada."))}`);
14310
+ requireYesIfHeadless(v.yes, `ghosty apply ${target} --create`);
14311
+ const created = await api("/api/v2/me/agents", { method: "POST", body: { name, ...x.engine ? { engine: x.engine } : {}, ...x.model ? { model: x.model } : {}, ...x.workspace ? { workspace: x.workspace } : {}, ...x.channels ? { channels: x.channels } : {} } });
14312
+ id = created.id;
14313
+ info(dim(t(`Created ${name} (${id}); applying the rest\u2026`, `${name} creado (${id}); aplicando lo dem\xE1s\u2026`)));
14314
+ } else {
14315
+ id = await resolveAgent(id);
14316
+ if (!id) throw usage(t("The file has no agent id.", "El archivo no trae id de agente."), t("Point it at one with --agent <name|id>, or make a new one with --create.", "Ap\xFAntalo a uno con --agent <nombre|id>, o crea uno nuevo con --create."));
14317
+ }
14318
+ const spec = JSON.stringify(v.create ? { ...file, title: void 0 } : file);
14319
+ const body = { spec, ...files ? { files } : {}, ...v.prune ? { prune: true } : {}, ...v["no-notify"] ? { notify: false } : {} };
14320
+ const path = `/api/v2/agents/${enc11(id)}/apply`;
14321
+ const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
14322
+ const actionable = plan.plan.filter((c) => c.op !== "!");
14323
+ const text2 = plan.plan.map(line).join("\n") || dim(t("No changes: the agent already matches the file.", "Sin cambios: el agente ya coincide con el archivo."));
14324
+ if (v["dry-run"] || !actionable.length) return out(plan, () => `${bold(id)}
14325
+ ${text2}${actionable.length ? `
14326
+ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}` : ""}`);
14327
+ const retry = `ghosty apply ${target}${v.agent ? ` --agent ${v.agent}` : ""}${v.prune ? " --prune" : ""}`;
14328
+ if (!v.create) requireYesIfHeadless(v.yes, retry);
14329
+ if (!v.create) await confirm(`${text2}
14330
+ ${t(`Apply ${actionable.length} change(s) to ${id}?`, `\xBFAplicar ${actionable.length} cambio(s) a ${id}?`)}`, { yes: v.yes, retry });
14331
+ const r = await api(path, { method: "POST", body });
14332
+ out(r, () => `${lavender("\u2713")} ${t(`${r.applied?.length ?? 0} change(s) applied to ${id}.`, `${r.applied?.length ?? 0} cambio(s) aplicados a ${id}.`)}${r.failed ? `
14333
+ ${red(t(`Stopped at \xAB${r.failed.what}\xBB: ${serverText(r.failed.error)}`, `Se detuvo en \xAB${r.failed.what}\xBB: ${r.failed.error}`))}` : ""}`);
14165
14334
  }
14166
- async function anonVisitorUsage(id, v) {
14167
- const r = await api(`/api/v2/agents/${enc10(id)}/usage?days=${enc10(v.days ?? "14")}&visitors=1`);
14168
- const vs = r.visitors ?? { list: [], total: { visitors: 0, accounts: 0, usd: 0 } };
14169
- const yes = t("yes", "s\xED");
14170
- const rows = vs.list.map((x) => ({
14171
- visitor: x.visitor.slice(0, 8),
14172
- first: x.firstAt ? x.firstAt.slice(0, 16).replace("T", " ") : "",
14173
- msgs: x.messages,
14174
- usd: `$${x.usd.toFixed(3)}`,
14175
- page: x.page ? yes : "",
14176
- pdf: x.pdfs || "",
14177
- account: x.accountAt ? x.accountAt.slice(0, 10) : ""
14178
- }));
14179
- const { visitors, accounts, usd } = vs.total;
14180
- const perVisitor = visitors ? usd / visitors : 0;
14181
- const perAccount = accounts ? usd / accounts : null;
14182
- const foot = t(
14183
- `${visitors} visitor(s) \xB7 ${accounts} created an account (${visitors ? Math.round(accounts / visitors * 100) : 0}%) \xB7 $${usd.toFixed(2)} USD \xB7 $${perVisitor.toFixed(3)} per visitor${perAccount !== null ? ` \xB7 $${perAccount.toFixed(2)} per new account` : ""} (provider list price; times in UTC)`,
14184
- `${visitors} visitante(s) \xB7 ${accounts} crearon cuenta (${visitors ? Math.round(accounts / visitors * 100) : 0}%) \xB7 $${usd.toFixed(2)} USD \xB7 $${perVisitor.toFixed(3)} por visitante${perAccount !== null ? ` \xB7 $${perAccount.toFixed(2)} por cuenta nueva` : ""} (precio de lista del proveedor; horas en UTC)`
14185
- );
14186
- out(r, () => rows.length ? `${bold(r.agent)} ${dim(t(`/c without an account, last ${r.days} day(s)`, `/c sin cuenta, \xFAltimos ${r.days} d\xEDa(s)`))}
14187
- ${table(rows, ["visitor", "first", "msgs", "usd", "page", "pdf", "account"])}
14188
- ${dim(foot)}` : dim(t(`No visitors in ${r.days} day(s).`, `Sin visitantes en ${r.days} d\xEDa(s).`)));
14335
+
14336
+ // src/commands/plan.ts
14337
+ async function planCmd(sub, slug, v, context) {
14338
+ if (sub !== "apply") throw usage(t(`Unknown plan subcommand: ${sub ?? "(none)"}.`, `Subcomando desconocido de plan: ${sub ?? "(ninguno)"}.`), "ghosty plan apply <workspace> [--dry-run]");
14339
+ slug ??= context;
14340
+ if (!slug) throw usage(t("Missing workspace.", "Falta el espacio."), "ghosty plan apply <workspace> [--dry-run] (or ghosty use <workspace>)");
14341
+ const path = `/api/v2/workspaces/${encodeURIComponent(slug)}/plan/apply`;
14342
+ const plan = await api(path, { method: "POST", body: { dryRun: true } });
14343
+ const money2 = (n) => `$${n.toLocaleString("es-MX")} MXN`;
14344
+ const text = [
14345
+ `${bold(plan.workspace)} ${dim(plan.scheduledFor ? t(`scheduled for ${plan.scheduledFor.slice(0, 10)}`, `programado para el ${plan.scheduledFor.slice(0, 10)}`) : "")}`,
14346
+ ...plan.changes.map((c) => ` ~ ${c.label}: ${String(c.from)} \u2192 ${String(c.to)}`),
14347
+ ` ${t("monthly", "al mes")}: ${money2(plan.monthlyTotal.from)} \u2192 ${money2(plan.monthlyTotal.to)}`
14348
+ ].join("\n");
14349
+ if (v["dry-run"]) return out(plan, () => `${text}
14350
+ ${dim(t("Dry run: nothing applied.", "Simulaci\xF3n: no se aplic\xF3 nada."))}`);
14351
+ requireYesIfHeadless(v.yes, `ghosty plan apply ${slug}`);
14352
+ await confirm(`${text}
14353
+ ${t("Apply this plan now?", "\xBFAplicar este plan ya?")}`, { yes: v.yes, retry: `ghosty plan apply ${slug}` });
14354
+ const r = await api(path, { method: "POST", body: {} });
14355
+ out(r, () => `${lavender("\u2713")} ${r.applied ? t(`Plan applied to ${r.workspace}.`, `Plan aplicado a ${r.workspace}.`) : t("Nothing applied.", "No se aplic\xF3 nada.")}`);
14356
+ }
14357
+ async function boxesUsage(slug) {
14358
+ if (!slug) throw usage(t("Which workspace?", "\xBFQu\xE9 espacio?"), "ghosty usage --boxes --workspace <slug> (or ghosty use <workspace>)");
14359
+ const r = await api(`/api/v2/workspaces/${encodeURIComponent(slug)}/boxes`);
14360
+ const rows = r.agents.map((a) => ({ agent: a.name ?? a.id, engine: a.engine, "per box": a.conversationsPerBox ?? "", boxes: a.boxes ?? "", on: a.boxesOn ?? "", spares: a.spares ?? "", "running now": a.activeTurns ?? "", ...a.error ? { note: a.error } : {} }));
14361
+ const head = `${bold(r.workspace)} ${dim(t(`plan: ${r.plan.parallel} in parallel \xB7 up to ${r.plan.maxBoxes} box(es) \xB7 ${r.plan.hotBoxes} always-on spare(s)`, `plan: ${r.plan.parallel} en paralelo \xB7 hasta ${r.plan.maxBoxes} caja(s) \xB7 ${r.plan.hotBoxes} spare(s) siempre encendido(s)`))}`;
14362
+ const space = r.space ? (r.space.used > r.space.limit ? (s2) => s2 : dim)(
14363
+ t(
14364
+ `${r.space.used} of ${r.space.limit} box(es) in use \xB7 ${r.space.spares} spare \xB7 ${r.space.asleep} asleep${r.space.used > r.space.limit ? " \xB7 OVER the limit" : ""}`,
14365
+ `${r.space.used} de ${r.space.limit} caja(s) \xB7 ${r.space.spares} spare \xB7 ${r.space.asleep} dormida(s)${r.space.used > r.space.limit ? " \xB7 SOBRE el tope" : ""}`
14366
+ )
14367
+ ) : dim(t("(could not read the host)", "(no pude leer el host)"));
14368
+ out(r, () => `${head}
14369
+ ${space}
14370
+ ${table(rows, ["agent", "engine", "per box", "boxes", "on", "spares", "running now"])}
14371
+ ${dim(t(`${r.totals.activeTurns} turn(s) running`, `${r.totals.activeTurns} turno(s) corriendo`))}`);
14189
14372
  }
14190
14373
 
14191
14374
  // src/commands/demos.ts
@@ -14193,12 +14376,12 @@ import { randomUUID as randomUUID2 } from "node:crypto";
14193
14376
  import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
14194
14377
  import { homedir as homedir5 } from "node:os";
14195
14378
  import { dirname as dirname4, join as join10 } from "node:path";
14196
- var enc11 = encodeURIComponent;
14379
+ var enc12 = encodeURIComponent;
14197
14380
  var hora = (iso) => new Date(iso).toLocaleString("es-MX", { timeZone: "America/Mexico_City", hour12: false, dateStyle: "short", timeStyle: "short" });
14198
14381
  async function history(agent, v) {
14199
14382
  if (!agent) throw usage(t("Missing agent.", "Falta el agente."), "ghosty demos history <agent> [--since 2d] [--session web:\u2026] [--timeline] [--full]");
14200
14383
  const q = new URLSearchParams({ since: v.since ?? "7d", ...v.session ? { session: v.session } : {}, ...v.timeline ? { timeline: "1" } : {}, ...v.full ? { full: "1" } : {} });
14201
- const r = await api(`/api/v2/agents/${enc11(agent)}/demo/conversations?${q}`);
14384
+ const r = await api(`/api/v2/agents/${enc12(agent)}/demo/conversations?${q}`);
14202
14385
  out(r, () => {
14203
14386
  const lines = [`${bold(r.agent.name ?? r.agent.id)} ${dim(`/p/${r.agent.slug ?? "\u2014"} \xB7 ${t("since", "desde")} ${hora(r.since)} \xB7 ${t("clones", "clones")}: ${r.clones.length}`)}`];
14204
14387
  for (const c of r.clones) lines.push(dim(` ${t("clone", "clon")} ${c.id} (${hora(c.createdAt)})${c.sponsored ? t(" \xB7 on your key", " \xB7 con tu llave") : ""}`));
@@ -14227,7 +14410,7 @@ function anonCookieOf(setCookie) {
14227
14410
  async function visitorCookie(base, slug, fresh) {
14228
14411
  const jar = await readJar();
14229
14412
  if (jar[slug] && !fresh) return jar[slug];
14230
- const res = await fetch(`${base}/p/${enc11(slug)}`, { redirect: "manual" });
14413
+ const res = await fetch(`${base}/p/${enc12(slug)}`, { redirect: "manual" });
14231
14414
  if (res.status === 404) throw new CliError(t(`No demo \xAB${slug}\xBB (the link is name-code, e.g. guardias-k3x9q2).`, `No hay demo \xAB${slug}\xBB (la liga es nombre-c\xF3digo, p. ej. guardias-k3x9q2).`), EXIT.API);
14232
14415
  const cookie = anonCookieOf(res.headers.get("set-cookie"));
14233
14416
  if (!cookie) throw new CliError(t("The demo didn't hand out a visitor cookie.", "El demo no entreg\xF3 cookie de visitante."), EXIT.API);
@@ -14239,9 +14422,9 @@ async function visitorCookie(base, slug, fresh) {
14239
14422
  async function visitorTurn(base, slug, cookie, text) {
14240
14423
  const turnId = randomUUID2();
14241
14424
  const ctrl = new AbortController();
14242
- const ev = await fetch(`${base}/p/${enc11(slug)}/events`, { headers: { cookie, accept: "text/event-stream" }, signal: ctrl.signal });
14425
+ const ev = await fetch(`${base}/p/${enc12(slug)}/events`, { headers: { cookie, accept: "text/event-stream" }, signal: ctrl.signal });
14243
14426
  if (!ev.ok) throw await toError(ev);
14244
- const sent = await fetch(`${base}/p/${enc11(slug)}/messages`, { method: "POST", headers: { cookie, "content-type": "application/json" }, body: JSON.stringify({ content: text, turnId }) });
14427
+ const sent = await fetch(`${base}/p/${enc12(slug)}/messages`, { method: "POST", headers: { cookie, "content-type": "application/json" }, body: JSON.stringify({ content: text, turnId }) });
14245
14428
  if (!sent.ok) {
14246
14429
  ctrl.abort();
14247
14430
  const body = await sent.text();
@@ -14271,7 +14454,7 @@ async function visitorTurn(base, slug, cookie, text) {
14271
14454
  async function recycle(agent) {
14272
14455
  for (let i = 0; i < 10; i++) {
14273
14456
  try {
14274
- await api(`/api/v2/agents/${enc11(agent)}/box`, { method: "POST", body: { recycle: true } });
14457
+ await api(`/api/v2/agents/${enc12(agent)}/box`, { method: "POST", body: { recycle: true } });
14275
14458
  return;
14276
14459
  } catch (e) {
14277
14460
  if (!(e instanceof CliError) || !/turno|turn/i.test(e.message)) throw e;
@@ -14446,7 +14629,7 @@ ${dim(t("Times in UTC. --full for the whole detail; --json for everything.", "Ho
14446
14629
  }
14447
14630
 
14448
14631
  // src/commands/factory.ts
14449
- var enc12 = encodeURIComponent;
14632
+ var enc13 = encodeURIComponent;
14450
14633
  var money = (n) => n === null || n === void 0 ? "\u2014" : `$${n.toFixed(2)}`;
14451
14634
  var mins = (n) => n === null || n === void 0 ? "\u2014" : n >= 120 ? `${(n / 60).toFixed(1)} h` : `${n} min`;
14452
14635
  var hhmm = (iso) => new Date(iso).toLocaleTimeString("es-MX", { hour: "2-digit", minute: "2-digit", hour12: false });
@@ -14455,7 +14638,7 @@ async function factoryCmd(sub, verb, ref, title, v, context) {
14455
14638
  if (sub !== "runs") throw usage(t(`Unknown factory subcommand: ${sub ?? "(none)"}.`, `Subcomando de factory desconocido: ${sub ?? "(ninguno)"}.`), "ghosty factory runs ls|show|why|stats|rename \u2026");
14456
14639
  const ws = v.workspace ?? context;
14457
14640
  if (!ws) throw usage(t("Which workspace?", "\xBFQu\xE9 espacio?"), t("Add --workspace SLUG (or set one with ghosty use SLUG).", "Agrega --workspace SLUG (o fija uno con ghosty use SLUG)."));
14458
- const base = `/api/v2/workspaces/${enc12(ws)}/factory/runs`;
14641
+ const base = `/api/v2/workspaces/${enc13(ws)}/factory/runs`;
14459
14642
  if (!verb || verb === "ls" || verb === "list") {
14460
14643
  const qs = new URLSearchParams({ ...v.room ? { room: v.room } : {}, ...v.status ? { status: v.status } : {}, ...v.limit ? { limit: v.limit } : {} });
14461
14644
  const r = await api(`${base}?${qs}`);
@@ -14465,7 +14648,7 @@ ${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r
14465
14648
  }
14466
14649
  if (verb === "stats") {
14467
14650
  const qs = new URLSearchParams({ since: v.since ?? "30d", ...v.repo ? { repo: v.repo } : {} });
14468
- const { stats: s2, workspace } = await api(`/api/v2/workspaces/${enc12(ws)}/factory/stats?${qs}`);
14651
+ const { stats: s2, workspace } = await api(`/api/v2/workspaces/${enc13(ws)}/factory/stats?${qs}`);
14469
14652
  return out({ workspace, stats: s2 }, () => {
14470
14653
  if (!s2.runs) return dim(t(`No factory runs in ${workspace} in the last ${s2.sinceDays} days.`, `No hay pedidos de la f\xE1brica en ${workspace} en los \xFAltimos ${s2.sinceDays} d\xEDas.`));
14471
14654
  return [
@@ -14479,7 +14662,7 @@ ${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r
14479
14662
  });
14480
14663
  }
14481
14664
  if (!ref) throw usage(t("Missing run.", "Falta el pedido."), `ghosty factory runs ${verb} <run> --workspace ${ws}`);
14482
- const path = `${base}/${enc12(ref.replace(/^#/, ""))}`;
14665
+ const path = `${base}/${enc13(ref.replace(/^#/, ""))}`;
14483
14666
  if (verb === "show" && v.timing) {
14484
14667
  const { timing: tm, workspace } = await api(`${path}/timing`);
14485
14668
  return out({ workspace, timing: tm }, () => {
@@ -14619,7 +14802,7 @@ async function settingsCmd(sub, key, value) {
14619
14802
 
14620
14803
  // src/commands/support.ts
14621
14804
  import { readFile as readFile14 } from "node:fs/promises";
14622
- var enc13 = encodeURIComponent;
14805
+ var enc14 = encodeURIComponent;
14623
14806
  var cdmx = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
14624
14807
  async function supportThreads(email, v) {
14625
14808
  const q = new URLSearchParams({ user: email, since: v.since ?? "30d", ...v.surface ? { surface: v.surface } : {} });
@@ -14639,7 +14822,7 @@ ${table(rows, ["last", "agent", "conversation", "title", "kind", "listed", "turn
14639
14822
  ${dim(t("Read one: ghosty conversations show <agent> <conversation> --why \xB7 times in CDMX \xB7 listed = in their app list", "Leer uno: ghosty conversations show <agente> <conversaci\xF3n> --why \xB7 horas CDMX \xB7 listed = sale en su lista de la app"))}`);
14640
14823
  }
14641
14824
  async function supportThread(agent, sid, v) {
14642
- const r = await api(`/api/v2/support/threads/${enc13(agent)}/${enc13(sid)}?tail=${enc13(v.limit ?? "40")}${v.tools ? "&tools=1" : ""}`);
14825
+ const r = await api(`/api/v2/support/threads/${enc14(agent)}/${enc14(sid)}?tail=${enc14(v.limit ?? "40")}${v.tools ? "&tools=1" : ""}`);
14643
14826
  out(r, () => {
14644
14827
  if (v.tools && r.tools) {
14645
14828
  const x = r.tools;
@@ -14672,7 +14855,7 @@ async function cleanupConversations(action, agent, v) {
14672
14855
  if (!agent) throw usage(t("Missing agent.", "Falta el agente."), ex);
14673
14856
  if (v.kind !== "test" && v.kind !== "channel" && v.kind !== "personal") throw usage(t("--kind takes test, channel or personal.", "--kind acepta test, channel o personal."), ex);
14674
14857
  if (action === "rm" && v.kind === "channel") throw usage(t("Customer conversations (channel) are archived, never deleted: their memory keeps serving them.", "Los hilos de clientes (channel) se archivan, nunca se borran: su memoria los sigue atendiendo."), ex);
14675
- const path = `/api/v2/agents/${enc13(agent)}/conversations/cleanup`;
14858
+ const path = `/api/v2/agents/${enc14(agent)}/conversations/cleanup`;
14676
14859
  const body = { kind: v.kind, action, ...v.before ? { before: v.before } : {} };
14677
14860
  const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
14678
14861
  const verb = action === "rm" ? t("delete (rows + agent memory)", "borrar (filas + memoria del agente)") : t("archive (hidden; memory stays)", "archivar (se ocultan; la memoria se queda)");
@@ -14687,7 +14870,7 @@ ${t("Go ahead?", "\xBFSigo?")}`, { yes: v.yes, retry: `ghosty conversations ${ac
14687
14870
  out(r, () => `${lavender("\u2713")} ${t(`${r.threads} conversation(s) ${action === "rm" ? "deleted" : "archived"}.`, `${r.threads} conversaci\xF3n(es) ${action === "rm" ? "borradas" : "archivadas"}.`)}`);
14688
14871
  }
14689
14872
  async function turnStats(v) {
14690
- const r = await api(`/api/v2/support/turn-stats?since=${enc13(v.since ?? "14d")}${v.all ? "&all=1" : ""}`);
14873
+ const r = await api(`/api/v2/support/turn-stats?since=${enc14(v.since ?? "14d")}${v.all ? "&all=1" : ""}`);
14691
14874
  out(
14692
14875
  r,
14693
14876
  () => [
@@ -14707,7 +14890,7 @@ async function fleetBoxes() {
14707
14890
  ${table(rows, ["template", "boxes", "on", "asleep", "ram", "vcpu", "ram on"])}`);
14708
14891
  }
14709
14892
  async function appAgentsOf(email, client) {
14710
- const r = await api(`/api/v2/support/app-agents?user=${enc13(email)}${client && client !== "all" ? `&client=${enc13(client)}` : ""}`);
14893
+ const r = await api(`/api/v2/support/app-agents?user=${enc14(email)}${client && client !== "all" ? `&client=${enc14(client)}` : ""}`);
14711
14894
  const rows = r.agentes.map((a) => ({ id: a.id, name: a.nombre, engine: a.motor ?? "", model: a.modelo ?? "", tone: a.tono ?? t("(by position)", "(por posici\xF3n)"), space: a.espacio?.tipo === "workspace" ? a.espacio.nombre : a.espacio?.tipo ?? "", last: a.ultimaActividad ? cdmx(a.ultimaActividad) : "" }));
14712
14895
  const phones = (r.devices ?? []).map((d) => `${d.platform} ${d.appVersion ?? ""}${d.appBuild ? ` (build ${d.appBuild})` : " (build ?)"} \xB7 ${t("seen", "visto")} ${cdmx(d.lastSeenAt)}`.replace(/\s+/g, " "));
14713
14896
  out(r, () => `${bold(r.user)} ${dim(r.client ? t(`as the ${r.client} app sees it`, `como lo ve la app de ${r.client}`) : t("all apps", "todas las apps"))}
@@ -14718,7 +14901,7 @@ ${dim(t("No phones registered for push.", "Sin tel\xE9fonos registrados para avi
14718
14901
  }
14719
14902
  async function pushLog(email, v) {
14720
14903
  if (!email) throw usage(t("Missing --user.", "Falta --user."), "ghosty push log --user ana@empresa.com [--since 2d]");
14721
- const r = await api(`/api/v2/support/push-log?user=${enc13(email)}&since=${enc13(v.since ?? "2d")}${v.limit ? `&limit=${enc13(v.limit)}` : ""}`);
14904
+ const r = await api(`/api/v2/support/push-log?user=${enc14(email)}&since=${enc14(v.since ?? "2d")}${v.limit ? `&limit=${enc14(v.limit)}` : ""}`);
14722
14905
  const rows = r.pushes.map((p) => ({ at: cdmx(p.at), kind: p.kind, to: p.platform, status: p.status === "sent" ? p.status : red(p.status), thread: p.threadKind ? `${p.threadId?.slice(0, 18)} (${p.threadKind})` : (p.threadId ?? "").slice(0, 24), title: (p.title ?? "").slice(0, 40), detail: p.detail ?? "" }));
14723
14906
  out(r, () => rows.length ? `${bold(r.user)}
14724
14907
  ${table(rows, ["at", "kind", "to", "status", "thread", "title", "detail"])}` : dim(t(`No pushes for ${r.user} in that range (logged since 2026-10-01).`, `Sin avisos para ${r.user} en ese rango (se registran desde el 1-oct).`)));
@@ -14756,24 +14939,24 @@ ${dim(t(`Try it on your phone: ghosty novedades test ${r.id}`, `Pru\xE9bala en t
14756
14939
  if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
14757
14940
  const b = await fields();
14758
14941
  if (!Object.keys(b).length) throw usage(t("Nothing to change.", "Nada que cambiar."), ex);
14759
- const r = await api(`${base}/${enc13(id)}`, { method: "PATCH", body: b });
14942
+ const r = await api(`${base}/${enc14(id)}`, { method: "PATCH", body: b });
14760
14943
  return out(r, () => `${lavender("\u2713")} ${line2(r)}`);
14761
14944
  }
14762
14945
  case "test": {
14763
14946
  if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
14764
- const r = await api(`${base}/${enc13(id)}`, { method: "POST", body: { push: "test" } });
14947
+ const r = await api(`${base}/${enc14(id)}`, { method: "POST", body: { push: "test" } });
14765
14948
  return out(r, () => `${lavender("\u2713")} ${t(`Sent to your phone (${r.enviados} device(s)).`, `Enviada a tu tel\xE9fono (${r.enviados} dispositivo(s)).`)}`);
14766
14949
  }
14767
14950
  case "send":
14768
14951
  case "schedule": {
14769
14952
  if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
14770
14953
  if (v.at !== void 0) {
14771
- const r2 = await api(`${base}/${enc13(id)}`, { method: "PATCH", body: { schedule: v.at || null } });
14954
+ const r2 = await api(`${base}/${enc14(id)}`, { method: "PATCH", body: { schedule: v.at || null } });
14772
14955
  return out(r2, () => `${lavender("\u2713")} ${line2(r2)}`);
14773
14956
  }
14774
14957
  requireYesIfHeadless(v.yes, `ghosty novedades send ${id}`);
14775
14958
  await confirm(t("Push this announcement to every recipient now?", "\xBFMandar ya el push de esta novedad a todos sus destinatarios?"), { yes: v.yes, retry: `ghosty novedades send ${id}` });
14776
- const r = await api(`${base}/${enc13(id)}`, { method: "POST", body: { push: "all" } });
14959
+ const r = await api(`${base}/${enc14(id)}`, { method: "POST", body: { push: "all" } });
14777
14960
  return out(r, () => `${lavender("\u2713")} ${t(`Pushed to ${r.enviados} device(s).`, `Enviada a ${r.enviados} dispositivo(s).`)}`);
14778
14961
  }
14779
14962
  case "rm":
@@ -14781,7 +14964,7 @@ ${dim(t(`Try it on your phone: ghosty novedades test ${r.id}`, `Pru\xE9bala en t
14781
14964
  if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
14782
14965
  requireYesIfHeadless(v.yes, `ghosty novedades rm ${id}`);
14783
14966
  await confirm(t(`Delete announcement ${id}?`, `\xBFBorrar la novedad ${id}?`), { yes: v.yes, retry: `ghosty novedades rm ${id}` });
14784
- return out(await api(`${base}/${enc13(id)}`, { method: "DELETE" }), () => `${lavender("\u2713")} ${t("Deleted.", "Borrada.")}`);
14967
+ return out(await api(`${base}/${enc14(id)}`, { method: "DELETE" }), () => `${lavender("\u2713")} ${t("Deleted.", "Borrada.")}`);
14785
14968
  }
14786
14969
  default:
14787
14970
  throw usage(t(`Unknown novedades subcommand: ${sub}.`, `Subcomando de novedades desconocido: ${sub}.`), ex);
@@ -14850,7 +15033,7 @@ ${dim(r.case.id)}`);
14850
15033
  }
14851
15034
  if (!id) throw usage(t("Missing the case id.", "Falta el id del caso."), "ghosty support ls");
14852
15035
  if (sub === "show") {
14853
- const r = v.all ? await api(`/api/v2/support/cases/${enc13(id)}`) : await api(`/api/v2/me/support/${enc13(id)}`);
15036
+ const r = v.all ? await api(`/api/v2/support/cases/${enc14(id)}`) : await api(`/api/v2/me/support/${enc14(id)}`);
14854
15037
  const c = r.case;
14855
15038
  return out(r, () => {
14856
15039
  const who2 = (a) => a === "staff" ? "Ghosty" : a === "agent" ? t("agent", "agente") : a === "customer" ? t("customer", "cliente") : a;
@@ -14871,18 +15054,18 @@ ${m.body}${m.attachments.length ? dim(`
14871
15054
  }
14872
15055
  if (sub === "reply") {
14873
15056
  if (!v.body && !v.resolve) throw usage(t("Missing --body.", "Falta --body."), 'ghosty support reply <id> --body "\u2026" [--resolve]');
14874
- const r = v.all || v.resolve ? await api(`/api/v2/support/cases/${enc13(id)}`, { method: "POST", body: { intent: "reply", body: v.body ?? "", resolve: Boolean(v.resolve) } }) : await api(`/api/v2/me/support/${enc13(id)}`, { method: "POST", body: { body: v.body } });
15057
+ const r = v.all || v.resolve ? await api(`/api/v2/support/cases/${enc14(id)}`, { method: "POST", body: { intent: "reply", body: v.body ?? "", resolve: Boolean(v.resolve) } }) : await api(`/api/v2/me/support/${enc14(id)}`, { method: "POST", body: { body: v.body } });
14875
15058
  return out(r, () => `${lavender("\u2713")} ${r.case.code} \xB7 ${STATUS(r.case.status)}`);
14876
15059
  }
14877
15060
  if (sub === "link") {
14878
15061
  const ref = v.ref;
14879
15062
  if (!ref) throw usage(t("Missing the issue or PR.", "Falta el issue o PR."), "ghosty support link <id> owner/repo#12 (or the issue/PR URL)");
14880
- const r = await api(`/api/v2/support/cases/${enc13(id)}`, { method: "POST", body: { intent: "link", ref } });
15063
+ const r = await api(`/api/v2/support/cases/${enc14(id)}`, { method: "POST", body: { intent: "link", ref } });
14881
15064
  return out(r, () => `${lavender("\u2713")} ${r.case.code} \u2192 ${bold(r.ref)} ${dim(`(${r.github.title})`)}${r.alsoWaiting ? `
14882
15065
  ${dim(t(`${r.alsoWaiting} more cases wait for the same fix: all get "fixed" when it closes.`, `Otros ${r.alsoWaiting} casos esperan el mismo arreglo: a todos les llega \xABya qued\xF3\xBB al cerrarse.`))}` : ""}`);
14883
15066
  }
14884
15067
  if (sub === "resolve" || sub === "reopen") {
14885
- const r = await api(`/api/v2/support/cases/${enc13(id)}`, { method: "POST", body: { intent: sub } });
15068
+ const r = await api(`/api/v2/support/cases/${enc14(id)}`, { method: "POST", body: { intent: sub } });
14886
15069
  return out(r, () => `${lavender("\u2713")} ${r.case.code} \xB7 ${STATUS(r.case.status)}`);
14887
15070
  }
14888
15071
  throw usage(t(`Unknown support subcommand: ${sub}.`, `Subcomando de support desconocido: ${sub}.`), "ghosty support ls | open | show | reply | resolve | reopen | link");
@@ -14907,14 +15090,16 @@ ${bold("Session")}
14907
15090
  ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
14908
15091
  bags ls|create|set|rm <workspace> ["bag"] Token bags its agents spend from [--tokens 20M] [--recurring on|off] [--hard-cap on|off] [--note]
14909
15092
  spaces ls Your workspaces (role, plan, combo) [--user EMAIL: someone else's]
14910
- spaces create <slug> New workspace owned by you (trial) [--combo teams|sales|both: staff] [--dry-run]
15093
+ spaces create <slug> New workspace owned by you (trial) [--combo teams|sales|both|none: staff] [--dry-run]
14911
15094
  spaces join|leave <workspace> Enter or leave a client's workspace as staff (no seat; logged)
14912
- spaces set <workspace> --combo sales teams | sales | both (without Sales its boards are invisible) [--model-cap 0.25|1|4|none: most expensive model] [--house-claude on|off]
15095
+ spaces set <workspace> --combo sales teams | sales | both | none (without Sales its boards are invisible) [--model-cap 0.25|1|4|none: most expensive model] [--house-claude on|off]
14913
15096
  spaces renew <workspace> --until DATE Move \xABpaid until\xBB (logged) [--note] [--dry-run]
14914
15097
  spaces rename <old> <new> Change its slug only [--dry-run]
14915
15098
  spaces archive|restore <workspace> Archive it (out of /app and spaces ls; agents and data stay) or bring it back (owner or staff) [--dry-run]
14916
15099
  spaces transfer <workspace> --to EMAIL New owner; agents go with it (ACP boxes move with their /data, pool boxes are recycled) [--keep-as admin|member|none] [--copy-keys] [--dry-run]
14917
15100
  spaces log <workspace> Its log: transfers, staff in/out, renewals (owner or staff) [--since 30d] [--action workspace]
15101
+ spaces members ls|add|rm <workspace> [email] Its people, without the Members screen (owner, admin or staff) [--role member|admin] [--until 2026-10-28|14d: removed automatically]
15102
+ access why <email> <agent-id> Why someone sees an agent or a board (every reason, and when temporary access expires) [--workspace W --board B]
14918
15103
  staff ls [staff-email] [client-email] Ghosty staff assigned to client accounts (staff) [--all: also revoked]
14919
15104
  staff assign|unassign <staff> <client> Staff sees and runs that account's agents, documents, databases and boards as themselves (no keys, billing or transfers; logged) [--reason]
14920
15105
  settings ls|set|unset <KEY> [value] Platform switches (staff): who answers /c without an account, native subagents\u2026 [+id/-id edits a list]
@@ -14985,7 +15170,7 @@ ${bold("Configure an agent")}
14985
15170
  channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
14986
15171
  channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
14987
15172
  channels wa groups ls|enable|disable|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--wake mention|all] [--name "New name"] [--dry-run]
14988
- boards ls|create|assign|move-cards|rm A workspace's boards: create (--columns-from), where an agent sends cards, move cards
15173
+ boards ls|create|assign|move-cards|access|rm A workspace's boards: create (--columns-from), where an agent sends cards, move cards, who sees each one
14989
15174
  cards ls <id> Recent cards on its sales board [--q TEXT] [--paused] [--limit 20]
14990
15175
  cards show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
14991
15176
  cards archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
@@ -15035,6 +15220,9 @@ ${bold("Schedule turns")} ${dim("(each run lands in a conversation and notifies
15035
15220
  schedule rm <id> <schedule-id> Cancel a pending turn
15036
15221
  schedule add|ls|rm <id> \u2026 --teams WS#room[/thread] The same in a Teams room or thread: it answers there as if mentioned
15037
15222
 
15223
+ ${bold("Teams")}
15224
+ teams post WS#room[/thread] "msg" Write there as you; any @agent it mentions answers (no thread = new thread) [--wait [--timeout 600]: print their answers]
15225
+
15038
15226
  ${bold("Browser")} ${dim("(Ghosty for Chrome)")}
15039
15227
  browser logout Revoke the browser tokens of your terminal agents and disconnect the extension
15040
15228
 
@@ -15082,14 +15270,16 @@ ${bold("Sesi\xF3n")}
15082
15270
  ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
15083
15271
  bags ls|create|set|rm <espacio> ["bolsa"] Bolsas de tokens de las que gastan sus agentes [--tokens 20M] [--recurring on|off] [--hard-cap on|off] [--note]
15084
15272
  spaces ls Tus espacios (rol, plan, combo) [--user CORREO: los de alguien m\xE1s]
15085
- spaces create <slug> Espacio nuevo a tu nombre (trial) [--combo teams|sales|both: staff] [--dry-run]
15273
+ spaces create <slug> Espacio nuevo a tu nombre (trial) [--combo teams|sales|both|none: staff] [--dry-run]
15086
15274
  spaces join|leave <espacio> Entra o sal del espacio de un cliente como staff (sin asiento; queda en bit\xE1cora)
15087
- spaces set <espacio> --combo sales teams | sales | both (sin Ventas sus tableros no se ven) [--model-cap 0.25|1|4|none: el modelo m\xE1s caro] [--house-claude on|off]
15275
+ spaces set <espacio> --combo sales teams | sales | both | none (sin Ventas sus tableros no se ven) [--model-cap 0.25|1|4|none: el modelo m\xE1s caro] [--house-claude on|off]
15088
15276
  spaces renew <espacio> --until FECHA Mueve \xABpagado hasta\xBB (queda en bit\xE1cora) [--note] [--dry-run]
15089
15277
  spaces rename <viejo> <nuevo> Cambia s\xF3lo su slug [--dry-run]
15090
15278
  spaces archive|restore <espacio> Arch\xEDvalo (sale de /app y de spaces ls; agentes y datos se quedan) o tr\xE1elo de vuelta (due\xF1o o staff) [--dry-run]
15091
15279
  spaces transfer <espacio> --to CORREO Due\xF1o nuevo; los agentes se van con \xE9l (las cajas ACP se mudan con su /data, las del pool se reciclan) [--keep-as admin|member|none] [--copy-keys] [--dry-run]
15092
15280
  spaces log <espacio> Su bit\xE1cora: traspasos, staff que entra o sale, renovaciones (due\xF1o o staff) [--since 30d] [--action workspace]
15281
+ spaces members ls|add|rm <espacio> [correo] Su gente, sin la pantalla Miembros (due\xF1o, admin o staff) [--role member|admin] [--until 2026-10-28|14d: se retira solo]
15282
+ access why <correo> <agente> Por qu\xE9 alguien ve un agente o un tablero (todas las razones y cu\xE1ndo vence lo temporal) [--workspace W --board B]
15093
15283
  staff ls [correo-staff] [correo-cliente] Staff de Ghosty asignado a cuentas cliente (staff) [--all: tambi\xE9n revocadas]
15094
15284
  staff assign|unassign <staff> <cliente> El staff ve y opera agentes, documentos, bases y tableros de esa cuenta como s\xED mismo (sin llaves, cobro ni traspasos; queda en bit\xE1cora) [--reason]
15095
15285
  settings ls|set|unset <CLAVE> [valor] Perillas de la plataforma (staff): qui\xE9n atiende /c sin cuenta, subagentes nativos\u2026 [+id/-id edita una lista]
@@ -15160,7 +15350,7 @@ ${bold("Configurar un agente")}
15160
15350
  channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
15161
15351
  channels wa status|pause <id> WhatsApp personal: c\xF3mo est\xE1 / pausarlo (conserva la sesi\xF3n)
15162
15352
  channels wa groups ls|enable|disable|set|create <id> [grupo] Grupos de WhatsApp personal que contesta, y como qui\xE9n [--role equipo|cliente] [--wake mention|all] [--name "Nombre nuevo"] [--dry-run]
15163
- boards ls|create|assign|move-cards|rm Los tableros de un espacio: crear (--columns-from), a d\xF3nde manda tarjetas un agente, mover tarjetas
15353
+ boards ls|create|assign|move-cards|access|rm Los tableros de un espacio: crear (--columns-from), a d\xF3nde manda tarjetas un agente, mover tarjetas, qui\xE9n ve cada uno
15164
15354
  cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--paused] [--limit 20]
15165
15355
  cards show <id> <tarjeta> El hilo de una tarjeta: qui\xE9n escribi\xF3, texto, adjuntos y si gs guard\xF3 copia (folio, tel\xE9fono o id)
15166
15356
  cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
@@ -15210,6 +15400,9 @@ ${bold("Turnos programados")} ${dim("(cada corrida cae en una conversaci\xF3n y
15210
15400
  schedule rm <id> <schedule-id> Cancela un turno pendiente
15211
15401
  schedule add|ls|rm <id> \u2026 --teams ESP#room[/hilo] Lo mismo en un room o hilo de Teams: contesta ah\xED como si lo mencionaran
15212
15402
 
15403
+ ${bold("Teams")}
15404
+ teams post ESP#room[/hilo] "texto" Escribe ah\xED a tu nombre; el @agente que menciones contesta (sin hilo = hilo nuevo) [--wait [--timeout 600]: imprime sus respuestas]
15405
+
15213
15406
  ${bold("Navegador")} ${dim("(Ghosty para Chrome)")}
15214
15407
  browser logout Revoca los tokens del navegador de tus agentes de terminal y desconecta la extensi\xF3n
15215
15408
 
@@ -15239,7 +15432,7 @@ ${bold("Opciones globales")}
15239
15432
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
15240
15433
  ${dim("Nota: `ghosty` puede chocar con otro binario en tu PATH (p. ej. la TUI de Ghosty). Rev\xEDsalo con `which -a ghosty`; corre `npx @ghostystudio/cli` para ir a la segura.")}
15241
15434
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
15242
- var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "settings", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "connectors", "browser", "support", "docs", "help"];
15435
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "settings", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "teams", "partner", "me", "channels", "board", "boards", "access", "credentials", "connectors", "browser", "support", "docs", "help"];
15243
15436
  var AGENTS_SUBS = ["ls", "create", "get", "show", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box"];
15244
15437
  function unknown(what, input, known, helpCmd) {
15245
15438
  const s2 = suggest(input, known);
@@ -15390,6 +15583,10 @@ async function main() {
15390
15583
  enable: { type: "string" },
15391
15584
  disable: { type: "string" },
15392
15585
  "columns-from": { type: "string" },
15586
+ add: { type: "string", multiple: true },
15587
+ remove: { type: "string", multiple: true },
15588
+ everyone: { type: "boolean" },
15589
+ restrict: { type: "boolean" },
15393
15590
  to: { type: "string" },
15394
15591
  all: { type: "boolean" },
15395
15592
  resolve: { type: "boolean" },
@@ -15468,6 +15665,8 @@ async function main() {
15468
15665
  canal: { type: "string" },
15469
15666
  models: { type: "boolean" },
15470
15667
  teams: { type: "string" },
15668
+ wait: { type: "boolean" },
15669
+ timeout: { type: "string" },
15471
15670
  owner: { type: "string" },
15472
15671
  cursor: { type: "string" },
15473
15672
  user: { type: "string" },
@@ -15513,7 +15712,7 @@ async function main() {
15513
15712
  const tokens = splitTokens(pos, v);
15514
15713
  globals.token = tokens.session;
15515
15714
  globals.profile = v.profile;
15516
- if (v.version) return out({ version: "0.66.0" }, () => "0.66.0");
15715
+ if (v.version) return out({ version: "0.68.0" }, () => "0.68.0");
15517
15716
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
15518
15717
  const context = (await readConfig()).workspace;
15519
15718
  if (!explicitToken()?.startsWith("gat_")) {
@@ -15637,6 +15836,8 @@ async function main() {
15637
15836
  throw usage(t(`Unknown conversations subcommand: ${sub ?? "(none)"}.`, `Subcomando de conversations desconocido: ${sub ?? "(ninguno)"}.`), t("Try: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>", "Prueba: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>"));
15638
15837
  case "schedule":
15639
15838
  return scheduleCmd(sub, a1, a2, v);
15839
+ case "teams":
15840
+ return teamsCmd(sub, a1, a2, v);
15640
15841
  case "browser":
15641
15842
  return browserCmd(sub, v);
15642
15843
  case "partner":
@@ -15659,7 +15860,10 @@ async function main() {
15659
15860
  case "spaces":
15660
15861
  case "space":
15661
15862
  case "workspaces":
15863
+ if (sub === "members") return spacesMembers(a1, a2, pos[4], v, context);
15662
15864
  return spacesCmd(sub, a1, a2, v, context);
15865
+ case "access":
15866
+ return accessCmd(sub, a1, a2, v, context);
15663
15867
  case "staff":
15664
15868
  return staffCmd(sub, a1, a2, v);
15665
15869
  case "settings":
@@ -15685,7 +15889,7 @@ async function main() {
15685
15889
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
15686
15890
  }
15687
15891
  }
15688
- var notice = updateNotice("0.66.0").catch(() => null);
15892
+ var notice = updateNotice("0.68.0").catch(() => null);
15689
15893
  main().then(
15690
15894
  async () => {
15691
15895
  const n = await notice;