@swmansion/argent 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli-cmds.mjs CHANGED
@@ -25,62 +25,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  mod
26
26
  ));
27
27
 
28
- // ../../node_modules/sisteransi/src/index.js
29
- var require_src = __commonJS({
30
- "../../node_modules/sisteransi/src/index.js"(exports, module) {
31
- "use strict";
32
- var ESC2 = "\x1B";
33
- var CSI2 = `${ESC2}[`;
34
- var beep = "\x07";
35
- var cursor = {
36
- to(x3, y) {
37
- if (!y) return `${CSI2}${x3 + 1}G`;
38
- return `${CSI2}${y + 1};${x3 + 1}H`;
39
- },
40
- move(x3, y) {
41
- let ret = "";
42
- if (x3 < 0) ret += `${CSI2}${-x3}D`;
43
- else if (x3 > 0) ret += `${CSI2}${x3}C`;
44
- if (y < 0) ret += `${CSI2}${-y}A`;
45
- else if (y > 0) ret += `${CSI2}${y}B`;
46
- return ret;
47
- },
48
- up: (count = 1) => `${CSI2}${count}A`,
49
- down: (count = 1) => `${CSI2}${count}B`,
50
- forward: (count = 1) => `${CSI2}${count}C`,
51
- backward: (count = 1) => `${CSI2}${count}D`,
52
- nextLine: (count = 1) => `${CSI2}E`.repeat(count),
53
- prevLine: (count = 1) => `${CSI2}F`.repeat(count),
54
- left: `${CSI2}G`,
55
- hide: `${CSI2}?25l`,
56
- show: `${CSI2}?25h`,
57
- save: `${ESC2}7`,
58
- restore: `${ESC2}8`
59
- };
60
- var scroll = {
61
- up: (count = 1) => `${CSI2}S`.repeat(count),
62
- down: (count = 1) => `${CSI2}T`.repeat(count)
63
- };
64
- var erase = {
65
- screen: `${CSI2}2J`,
66
- up: (count = 1) => `${CSI2}1J`.repeat(count),
67
- down: (count = 1) => `${CSI2}J`.repeat(count),
68
- line: `${CSI2}2K`,
69
- lineEnd: `${CSI2}K`,
70
- lineStart: `${CSI2}1K`,
71
- lines(count) {
72
- let clear = "";
73
- for (let i = 0; i < count; i++)
74
- clear += this.line + (i < count - 1 ? cursor.up() : "");
75
- if (count)
76
- clear += cursor.left;
77
- return clear;
78
- }
79
- };
80
- module.exports = { cursor, scroll, erase, beep };
81
- }
82
- });
83
-
84
28
  // ../../node_modules/picocolors/picocolors.js
85
29
  var require_picocolors = __commonJS({
86
30
  "../../node_modules/picocolors/picocolors.js"(exports, module) {
@@ -153,6 +97,62 @@ var require_picocolors = __commonJS({
153
97
  }
154
98
  });
155
99
 
100
+ // ../../node_modules/sisteransi/src/index.js
101
+ var require_src = __commonJS({
102
+ "../../node_modules/sisteransi/src/index.js"(exports, module) {
103
+ "use strict";
104
+ var ESC2 = "\x1B";
105
+ var CSI2 = `${ESC2}[`;
106
+ var beep = "\x07";
107
+ var cursor = {
108
+ to(x3, y) {
109
+ if (!y) return `${CSI2}${x3 + 1}G`;
110
+ return `${CSI2}${y + 1};${x3 + 1}H`;
111
+ },
112
+ move(x3, y) {
113
+ let ret = "";
114
+ if (x3 < 0) ret += `${CSI2}${-x3}D`;
115
+ else if (x3 > 0) ret += `${CSI2}${x3}C`;
116
+ if (y < 0) ret += `${CSI2}${-y}A`;
117
+ else if (y > 0) ret += `${CSI2}${y}B`;
118
+ return ret;
119
+ },
120
+ up: (count = 1) => `${CSI2}${count}A`,
121
+ down: (count = 1) => `${CSI2}${count}B`,
122
+ forward: (count = 1) => `${CSI2}${count}C`,
123
+ backward: (count = 1) => `${CSI2}${count}D`,
124
+ nextLine: (count = 1) => `${CSI2}E`.repeat(count),
125
+ prevLine: (count = 1) => `${CSI2}F`.repeat(count),
126
+ left: `${CSI2}G`,
127
+ hide: `${CSI2}?25l`,
128
+ show: `${CSI2}?25h`,
129
+ save: `${ESC2}7`,
130
+ restore: `${ESC2}8`
131
+ };
132
+ var scroll = {
133
+ up: (count = 1) => `${CSI2}S`.repeat(count),
134
+ down: (count = 1) => `${CSI2}T`.repeat(count)
135
+ };
136
+ var erase = {
137
+ screen: `${CSI2}2J`,
138
+ up: (count = 1) => `${CSI2}1J`.repeat(count),
139
+ down: (count = 1) => `${CSI2}J`.repeat(count),
140
+ line: `${CSI2}2K`,
141
+ lineEnd: `${CSI2}K`,
142
+ lineStart: `${CSI2}1K`,
143
+ lines(count) {
144
+ let clear = "";
145
+ for (let i = 0; i < count; i++)
146
+ clear += this.line + (i < count - 1 ? cursor.up() : "");
147
+ if (count)
148
+ clear += cursor.left;
149
+ return clear;
150
+ }
151
+ };
152
+ module.exports = { cursor, scroll, erase, beep };
153
+ }
154
+ });
155
+
156
156
  // ../argent-cli/src/run.ts
157
157
  import * as fs2 from "node:fs";
158
158
  import * as path3 from "node:path";
@@ -566,6 +566,138 @@ function createToolsClient(options = {}) {
566
566
  return { fetchTools, fetchTool, callTool, baseUrl };
567
567
  }
568
568
 
569
+ // ../argent-tools-client/src/artifacts.ts
570
+ import { mkdir as mkdir3, readFile as readFile3, rm, stat, writeFile as writeFile3 } from "node:fs/promises";
571
+ import { tmpdir } from "node:os";
572
+ import { basename, join as join3 } from "node:path";
573
+ import { createHash } from "node:crypto";
574
+ import { execFile } from "node:child_process";
575
+ import { promisify } from "node:util";
576
+ var execFileAsync = promisify(execFile);
577
+ var ARTIFACT_MARKER = "__argentArtifact";
578
+ function isArtifactHandle(value) {
579
+ return !!value && typeof value === "object" && value[ARTIFACT_MARKER] === true && typeof value.id === "string" && typeof value.filename === "string";
580
+ }
581
+ var SESSION_ID = null;
582
+ function sessionId() {
583
+ if (!SESSION_ID) {
584
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, (m2) => m2 === "T" ? "-" : "").slice(0, 15);
585
+ SESSION_ID = `${stamp}-${process.pid}`;
586
+ }
587
+ return SESSION_ID;
588
+ }
589
+ function sanitizeSegment(segment) {
590
+ return segment.replace(/[^A-Za-z0-9._-]/g, "_");
591
+ }
592
+ function projectSlug() {
593
+ const cwd = process.cwd();
594
+ const hash = createHash("sha1").update(cwd).digest("hex").slice(0, 6);
595
+ const name = sanitizeSegment(basename(cwd)) || "root";
596
+ return `${name}-${hash}`;
597
+ }
598
+ function artifactsRoot() {
599
+ return process.env.ARGENT_ARTIFACTS_DIR ?? join3(tmpdir(), "argent-artifacts");
600
+ }
601
+ function artifactDir(deviceId) {
602
+ const parts = [artifactsRoot(), projectSlug(), sessionId()];
603
+ if (deviceId) parts.push(sanitizeSegment(deviceId));
604
+ return join3(...parts);
605
+ }
606
+ async function resolveLocalFile(handle) {
607
+ if (!handle.hostPath) return null;
608
+ try {
609
+ const st2 = await stat(handle.hostPath);
610
+ if (handle.archive) {
611
+ return st2.isDirectory() ? handle.hostPath : null;
612
+ }
613
+ if (!st2.isFile()) return null;
614
+ if (st2.size !== handle.size) return null;
615
+ if (handle.mtimeMs != null && Math.round(st2.mtimeMs) !== Math.round(handle.mtimeMs)) {
616
+ return null;
617
+ }
618
+ return handle.hostPath;
619
+ } catch {
620
+ return null;
621
+ }
622
+ }
623
+ async function downloadAndExtractArchive(handle, data, dir) {
624
+ const tarball = join3(dir, `${sanitizeSegment(handle.filename)}.tar.gz`);
625
+ try {
626
+ await writeFile3(tarball, data);
627
+ await execFileAsync("tar", ["-xzf", tarball, "-C", dir]);
628
+ return join3(dir, handle.filename);
629
+ } catch {
630
+ return null;
631
+ } finally {
632
+ await rm(tarball, { force: true }).catch(() => {
633
+ });
634
+ }
635
+ }
636
+ async function materializeArtifacts(result, ctx) {
637
+ const images = [];
638
+ const fetchFn = ctx.fetchImpl ?? fetch;
639
+ const authHeaders3 = ctx.authToken ? { Authorization: `Bearer ${ctx.authToken}` } : {};
640
+ const dir = artifactDir(ctx.deviceId);
641
+ let dirReady = false;
642
+ async function ensureDir() {
643
+ if (!dirReady) {
644
+ await mkdir3(dir, { recursive: true });
645
+ dirReady = true;
646
+ }
647
+ }
648
+ async function walk(value) {
649
+ if (isArtifactHandle(value)) {
650
+ const localPath = await resolveLocalFile(value);
651
+ if (localPath) {
652
+ if (value.mimeType.startsWith("image/")) {
653
+ images.push({ localPath, data: await readFile3(localPath), mimeType: value.mimeType });
654
+ }
655
+ return localPath;
656
+ }
657
+ try {
658
+ const res = await fetchFn(`${ctx.toolsUrl}/artifacts/${value.id}`, {
659
+ headers: authHeaders3
660
+ });
661
+ if (!res.ok) return null;
662
+ const data = Buffer.from(await res.arrayBuffer());
663
+ await ensureDir();
664
+ if (value.archive === "tar.gz") {
665
+ return await downloadAndExtractArchive(value, data, dir);
666
+ }
667
+ if (value.size > 0 && data.length !== value.size) return null;
668
+ const downloadedPath = join3(dir, sanitizeSegment(value.filename));
669
+ await writeFile3(downloadedPath, data);
670
+ if (value.mimeType.startsWith("image/")) {
671
+ images.push({ localPath: downloadedPath, data, mimeType: value.mimeType });
672
+ }
673
+ return downloadedPath;
674
+ } catch {
675
+ return null;
676
+ }
677
+ }
678
+ if (Array.isArray(value)) {
679
+ return Promise.all(value.map(walk));
680
+ }
681
+ if (value && typeof value === "object") {
682
+ const out = {};
683
+ for (const [k2, v2] of Object.entries(value)) {
684
+ out[k2] = await walk(v2);
685
+ }
686
+ return out;
687
+ }
688
+ return value;
689
+ }
690
+ const rewritten = await walk(result);
691
+ return { result: rewritten, images };
692
+ }
693
+ function getDeviceIdFromArgs(args) {
694
+ if (!args || typeof args !== "object") return void 0;
695
+ const rec = args;
696
+ if (typeof rec.udid === "string") return rec.udid;
697
+ if (typeof rec.device_id === "string") return rec.device_id;
698
+ return void 0;
699
+ }
700
+
569
701
  // ../argent-cli/src/flag-parser.ts
570
702
  var FlagParseException = class extends Error {
571
703
  };
@@ -801,17 +933,19 @@ async function fetchImageToFile(result, outPath) {
801
933
  fs2.mkdirSync(path3.dirname(path3.resolve(outPath)), { recursive: true });
802
934
  fs2.writeFileSync(outPath, buf);
803
935
  }
804
- function renderResult(result, outputHint, json) {
936
+ function renderResult(result, outputHint, images, json) {
805
937
  if (json) return JSON.stringify(result, null, 2);
806
- if (outputHint === "image" && result && typeof result === "object" && "path" in result && typeof result.path === "string") {
807
- const r = result;
808
- return `Saved screenshot: ${r.path}`;
938
+ if (outputHint === "image") {
939
+ if (images.length > 0) return `Saved screenshot: ${images[0].localPath}`;
940
+ if (result && typeof result === "object" && "path" in result && typeof result.path === "string") {
941
+ return `Saved screenshot: ${result.path}`;
942
+ }
809
943
  }
810
944
  if (typeof result === "string") return result;
811
945
  return JSON.stringify(result, null, 2);
812
946
  }
813
947
  async function run(argv, options) {
814
- const { fetchTool, callTool } = createToolsClient({ paths: options.paths });
948
+ const { fetchTool, callTool, baseUrl } = createToolsClient({ paths: options.paths });
815
949
  const [toolName, ...rest] = argv;
816
950
  if (!toolName || toolName === "--help" || toolName === "-h") {
817
951
  console.log(`Usage: argent run <tool> [flags]
@@ -873,24 +1007,37 @@ Examples:
873
1007
  }
874
1008
  let result;
875
1009
  let note;
1010
+ let images = [];
876
1011
  try {
877
1012
  const resp = await callTool(toolName, payload);
878
- result = resp.data;
1013
+ const { url, token } = await baseUrl();
1014
+ const materialized = await materializeArtifacts(resp.data, {
1015
+ toolsUrl: url,
1016
+ authToken: token,
1017
+ deviceId: getDeviceIdFromArgs(payload)
1018
+ });
1019
+ result = materialized.result;
1020
+ images = materialized.images;
879
1021
  note = resp.note;
880
1022
  } catch (err) {
881
1023
  console.error(err instanceof Error ? err.message : String(err));
882
1024
  process.exit(1);
883
1025
  }
884
- if (outPath && meta.outputHint === "image" && result && typeof result === "object") {
1026
+ if (outPath && meta.outputHint === "image") {
885
1027
  try {
886
- await fetchImageToFile(result, outPath);
1028
+ if (images.length > 0) {
1029
+ fs2.mkdirSync(path3.dirname(path3.resolve(outPath)), { recursive: true });
1030
+ fs2.writeFileSync(outPath, images[0].data);
1031
+ } else if (result && typeof result === "object") {
1032
+ await fetchImageToFile(result, outPath);
1033
+ }
887
1034
  } catch (err) {
888
1035
  console.error(`Failed to save image: ${err instanceof Error ? err.message : err}`);
889
1036
  process.exit(1);
890
1037
  }
891
1038
  }
892
1039
  if (note) console.error(note);
893
- console.log(renderResult(result, meta.outputHint, json));
1040
+ console.log(renderResult(result, meta.outputHint, images, json));
894
1041
  if (outPath && meta.outputHint === "image" && !json) {
895
1042
  console.log(`Wrote: ${outPath}`);
896
1043
  }
@@ -1375,99 +1522,395 @@ async function server(argv, options) {
1375
1522
  }
1376
1523
  }
1377
1524
 
1378
- // ../../node_modules/@clack/core/dist/index.mjs
1379
- import { styleText as v } from "node:util";
1380
- import { stdout as x, stdin as D } from "node:process";
1381
- import * as b from "node:readline";
1382
- import E from "node:readline";
1383
-
1384
- // ../../node_modules/fast-string-truncated-width/dist/utils.js
1385
- var getCodePointsLength = /* @__PURE__ */ (() => {
1386
- const SURROGATE_PAIR_RE = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
1387
- return (input) => {
1388
- let surrogatePairsNr = 0;
1389
- SURROGATE_PAIR_RE.lastIndex = 0;
1390
- while (SURROGATE_PAIR_RE.test(input)) {
1391
- surrogatePairsNr += 1;
1392
- }
1393
- return input.length - surrogatePairsNr;
1394
- };
1395
- })();
1396
- var isFullWidth = (x3) => {
1397
- return x3 === 12288 || x3 >= 65281 && x3 <= 65376 || x3 >= 65504 && x3 <= 65510;
1398
- };
1399
- var isWideNotCJKTNotEmoji = (x3) => {
1400
- return x3 === 8987 || x3 === 9001 || x3 >= 12272 && x3 <= 12287 || x3 >= 12289 && x3 <= 12350 || x3 >= 12441 && x3 <= 12543 || x3 >= 12549 && x3 <= 12591 || x3 >= 12593 && x3 <= 12686 || x3 >= 12688 && x3 <= 12771 || x3 >= 12783 && x3 <= 12830 || x3 >= 12832 && x3 <= 12871 || x3 >= 12880 && x3 <= 19903 || x3 >= 65040 && x3 <= 65049 || x3 >= 65072 && x3 <= 65106 || x3 >= 65108 && x3 <= 65126 || x3 >= 65128 && x3 <= 65131 || x3 >= 127488 && x3 <= 127490 || x3 >= 127504 && x3 <= 127547 || x3 >= 127552 && x3 <= 127560 || x3 >= 131072 && x3 <= 196605 || x3 >= 196608 && x3 <= 262141;
1401
- };
1525
+ // ../argent-cli/src/flags.ts
1526
+ var import_picocolors = __toESM(require_picocolors(), 1);
1402
1527
 
1403
- // ../../node_modules/fast-string-truncated-width/dist/index.js
1404
- var ANSI_RE = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y;
1405
- var CONTROL_RE = /[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y;
1406
- var CJKT_WIDE_RE = /(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/yu;
1407
- var TAB_RE = /\t{1,1000}/y;
1408
- var EMOJI_RE = new RegExp("[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|\\p{Emoji_Presentation}|\\p{Emoji}\\uFE0F\\u20E3?))*", "yu");
1409
- var LATIN_RE = /(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y;
1410
- var MODIFIER_RE = new RegExp("\\p{M}+", "gu");
1411
- var NO_TRUNCATION = { limit: Infinity, ellipsis: "" };
1412
- var getStringTruncatedWidth = (input, truncationOptions = {}, widthOptions = {}) => {
1413
- const LIMIT = truncationOptions.limit ?? Infinity;
1414
- const ELLIPSIS = truncationOptions.ellipsis ?? "";
1415
- const ELLIPSIS_WIDTH = truncationOptions?.ellipsisWidth ?? (ELLIPSIS ? getStringTruncatedWidth(ELLIPSIS, NO_TRUNCATION, widthOptions).width : 0);
1416
- const ANSI_WIDTH = 0;
1417
- const CONTROL_WIDTH = widthOptions.controlWidth ?? 0;
1418
- const TAB_WIDTH = widthOptions.tabWidth ?? 8;
1419
- const EMOJI_WIDTH = widthOptions.emojiWidth ?? 2;
1420
- const FULL_WIDTH_WIDTH = 2;
1421
- const REGULAR_WIDTH = widthOptions.regularWidth ?? 1;
1422
- const WIDE_WIDTH = widthOptions.wideWidth ?? FULL_WIDTH_WIDTH;
1423
- const PARSE_BLOCKS = [
1424
- [LATIN_RE, REGULAR_WIDTH],
1425
- [ANSI_RE, ANSI_WIDTH],
1426
- [CONTROL_RE, CONTROL_WIDTH],
1427
- [TAB_RE, TAB_WIDTH],
1428
- [EMOJI_RE, EMOJI_WIDTH],
1429
- [CJKT_WIDE_RE, WIDE_WIDTH]
1430
- ];
1431
- let indexPrev = 0;
1432
- let index = 0;
1433
- let length = input.length;
1434
- let lengthExtra = 0;
1435
- let truncationEnabled = false;
1436
- let truncationIndex = length;
1437
- let truncationLimit = Math.max(0, LIMIT - ELLIPSIS_WIDTH);
1438
- let unmatchedStart = 0;
1439
- let unmatchedEnd = 0;
1440
- let width = 0;
1441
- let widthExtra = 0;
1442
- outer: while (true) {
1443
- if (unmatchedEnd > unmatchedStart || index >= length && index > indexPrev) {
1444
- const unmatched = input.slice(unmatchedStart, unmatchedEnd) || input.slice(indexPrev, index);
1445
- lengthExtra = 0;
1446
- for (const char of unmatched.replaceAll(MODIFIER_RE, "")) {
1447
- const codePoint = char.codePointAt(0) || 0;
1448
- if (isFullWidth(codePoint)) {
1449
- widthExtra = FULL_WIDTH_WIDTH;
1450
- } else if (isWideNotCJKTNotEmoji(codePoint)) {
1451
- widthExtra = WIDE_WIDTH;
1452
- } else {
1453
- widthExtra = REGULAR_WIDTH;
1454
- }
1455
- if (width + widthExtra > truncationLimit) {
1456
- truncationIndex = Math.min(truncationIndex, Math.max(unmatchedStart, indexPrev) + lengthExtra);
1457
- }
1458
- if (width + widthExtra > LIMIT) {
1459
- truncationEnabled = true;
1460
- break outer;
1461
- }
1462
- lengthExtra += char.length;
1463
- width += widthExtra;
1464
- }
1465
- unmatchedStart = unmatchedEnd = 0;
1466
- }
1467
- if (index >= length) {
1468
- break outer;
1528
+ // ../configuration-core/src/flags.ts
1529
+ import * as fs4 from "node:fs";
1530
+ import * as path5 from "node:path";
1531
+ import { homedir as homedir4 } from "node:os";
1532
+ var FLAG_REGISTRY = [
1533
+ {
1534
+ name: "disable-auto-screenshot",
1535
+ description: "Disable the automatic screenshot captured after interaction tools."
1536
+ }
1537
+ ];
1538
+ function getFlagDefinition(name, registry = FLAG_REGISTRY) {
1539
+ return registry.find((def) => def.name === name);
1540
+ }
1541
+ var PROJECT_MARKERS = [".argent", ".git", "package.json"];
1542
+ function resolveProjectRoot(startDir) {
1543
+ const initial = path5.resolve(startDir);
1544
+ let current = initial;
1545
+ while (true) {
1546
+ for (const marker of PROJECT_MARKERS) {
1547
+ if (fs4.existsSync(path5.join(current, marker))) return current;
1469
1548
  }
1470
- for (let i = 0, l = PARSE_BLOCKS.length; i < l; i++) {
1549
+ const parent = path5.dirname(current);
1550
+ if (parent === current) return initial;
1551
+ current = parent;
1552
+ }
1553
+ }
1554
+ function getFlagsPath(scope, options = {}) {
1555
+ const home = options.homeDir ?? homedir4();
1556
+ if (scope === "global") {
1557
+ return path5.join(home, ".argent", "flags.json");
1558
+ }
1559
+ const cwd = options.cwd ?? process.cwd();
1560
+ return path5.join(resolveProjectRoot(cwd), ".argent", "flags.json");
1561
+ }
1562
+ function readFlagsFile(filePath) {
1563
+ let raw;
1564
+ try {
1565
+ raw = fs4.readFileSync(filePath, "utf8");
1566
+ } catch {
1567
+ return {};
1568
+ }
1569
+ let parsed;
1570
+ try {
1571
+ parsed = JSON.parse(raw);
1572
+ } catch {
1573
+ return {};
1574
+ }
1575
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
1576
+ const flags2 = parsed.flags;
1577
+ if (!flags2 || typeof flags2 !== "object" || Array.isArray(flags2)) return {};
1578
+ const out = {};
1579
+ for (const [k2, v2] of Object.entries(flags2)) {
1580
+ if (typeof v2 === "boolean") out[k2] = v2;
1581
+ }
1582
+ return out;
1583
+ }
1584
+ function writeFlagsFile(filePath, flags2) {
1585
+ if (Object.keys(flags2).length === 0) {
1586
+ if (fs4.existsSync(filePath)) fs4.rmSync(filePath, { force: true });
1587
+ const parent = path5.dirname(filePath);
1588
+ try {
1589
+ if (fs4.existsSync(parent) && fs4.readdirSync(parent).length === 0) {
1590
+ fs4.rmdirSync(parent);
1591
+ }
1592
+ } catch {
1593
+ }
1594
+ return;
1595
+ }
1596
+ fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
1597
+ const tmp = `${filePath}.${process.pid}.tmp`;
1598
+ fs4.writeFileSync(tmp, JSON.stringify({ flags: flags2 }, null, 2) + "\n");
1599
+ fs4.renameSync(tmp, filePath);
1600
+ }
1601
+ function readFlags(scope, options = {}) {
1602
+ return readFlagsFile(getFlagsPath(scope, options));
1603
+ }
1604
+ function setFlag(name, value, scope, options = {}) {
1605
+ const filePath = getFlagsPath(scope, options);
1606
+ const current = readFlagsFile(filePath);
1607
+ current[name] = value;
1608
+ writeFlagsFile(filePath, current);
1609
+ }
1610
+ function unsetFlag(name, scope, options = {}) {
1611
+ const filePath = getFlagsPath(scope, options);
1612
+ const current = readFlagsFile(filePath);
1613
+ if (!Object.hasOwn(current, name)) return false;
1614
+ delete current[name];
1615
+ writeFlagsFile(filePath, current);
1616
+ return true;
1617
+ }
1618
+ function isFlagEnabled(name, options = {}) {
1619
+ const projectFlags = readFlags("project", options);
1620
+ if (Object.hasOwn(projectFlags, name)) return projectFlags[name];
1621
+ const globalFlags = readFlags("global", options);
1622
+ if (Object.hasOwn(globalFlags, name)) return globalFlags[name];
1623
+ return false;
1624
+ }
1625
+
1626
+ // ../argent-cli/src/flags.ts
1627
+ function colorState(enabled) {
1628
+ const label = (enabled ? "enabled" : "disabled").padEnd(8);
1629
+ if (!process.stdout.isTTY) return label;
1630
+ return enabled ? import_picocolors.default.green(label) : import_picocolors.default.red(label);
1631
+ }
1632
+ var FLAG_NAME_RE = /^[a-zA-Z][a-zA-Z0-9._-]*$/;
1633
+ function parseToggleArgs(argv, command) {
1634
+ let name = null;
1635
+ let scope = "global";
1636
+ let positionalOnly = false;
1637
+ for (let i = 0; i < argv.length; i++) {
1638
+ const tok = argv[i];
1639
+ if (positionalOnly) {
1640
+ if (name !== null) throw new Error(`Unexpected extra argument: "${tok}"`);
1641
+ name = tok;
1642
+ continue;
1643
+ }
1644
+ if (tok === "--") {
1645
+ positionalOnly = true;
1646
+ continue;
1647
+ }
1648
+ if (tok === "--scope") {
1649
+ const v2 = argv[i + 1];
1650
+ if (v2 === void 0) throw new Error("--scope requires a value (project|global)");
1651
+ scope = parseScope(v2);
1652
+ i += 1;
1653
+ continue;
1654
+ }
1655
+ if (tok.startsWith("--scope=")) {
1656
+ scope = parseScope(tok.slice("--scope=".length));
1657
+ continue;
1658
+ }
1659
+ if (tok.startsWith("--")) {
1660
+ throw new Error(`Unknown flag: ${tok}`);
1661
+ }
1662
+ if (name !== null) {
1663
+ throw new Error(`Unexpected extra argument: "${tok}"`);
1664
+ }
1665
+ name = tok;
1666
+ }
1667
+ if (name === null) {
1668
+ throw new Error(`Usage: argent ${command} <flag-name> [--scope project|global]`);
1669
+ }
1670
+ if (!FLAG_NAME_RE.test(name)) {
1671
+ throw new Error(
1672
+ `Invalid flag name "${name}". Must start with a letter and contain only letters, digits, ".", "_", or "-".`
1673
+ );
1674
+ }
1675
+ return { name, scope };
1676
+ }
1677
+ function parseScope(raw) {
1678
+ if (raw === "global" || raw === "project") return raw;
1679
+ throw new Error(`--scope must be "project" or "global", got "${raw}"`);
1680
+ }
1681
+ function formatAvailableFlags(registry) {
1682
+ if (registry.length === 0) {
1683
+ return "Available flags:\n (none defined)";
1684
+ }
1685
+ const maxName = registry.reduce((m2, def) => Math.max(m2, def.name.length), 0);
1686
+ const lines = registry.map((def) => ` ${def.name.padEnd(maxName)} ${def.description}`);
1687
+ return ["Available flags:", ...lines].join("\n");
1688
+ }
1689
+ function printToggleHelp(command, registry) {
1690
+ const summary = command === "enable" ? "Enable a predefined feature flag (see `argent flags`) at the chosen scope." : "Remove a feature flag entry at the chosen scope. Falls back to the global value if set; otherwise the flag is treated as off.";
1691
+ console.log(`Usage: argent ${command} <flag-name> [--scope project|global]
1692
+
1693
+ ${summary}
1694
+
1695
+ ${formatAvailableFlags(registry)}
1696
+
1697
+ Storage:
1698
+ ~/.argent/flags.json (global, default)
1699
+ <project-root>/.argent/flags.json (project, with --scope project)
1700
+
1701
+ Options:
1702
+ --scope <global|project> Where to write (default: global)
1703
+ --help, -h Show this help
1704
+ `);
1705
+ }
1706
+ function runToggle(argv, command, registry) {
1707
+ if (argv.includes("--help") || argv.includes("-h")) {
1708
+ printToggleHelp(command, registry);
1709
+ return;
1710
+ }
1711
+ let parsed;
1712
+ try {
1713
+ parsed = parseToggleArgs(argv, command);
1714
+ } catch (err) {
1715
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1716
+ process.exit(2);
1717
+ }
1718
+ if (command === "enable" && getFlagDefinition(parsed.name, registry) === void 0) {
1719
+ console.error(
1720
+ `Error: Unknown feature flag "${parsed.name}". Run \`argent flags\` to see available flags.`
1721
+ );
1722
+ process.exit(2);
1723
+ }
1724
+ const filePath = getFlagsPath(parsed.scope);
1725
+ try {
1726
+ if (command === "enable") {
1727
+ setFlag(parsed.name, true, parsed.scope);
1728
+ } else {
1729
+ unsetFlag(parsed.name, parsed.scope);
1730
+ }
1731
+ } catch (err) {
1732
+ console.error(`Failed to ${command} flag: ${err instanceof Error ? err.message : String(err)}`);
1733
+ process.exit(1);
1734
+ }
1735
+ if (command === "enable") {
1736
+ console.log(`Enabled flag "${parsed.name}" (${parsed.scope}). Stored at ${filePath}.`);
1737
+ } else {
1738
+ console.log(`Disabled flag "${parsed.name}" (${parsed.scope}).`);
1739
+ }
1740
+ }
1741
+ function enable(argv, registry = FLAG_REGISTRY) {
1742
+ runToggle(argv, "enable", registry);
1743
+ }
1744
+ function disable(argv, registry = FLAG_REGISTRY) {
1745
+ runToggle(argv, "disable", registry);
1746
+ }
1747
+ function flags(argv, registry = FLAG_REGISTRY) {
1748
+ if (argv.includes("--help") || argv.includes("-h")) {
1749
+ console.log(`Usage: argent flags [--json]
1750
+
1751
+ List the available feature flags and their current state. Flags are
1752
+ predefined; project-scoped values override global ones.
1753
+
1754
+ ${formatAvailableFlags(registry)}
1755
+
1756
+ Options:
1757
+ --json Print machine-readable JSON
1758
+ `);
1759
+ return;
1760
+ }
1761
+ const json = argv.includes("--json");
1762
+ const projectFlags = readFlags("project");
1763
+ const globalFlags = readFlags("global");
1764
+ const effective = {};
1765
+ for (const [k2, v2] of Object.entries(globalFlags)) effective[k2] = { value: v2, scope: "global" };
1766
+ for (const [k2, v2] of Object.entries(projectFlags)) effective[k2] = { value: v2, scope: "project" };
1767
+ const registryView = registry.map((def) => {
1768
+ const eff = Object.hasOwn(effective, def.name) ? effective[def.name] : void 0;
1769
+ return {
1770
+ name: def.name,
1771
+ description: def.description,
1772
+ enabled: eff?.value ?? false,
1773
+ scope: eff?.scope ?? null
1774
+ };
1775
+ });
1776
+ const known = new Set(registry.map((def) => def.name));
1777
+ const unrecognized = Object.keys(effective).filter((name) => !known.has(name)).sort().map((name) => ({ name, enabled: effective[name].value, scope: effective[name].scope }));
1778
+ if (json) {
1779
+ console.log(
1780
+ JSON.stringify(
1781
+ {
1782
+ flags: registryView,
1783
+ unrecognized,
1784
+ global: globalFlags,
1785
+ project: projectFlags,
1786
+ effective,
1787
+ paths: {
1788
+ global: getFlagsPath("global"),
1789
+ project: getFlagsPath("project")
1790
+ }
1791
+ },
1792
+ null,
1793
+ 2
1794
+ )
1795
+ );
1796
+ return;
1797
+ }
1798
+ if (registryView.length === 0) {
1799
+ console.log("No feature flags are defined.");
1800
+ } else {
1801
+ console.log("Feature flags (project overrides global):");
1802
+ const maxName = registryView.reduce((m2, f2) => Math.max(m2, f2.name.length), 0);
1803
+ for (const f2 of registryView) {
1804
+ const scopeLabel = f2.scope ? ` (${f2.scope})` : "";
1805
+ console.log(` ${f2.name.padEnd(maxName, " ")} ${colorState(f2.enabled)}${scopeLabel}`);
1806
+ console.log(` ${" ".repeat(maxName)} ${f2.description}`);
1807
+ }
1808
+ }
1809
+ if (unrecognized.length > 0) {
1810
+ console.log("\nStored but no longer recognized (safe to `argent disable`):");
1811
+ const maxName = unrecognized.reduce((m2, f2) => Math.max(m2, f2.name.length), 0);
1812
+ for (const f2 of unrecognized) {
1813
+ console.log(` ${f2.name.padEnd(maxName, " ")} ${colorState(f2.enabled)} (${f2.scope})`);
1814
+ }
1815
+ }
1816
+ console.log(`
1817
+ Global: ${getFlagsPath("global")}`);
1818
+ console.log(` Project: ${getFlagsPath("project")}`);
1819
+ }
1820
+
1821
+ // ../../node_modules/@clack/core/dist/index.mjs
1822
+ import { styleText as v } from "node:util";
1823
+ import { stdout as x, stdin as D } from "node:process";
1824
+ import * as b from "node:readline";
1825
+ import E from "node:readline";
1826
+
1827
+ // ../../node_modules/fast-string-truncated-width/dist/utils.js
1828
+ var getCodePointsLength = /* @__PURE__ */ (() => {
1829
+ const SURROGATE_PAIR_RE = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
1830
+ return (input) => {
1831
+ let surrogatePairsNr = 0;
1832
+ SURROGATE_PAIR_RE.lastIndex = 0;
1833
+ while (SURROGATE_PAIR_RE.test(input)) {
1834
+ surrogatePairsNr += 1;
1835
+ }
1836
+ return input.length - surrogatePairsNr;
1837
+ };
1838
+ })();
1839
+ var isFullWidth = (x3) => {
1840
+ return x3 === 12288 || x3 >= 65281 && x3 <= 65376 || x3 >= 65504 && x3 <= 65510;
1841
+ };
1842
+ var isWideNotCJKTNotEmoji = (x3) => {
1843
+ return x3 === 8987 || x3 === 9001 || x3 >= 12272 && x3 <= 12287 || x3 >= 12289 && x3 <= 12350 || x3 >= 12441 && x3 <= 12543 || x3 >= 12549 && x3 <= 12591 || x3 >= 12593 && x3 <= 12686 || x3 >= 12688 && x3 <= 12771 || x3 >= 12783 && x3 <= 12830 || x3 >= 12832 && x3 <= 12871 || x3 >= 12880 && x3 <= 19903 || x3 >= 65040 && x3 <= 65049 || x3 >= 65072 && x3 <= 65106 || x3 >= 65108 && x3 <= 65126 || x3 >= 65128 && x3 <= 65131 || x3 >= 127488 && x3 <= 127490 || x3 >= 127504 && x3 <= 127547 || x3 >= 127552 && x3 <= 127560 || x3 >= 131072 && x3 <= 196605 || x3 >= 196608 && x3 <= 262141;
1844
+ };
1845
+
1846
+ // ../../node_modules/fast-string-truncated-width/dist/index.js
1847
+ var ANSI_RE = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y;
1848
+ var CONTROL_RE = /[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y;
1849
+ var CJKT_WIDE_RE = /(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/yu;
1850
+ var TAB_RE = /\t{1,1000}/y;
1851
+ var EMOJI_RE = new RegExp("[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|\\p{Emoji_Presentation}|\\p{Emoji}\\uFE0F\\u20E3?))*", "yu");
1852
+ var LATIN_RE = /(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y;
1853
+ var MODIFIER_RE = new RegExp("\\p{M}+", "gu");
1854
+ var NO_TRUNCATION = { limit: Infinity, ellipsis: "" };
1855
+ var getStringTruncatedWidth = (input, truncationOptions = {}, widthOptions = {}) => {
1856
+ const LIMIT = truncationOptions.limit ?? Infinity;
1857
+ const ELLIPSIS = truncationOptions.ellipsis ?? "";
1858
+ const ELLIPSIS_WIDTH = truncationOptions?.ellipsisWidth ?? (ELLIPSIS ? getStringTruncatedWidth(ELLIPSIS, NO_TRUNCATION, widthOptions).width : 0);
1859
+ const ANSI_WIDTH = 0;
1860
+ const CONTROL_WIDTH = widthOptions.controlWidth ?? 0;
1861
+ const TAB_WIDTH = widthOptions.tabWidth ?? 8;
1862
+ const EMOJI_WIDTH = widthOptions.emojiWidth ?? 2;
1863
+ const FULL_WIDTH_WIDTH = 2;
1864
+ const REGULAR_WIDTH = widthOptions.regularWidth ?? 1;
1865
+ const WIDE_WIDTH = widthOptions.wideWidth ?? FULL_WIDTH_WIDTH;
1866
+ const PARSE_BLOCKS = [
1867
+ [LATIN_RE, REGULAR_WIDTH],
1868
+ [ANSI_RE, ANSI_WIDTH],
1869
+ [CONTROL_RE, CONTROL_WIDTH],
1870
+ [TAB_RE, TAB_WIDTH],
1871
+ [EMOJI_RE, EMOJI_WIDTH],
1872
+ [CJKT_WIDE_RE, WIDE_WIDTH]
1873
+ ];
1874
+ let indexPrev = 0;
1875
+ let index = 0;
1876
+ let length = input.length;
1877
+ let lengthExtra = 0;
1878
+ let truncationEnabled = false;
1879
+ let truncationIndex = length;
1880
+ let truncationLimit = Math.max(0, LIMIT - ELLIPSIS_WIDTH);
1881
+ let unmatchedStart = 0;
1882
+ let unmatchedEnd = 0;
1883
+ let width = 0;
1884
+ let widthExtra = 0;
1885
+ outer: while (true) {
1886
+ if (unmatchedEnd > unmatchedStart || index >= length && index > indexPrev) {
1887
+ const unmatched = input.slice(unmatchedStart, unmatchedEnd) || input.slice(indexPrev, index);
1888
+ lengthExtra = 0;
1889
+ for (const char of unmatched.replaceAll(MODIFIER_RE, "")) {
1890
+ const codePoint = char.codePointAt(0) || 0;
1891
+ if (isFullWidth(codePoint)) {
1892
+ widthExtra = FULL_WIDTH_WIDTH;
1893
+ } else if (isWideNotCJKTNotEmoji(codePoint)) {
1894
+ widthExtra = WIDE_WIDTH;
1895
+ } else {
1896
+ widthExtra = REGULAR_WIDTH;
1897
+ }
1898
+ if (width + widthExtra > truncationLimit) {
1899
+ truncationIndex = Math.min(truncationIndex, Math.max(unmatchedStart, indexPrev) + lengthExtra);
1900
+ }
1901
+ if (width + widthExtra > LIMIT) {
1902
+ truncationEnabled = true;
1903
+ break outer;
1904
+ }
1905
+ lengthExtra += char.length;
1906
+ width += widthExtra;
1907
+ }
1908
+ unmatchedStart = unmatchedEnd = 0;
1909
+ }
1910
+ if (index >= length) {
1911
+ break outer;
1912
+ }
1913
+ for (let i = 0, l = PARSE_BLOCKS.length; i < l; i++) {
1471
1914
  const [BLOCK_RE, BLOCK_WIDTH] = PARSE_BLOCKS[i];
1472
1915
  BLOCK_RE.lastIndex = index;
1473
1916
  if (BLOCK_RE.test(input)) {
@@ -2268,7 +2711,7 @@ ${c}
2268
2711
  } }).prompt();
2269
2712
 
2270
2713
  // ../argent-cli/src/link.ts
2271
- var import_picocolors = __toESM(require_picocolors(), 1);
2714
+ var import_picocolors2 = __toESM(require_picocolors(), 1);
2272
2715
  var WILDCARD_HOSTS = /* @__PURE__ */ new Set(["0.0.0.0", "::", "::0", ""]);
2273
2716
  function isLoopback2(host) {
2274
2717
  return host === "127.0.0.1" || host === "localhost" || host === "::1";
@@ -2420,587 +2863,311 @@ Examples:
2420
2863
  argent link --host 10.0.0.42 --port 3001 Confirms interactively, then saves.
2421
2864
  argent link --host 10.0.0.42 --token ab12\u2026c2 --yes
2422
2865
  Non-interactive, authenticated.
2423
- argent link --host 10.0.0.42 --yes --no-verify
2424
- Saves immediately, no health check.
2425
-
2426
- Security:
2427
- The token (if any) is stored 0600 and sent as a bearer header. An http:// link
2428
- has no TLS \u2014 treat any non-loopback http link as trusted-network-only. Prefer
2429
- an https:// tunnel/proxy when crossing untrusted networks.
2430
-
2431
- Notes:
2432
- - If ARGENT_TOOLS_URL is also set in your environment, it overrides the link.
2433
- - To stop using the remote target, run \`argent unlink\`.
2434
- - \`argent server start/stop/status\` manage the local tool-server lifecycle
2435
- and are unaffected by linking.
2436
- - Restart your editor (Claude / Cursor / VS Code) afterwards \u2014 a running
2437
- \`argent mcp\` process caches the target at startup and won't pick up the
2438
- new link until it's relaunched.
2439
- `);
2440
- }
2441
- function printUnlinkHelp() {
2442
- console.log(`Usage: argent unlink [flags]
2443
-
2444
- Remove the persisted remote tool-server link (~/.argent/link.json) and return
2445
- to default local auto-spawn behaviour.
2446
-
2447
- Flags:
2448
- --yes, -y Skip the confirmation prompt.
2449
- --help, -h Show this help.
2450
-
2451
- Examples:
2452
- argent unlink Confirms, then removes the link.
2453
- argent unlink --yes Removes without confirmation. Safe in scripts.
2454
-
2455
- Notes:
2456
- - No-op (exit 0) if no link is currently set.
2457
- - If ARGENT_TOOLS_URL is also set in your environment, it still takes
2458
- precedence after unlinking \u2014 unset it manually for fully local behaviour.
2459
- - This does not stop or start any tool-server process. Use
2460
- \`argent server stop\` if you also want to terminate a running local server.
2461
- - Restart your editor afterwards \u2014 a running \`argent mcp\` process won't
2462
- revert to local auto-spawn until it's relaunched.
2463
- `);
2464
- }
2465
- async function promptHost(existing, initial) {
2466
- const hostInput = await Pe({
2467
- message: "Remote host or IP to connect to",
2468
- placeholder: existing?.host ?? "127.0.0.1",
2469
- initialValue: initial ?? existing?.host,
2470
- validate(value) {
2471
- if (!value || !value.trim()) return "Host cannot be empty.";
2472
- const trimmed = value.trim();
2473
- if (WILDCARD_HOSTS.has(trimmed)) {
2474
- return `${trimmed} is a bind address, not a connect address \u2014 use 127.0.0.1 or the actual reachable host.`;
2475
- }
2476
- }
2477
- });
2478
- if (q(hostInput)) {
2479
- me("Link cancelled.");
2480
- process.exit(0);
2481
- }
2482
- return hostInput.trim();
2483
- }
2484
- async function promptPort(existing, initial) {
2485
- const defaultPort = initial ?? existing?.port ?? 3001;
2486
- const portInput = await Pe({
2487
- message: "Remote port",
2488
- placeholder: String(defaultPort),
2489
- initialValue: String(defaultPort),
2490
- validate(value) {
2491
- if (!value || !value.trim()) return "Port cannot be empty.";
2492
- try {
2493
- validateConnectPort(value.trim());
2494
- } catch (err) {
2495
- if (err instanceof StartFlagError) return err.message;
2496
- throw err;
2497
- }
2498
- }
2499
- });
2500
- if (q(portInput)) {
2501
- me("Link cancelled.");
2502
- process.exit(0);
2503
- }
2504
- return validateConnectPort(portInput.trim());
2505
- }
2506
- async function preflightHealth(url, token) {
2507
- const controller = new AbortController();
2508
- const timer = setTimeout(() => controller.abort(), 3e3);
2509
- try {
2510
- const res = await fetch(`${url}/tools`, {
2511
- signal: controller.signal,
2512
- headers: token ? { Authorization: `Bearer ${token}` } : {}
2513
- });
2514
- if (!res.ok) {
2515
- const hint = res.status === 401 ? " \u2014 the server requires a token; pass --token or use the argent:// link from `server start`" : res.status === 403 ? " \u2014 the server refused this host (DNS-rebinding guard); reach it by its bind host or start it with --host" : "";
2516
- return { ok: false, error: `${res.status} ${res.statusText}${hint}` };
2517
- }
2518
- return { ok: true };
2519
- } catch (err) {
2520
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
2521
- } finally {
2522
- clearTimeout(timer);
2523
- }
2524
- }
2525
- function printRestartHint() {
2526
- console.log(
2527
- import_picocolors.default.dim("Restart your editor to apply the change to any running `argent mcp` session.")
2528
- );
2529
- }
2530
- function printSecurityCaveat(host, token, url) {
2531
- if (isLoopback2(host)) return;
2532
- const tls = url.startsWith("https://");
2533
- if (tls) {
2534
- if (!token) {
2535
- process.stderr.write(
2536
- import_picocolors.default.yellow(
2537
- `WARNING: ${host} is reached over HTTPS but with NO token \u2014 anyone who can reach the URL can drive the server. Pair with a token.
2538
- `
2539
- )
2540
- );
2541
- }
2542
- return;
2543
- }
2544
- if (token) {
2545
- process.stderr.write(
2546
- import_picocolors.default.dim(
2547
- `Note: ${host} is reached over plain HTTP (bearer-token auth, no TLS). Keep this link to a trusted network or VPN, or front it with an https:// tunnel.
2548
- `
2549
- )
2550
- );
2551
- return;
2552
- }
2553
- process.stderr.write(
2554
- import_picocolors.default.yellow(
2555
- `WARNING: linked target ${host} is non-loopback and has NO token \u2014 tool calls travel over plain HTTP with no auth. Treat this link as trusted-network-only, or pair with a token (start the server without --no-auth).
2556
- `
2557
- )
2558
- );
2559
- }
2560
- async function link(argv) {
2561
- let flags2;
2562
- try {
2563
- flags2 = parseLinkFlags(argv);
2564
- } catch (err) {
2565
- if (err instanceof StartFlagError) {
2566
- console.error(`Error: ${err.message}
2567
- `);
2568
- printLinkHelp();
2569
- process.exit(2);
2570
- }
2571
- throw err;
2572
- }
2573
- if (flags2.help) {
2574
- printLinkHelp();
2575
- return;
2576
- }
2577
- if (flags2.yes && flags2.host === null) {
2578
- console.error("Error: --yes requires --host (port defaults to 3001).\n");
2579
- printLinkHelp();
2580
- process.exit(2);
2581
- }
2582
- const existing = await readLinkConfig();
2583
- let host;
2584
- if (flags2.host !== null) {
2585
- host = flags2.host;
2586
- } else {
2587
- ge(import_picocolors.default.bgCyan(import_picocolors.default.black(" argent link ")));
2588
- if (existing) {
2589
- R2.info(`Current link: ${import_picocolors.default.cyan(existing.url)} (${existing.createdAt})`);
2590
- }
2591
- host = await promptHost(existing);
2592
- }
2593
- let port;
2594
- if (flags2.port !== null) {
2595
- port = flags2.port;
2596
- } else if (flags2.yes) {
2597
- port = 3001;
2598
- } else {
2599
- port = await promptPort(existing);
2600
- }
2601
- const token = flags2.token ?? (existing && existing.host === host && existing.port === port ? existing.token : void 0);
2602
- let url = flags2.url ?? formatUrl(host, port);
2603
- if (!flags2.yes && existing) {
2604
- if (existing.url === url) {
2605
- R2.info(`Already linked to ${import_picocolors.default.cyan(url)}.`);
2606
- ye("No changes.");
2607
- return;
2608
- }
2609
- const overwrite = await ue({
2610
- message: `Replace existing link ${import_picocolors.default.dim(existing.url)} with ${import_picocolors.default.cyan(url)}?`,
2611
- initialValue: true
2612
- });
2613
- if (q(overwrite) || !overwrite) {
2614
- me("Link cancelled.");
2615
- process.exit(0);
2616
- }
2617
- }
2618
- if (!flags2.noVerify) {
2619
- while (true) {
2620
- const spinnerActive = !flags2.yes;
2621
- let spinner = null;
2622
- if (spinnerActive) {
2623
- spinner = ft();
2624
- spinner.start(`Verifying tool-server at ${url}...`);
2625
- }
2626
- const result = await preflightHealth(url, token);
2627
- if (result.ok) {
2628
- if (spinner) spinner.stop(import_picocolors.default.green("Tool-server reachable."));
2629
- break;
2630
- }
2631
- if (spinner) spinner.stop(import_picocolors.default.red("Verification failed."));
2632
- const detail = result.error ? ` (${result.error})` : "";
2633
- if (flags2.yes) {
2634
- console.error(
2635
- `Error: pre-flight GET ${url}/tools failed${detail}. Make sure the remote tool-server is running, or pass --no-verify to skip.`
2636
- );
2637
- process.exit(1);
2638
- }
2639
- R2.error(`pre-flight GET ${url}/tools failed${detail}.`);
2640
- const action = await xe({
2641
- message: "How would you like to proceed?",
2642
- options: [
2643
- { value: "retry", label: "Retry verification" },
2644
- { value: "modify", label: "Modify host and port, then retry" },
2645
- { value: "skip", label: "Skip verification and save anyway" },
2646
- { value: "cancel", label: "Cancel" }
2647
- ],
2648
- initialValue: "retry"
2649
- });
2650
- if (q(action) || action === "cancel") {
2651
- me("Link cancelled.");
2652
- process.exit(0);
2653
- }
2654
- if (action === "skip") break;
2655
- if (action === "modify") {
2656
- host = await promptHost(existing, host);
2657
- port = await promptPort(existing, port);
2658
- url = formatUrl(host, port);
2659
- }
2660
- }
2661
- }
2662
- const cfg = {
2663
- url,
2664
- host,
2665
- port,
2666
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2667
- ...token ? { token } : {}
2668
- };
2669
- await writeLinkConfig(cfg);
2670
- if (existing && existing.url !== url) {
2671
- console.log(`${import_picocolors.default.green("\u2713")} Link updated: ${import_picocolors.default.dim(existing.url)} \u2192 ${import_picocolors.default.cyan(url)}`);
2672
- } else {
2673
- console.log(`${import_picocolors.default.green("\u2713")} Linked: ${import_picocolors.default.cyan(url)}`);
2674
- }
2675
- if (token) console.log(import_picocolors.default.dim(" auth: token stored in ~/.argent/link.json (0600)"));
2676
- printSecurityCaveat(host, token, url);
2677
- if (process.env.ARGENT_TOOLS_URL) {
2678
- console.log(
2679
- import_picocolors.default.yellow(
2680
- `Note: ARGENT_TOOLS_URL=${process.env.ARGENT_TOOLS_URL} is set in your environment and takes precedence over the link.`
2681
- )
2682
- );
2683
- }
2684
- printRestartHint();
2685
- }
2686
- async function unlink3(argv) {
2687
- let flags2;
2688
- try {
2689
- flags2 = parseUnlinkFlags(argv);
2690
- } catch (err) {
2691
- if (err instanceof StartFlagError) {
2692
- console.error(`Error: ${err.message}
2866
+ argent link --host 10.0.0.42 --yes --no-verify
2867
+ Saves immediately, no health check.
2868
+
2869
+ Security:
2870
+ The token (if any) is stored 0600 and sent as a bearer header. An http:// link
2871
+ has no TLS \u2014 treat any non-loopback http link as trusted-network-only. Prefer
2872
+ an https:// tunnel/proxy when crossing untrusted networks.
2873
+
2874
+ Notes:
2875
+ - If ARGENT_TOOLS_URL is also set in your environment, it overrides the link.
2876
+ - To stop using the remote target, run \`argent unlink\`.
2877
+ - \`argent server start/stop/status\` manage the local tool-server lifecycle
2878
+ and are unaffected by linking.
2879
+ - Restart your editor (Claude / Cursor / VS Code) afterwards \u2014 a running
2880
+ \`argent mcp\` process caches the target at startup and won't pick up the
2881
+ new link until it's relaunched.
2693
2882
  `);
2694
- printUnlinkHelp();
2695
- process.exit(2);
2696
- }
2697
- throw err;
2698
- }
2699
- if (flags2.help) {
2700
- printUnlinkHelp();
2701
- return;
2702
- }
2703
- const existing = await readLinkConfig();
2704
- if (!existing) {
2705
- console.log("Not currently linked \u2014 already using local tool-server.");
2706
- return;
2707
- }
2708
- if (!flags2.yes) {
2709
- const confirmed = await ue({
2710
- message: `Remove link to ${import_picocolors.default.cyan(existing.url)}?`,
2711
- initialValue: true
2712
- });
2713
- if (q(confirmed) || !confirmed) {
2714
- me("Unlink cancelled.");
2715
- process.exit(0);
2716
- }
2717
- }
2718
- await clearLinkConfig();
2719
- console.log(`${import_picocolors.default.green("\u2713")} Unlinked from ${import_picocolors.default.dim(existing.url)}.`);
2720
- if (process.env.ARGENT_TOOLS_URL) {
2721
- console.log(
2722
- import_picocolors.default.yellow(
2723
- `The env var ARGENT_TOOLS_URL is also set in your shell (=${process.env.ARGENT_TOOLS_URL}) and takes precedence \u2014 unset it manually if you want fully local behaviour.`
2724
- )
2725
- );
2726
- }
2727
- printRestartHint();
2728
2883
  }
2884
+ function printUnlinkHelp() {
2885
+ console.log(`Usage: argent unlink [flags]
2729
2886
 
2730
- // ../argent-cli/src/flags.ts
2731
- import * as fs4 from "node:fs";
2732
- import * as path5 from "node:path";
2733
- import { homedir as homedir4 } from "node:os";
2734
- var FLAG_REGISTRY = [
2735
- // {
2736
- // name: "example-flag",
2737
- // description: "One-line summary shown by `argent flags`.",
2738
- // },
2739
- ];
2740
- function getFlagDefinition(name, registry = FLAG_REGISTRY) {
2741
- return registry.find((def) => def.name === name);
2887
+ Remove the persisted remote tool-server link (~/.argent/link.json) and return
2888
+ to default local auto-spawn behaviour.
2889
+
2890
+ Flags:
2891
+ --yes, -y Skip the confirmation prompt.
2892
+ --help, -h Show this help.
2893
+
2894
+ Examples:
2895
+ argent unlink Confirms, then removes the link.
2896
+ argent unlink --yes Removes without confirmation. Safe in scripts.
2897
+
2898
+ Notes:
2899
+ - No-op (exit 0) if no link is currently set.
2900
+ - If ARGENT_TOOLS_URL is also set in your environment, it still takes
2901
+ precedence after unlinking \u2014 unset it manually for fully local behaviour.
2902
+ - This does not stop or start any tool-server process. Use
2903
+ \`argent server stop\` if you also want to terminate a running local server.
2904
+ - Restart your editor afterwards \u2014 a running \`argent mcp\` process won't
2905
+ revert to local auto-spawn until it's relaunched.
2906
+ `);
2742
2907
  }
2743
- var PROJECT_MARKERS = [".argent", ".git", "package.json"];
2744
- function resolveProjectRoot(startDir) {
2745
- const initial = path5.resolve(startDir);
2746
- let current = initial;
2747
- while (true) {
2748
- for (const marker of PROJECT_MARKERS) {
2749
- if (fs4.existsSync(path5.join(current, marker))) return current;
2908
+ async function promptHost(existing, initial) {
2909
+ const hostInput = await Pe({
2910
+ message: "Remote host or IP to connect to",
2911
+ placeholder: existing?.host ?? "127.0.0.1",
2912
+ initialValue: initial ?? existing?.host,
2913
+ validate(value) {
2914
+ if (!value || !value.trim()) return "Host cannot be empty.";
2915
+ const trimmed = value.trim();
2916
+ if (WILDCARD_HOSTS.has(trimmed)) {
2917
+ return `${trimmed} is a bind address, not a connect address \u2014 use 127.0.0.1 or the actual reachable host.`;
2918
+ }
2750
2919
  }
2751
- const parent = path5.dirname(current);
2752
- if (parent === current) return initial;
2753
- current = parent;
2920
+ });
2921
+ if (q(hostInput)) {
2922
+ me("Link cancelled.");
2923
+ process.exit(0);
2754
2924
  }
2925
+ return hostInput.trim();
2755
2926
  }
2756
- function getFlagsPath(scope, options = {}) {
2757
- const home = options.homeDir ?? homedir4();
2758
- if (scope === "global") {
2759
- return path5.join(home, ".argent", "flags.json");
2927
+ async function promptPort(existing, initial) {
2928
+ const defaultPort = initial ?? existing?.port ?? 3001;
2929
+ const portInput = await Pe({
2930
+ message: "Remote port",
2931
+ placeholder: String(defaultPort),
2932
+ initialValue: String(defaultPort),
2933
+ validate(value) {
2934
+ if (!value || !value.trim()) return "Port cannot be empty.";
2935
+ try {
2936
+ validateConnectPort(value.trim());
2937
+ } catch (err) {
2938
+ if (err instanceof StartFlagError) return err.message;
2939
+ throw err;
2940
+ }
2941
+ }
2942
+ });
2943
+ if (q(portInput)) {
2944
+ me("Link cancelled.");
2945
+ process.exit(0);
2760
2946
  }
2761
- const cwd = options.cwd ?? process.cwd();
2762
- return path5.join(resolveProjectRoot(cwd), ".argent", "flags.json");
2947
+ return validateConnectPort(portInput.trim());
2763
2948
  }
2764
- function readFlagsFile(filePath) {
2765
- let raw;
2766
- try {
2767
- raw = fs4.readFileSync(filePath, "utf8");
2768
- } catch {
2769
- return {};
2770
- }
2771
- let parsed;
2949
+ async function preflightHealth(url, token) {
2950
+ const controller = new AbortController();
2951
+ const timer = setTimeout(() => controller.abort(), 3e3);
2772
2952
  try {
2773
- parsed = JSON.parse(raw);
2774
- } catch {
2775
- return {};
2776
- }
2777
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
2778
- const flags2 = parsed.flags;
2779
- if (!flags2 || typeof flags2 !== "object" || Array.isArray(flags2)) return {};
2780
- const out = {};
2781
- for (const [k2, v2] of Object.entries(flags2)) {
2782
- if (typeof v2 === "boolean") out[k2] = v2;
2783
- }
2784
- return out;
2785
- }
2786
- function writeFlagsFile(filePath, flags2) {
2787
- if (Object.keys(flags2).length === 0) {
2788
- if (fs4.existsSync(filePath)) fs4.rmSync(filePath, { force: true });
2789
- const parent = path5.dirname(filePath);
2790
- try {
2791
- if (fs4.existsSync(parent) && fs4.readdirSync(parent).length === 0) {
2792
- fs4.rmdirSync(parent);
2793
- }
2794
- } catch {
2953
+ const res = await fetch(`${url}/tools`, {
2954
+ signal: controller.signal,
2955
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
2956
+ });
2957
+ if (!res.ok) {
2958
+ const hint = res.status === 401 ? " \u2014 the server requires a token; pass --token or use the argent:// link from `server start`" : res.status === 403 ? " \u2014 the server refused this host (DNS-rebinding guard); reach it by its bind host or start it with --host" : "";
2959
+ return { ok: false, error: `${res.status} ${res.statusText}${hint}` };
2795
2960
  }
2796
- return;
2961
+ return { ok: true };
2962
+ } catch (err) {
2963
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
2964
+ } finally {
2965
+ clearTimeout(timer);
2797
2966
  }
2798
- fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
2799
- const tmp = `${filePath}.${process.pid}.tmp`;
2800
- fs4.writeFileSync(tmp, JSON.stringify({ flags: flags2 }, null, 2) + "\n");
2801
- fs4.renameSync(tmp, filePath);
2802
- }
2803
- function readFlags(scope, options = {}) {
2804
- return readFlagsFile(getFlagsPath(scope, options));
2805
- }
2806
- function setFlag(name, value, scope, options = {}) {
2807
- const filePath = getFlagsPath(scope, options);
2808
- const current = readFlagsFile(filePath);
2809
- current[name] = value;
2810
- writeFlagsFile(filePath, current);
2811
- }
2812
- function unsetFlag(name, scope, options = {}) {
2813
- const filePath = getFlagsPath(scope, options);
2814
- const current = readFlagsFile(filePath);
2815
- if (!Object.hasOwn(current, name)) return false;
2816
- delete current[name];
2817
- writeFlagsFile(filePath, current);
2818
- return true;
2819
2967
  }
2820
- function isFlagEnabled(name, options = {}) {
2821
- const projectFlags = readFlags("project", options);
2822
- if (Object.hasOwn(projectFlags, name)) return projectFlags[name];
2823
- const globalFlags = readFlags("global", options);
2824
- if (Object.hasOwn(globalFlags, name)) return globalFlags[name];
2825
- return false;
2968
+ function printRestartHint() {
2969
+ console.log(
2970
+ import_picocolors2.default.dim("Restart your editor to apply the change to any running `argent mcp` session.")
2971
+ );
2826
2972
  }
2827
- var FLAG_NAME_RE = /^[a-zA-Z][a-zA-Z0-9._-]*$/;
2828
- function parseToggleArgs(argv, command) {
2829
- let name = null;
2830
- let scope = "global";
2831
- let positionalOnly = false;
2832
- for (let i = 0; i < argv.length; i++) {
2833
- const tok = argv[i];
2834
- if (positionalOnly) {
2835
- if (name !== null) throw new Error(`Unexpected extra argument: "${tok}"`);
2836
- name = tok;
2837
- continue;
2838
- }
2839
- if (tok === "--") {
2840
- positionalOnly = true;
2841
- continue;
2842
- }
2843
- if (tok === "--scope") {
2844
- const v2 = argv[i + 1];
2845
- if (v2 === void 0) throw new Error("--scope requires a value (project|global)");
2846
- scope = parseScope(v2);
2847
- i += 1;
2848
- continue;
2849
- }
2850
- if (tok.startsWith("--scope=")) {
2851
- scope = parseScope(tok.slice("--scope=".length));
2852
- continue;
2853
- }
2854
- if (tok.startsWith("--")) {
2855
- throw new Error(`Unknown flag: ${tok}`);
2856
- }
2857
- if (name !== null) {
2858
- throw new Error(`Unexpected extra argument: "${tok}"`);
2973
+ function printSecurityCaveat(host, token, url) {
2974
+ if (isLoopback2(host)) return;
2975
+ const tls = url.startsWith("https://");
2976
+ if (tls) {
2977
+ if (!token) {
2978
+ process.stderr.write(
2979
+ import_picocolors2.default.yellow(
2980
+ `WARNING: ${host} is reached over HTTPS but with NO token \u2014 anyone who can reach the URL can drive the server. Pair with a token.
2981
+ `
2982
+ )
2983
+ );
2859
2984
  }
2860
- name = tok;
2861
- }
2862
- if (name === null) {
2863
- throw new Error(`Usage: argent ${command} <flag-name> [--scope project|global]`);
2864
- }
2865
- if (!FLAG_NAME_RE.test(name)) {
2866
- throw new Error(
2867
- `Invalid flag name "${name}". Must start with a letter and contain only letters, digits, ".", "_", or "-".`
2985
+ return;
2986
+ }
2987
+ if (token) {
2988
+ process.stderr.write(
2989
+ import_picocolors2.default.dim(
2990
+ `Note: ${host} is reached over plain HTTP (bearer-token auth, no TLS). Keep this link to a trusted network or VPN, or front it with an https:// tunnel.
2991
+ `
2992
+ )
2868
2993
  );
2994
+ return;
2869
2995
  }
2870
- return { name, scope };
2871
- }
2872
- function parseScope(raw) {
2873
- if (raw === "global" || raw === "project") return raw;
2874
- throw new Error(`--scope must be "project" or "global", got "${raw}"`);
2996
+ process.stderr.write(
2997
+ import_picocolors2.default.yellow(
2998
+ `WARNING: linked target ${host} is non-loopback and has NO token \u2014 tool calls travel over plain HTTP with no auth. Treat this link as trusted-network-only, or pair with a token (start the server without --no-auth).
2999
+ `
3000
+ )
3001
+ );
2875
3002
  }
2876
- function printToggleHelp(command) {
2877
- const summary = command === "enable" ? "Enable a predefined feature flag (see `argent flags`) at the chosen scope." : "Remove a feature flag entry at the chosen scope. Falls back to the global value if set; otherwise the flag is treated as off.";
2878
- console.log(`Usage: argent ${command} <flag-name> [--scope project|global]
2879
-
2880
- ${summary}
2881
-
2882
- Storage:
2883
- ~/.argent/flags.json (global, default)
2884
- <project-root>/.argent/flags.json (project, with --scope project)
2885
-
2886
- Options:
2887
- --scope <global|project> Where to write (default: global)
2888
- --help, -h Show this help
3003
+ async function link(argv) {
3004
+ let flags2;
3005
+ try {
3006
+ flags2 = parseLinkFlags(argv);
3007
+ } catch (err) {
3008
+ if (err instanceof StartFlagError) {
3009
+ console.error(`Error: ${err.message}
2889
3010
  `);
2890
- }
2891
- function runToggle(argv, command, registry) {
2892
- if (argv.includes("--help") || argv.includes("-h")) {
2893
- printToggleHelp(command);
3011
+ printLinkHelp();
3012
+ process.exit(2);
3013
+ }
3014
+ throw err;
3015
+ }
3016
+ if (flags2.help) {
3017
+ printLinkHelp();
2894
3018
  return;
2895
3019
  }
2896
- let parsed;
2897
- try {
2898
- parsed = parseToggleArgs(argv, command);
2899
- } catch (err) {
2900
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
3020
+ if (flags2.yes && flags2.host === null) {
3021
+ console.error("Error: --yes requires --host (port defaults to 3001).\n");
3022
+ printLinkHelp();
2901
3023
  process.exit(2);
2902
3024
  }
2903
- if (command === "enable" && getFlagDefinition(parsed.name, registry) === void 0) {
2904
- console.error(
2905
- `Error: Unknown feature flag "${parsed.name}". Run \`argent flags\` to see available flags.`
2906
- );
2907
- process.exit(2);
3025
+ const existing = await readLinkConfig();
3026
+ let host;
3027
+ if (flags2.host !== null) {
3028
+ host = flags2.host;
3029
+ } else {
3030
+ ge(import_picocolors2.default.bgCyan(import_picocolors2.default.black(" argent link ")));
3031
+ if (existing) {
3032
+ R2.info(`Current link: ${import_picocolors2.default.cyan(existing.url)} (${existing.createdAt})`);
3033
+ }
3034
+ host = await promptHost(existing);
2908
3035
  }
2909
- const filePath = getFlagsPath(parsed.scope);
2910
- try {
2911
- if (command === "enable") {
2912
- setFlag(parsed.name, true, parsed.scope);
2913
- } else {
2914
- unsetFlag(parsed.name, parsed.scope);
3036
+ let port;
3037
+ if (flags2.port !== null) {
3038
+ port = flags2.port;
3039
+ } else if (flags2.yes) {
3040
+ port = 3001;
3041
+ } else {
3042
+ port = await promptPort(existing);
3043
+ }
3044
+ const token = flags2.token ?? (existing && existing.host === host && existing.port === port ? existing.token : void 0);
3045
+ let url = flags2.url ?? formatUrl(host, port);
3046
+ if (!flags2.yes && existing) {
3047
+ if (existing.url === url) {
3048
+ R2.info(`Already linked to ${import_picocolors2.default.cyan(url)}.`);
3049
+ ye("No changes.");
3050
+ return;
3051
+ }
3052
+ const overwrite = await ue({
3053
+ message: `Replace existing link ${import_picocolors2.default.dim(existing.url)} with ${import_picocolors2.default.cyan(url)}?`,
3054
+ initialValue: true
3055
+ });
3056
+ if (q(overwrite) || !overwrite) {
3057
+ me("Link cancelled.");
3058
+ process.exit(0);
2915
3059
  }
2916
- } catch (err) {
2917
- console.error(`Failed to ${command} flag: ${err instanceof Error ? err.message : String(err)}`);
2918
- process.exit(1);
2919
3060
  }
2920
- if (command === "enable") {
2921
- console.log(`Enabled flag "${parsed.name}" (${parsed.scope}). Stored at ${filePath}.`);
3061
+ if (!flags2.noVerify) {
3062
+ while (true) {
3063
+ const spinnerActive = !flags2.yes;
3064
+ let spinner = null;
3065
+ if (spinnerActive) {
3066
+ spinner = ft();
3067
+ spinner.start(`Verifying tool-server at ${url}...`);
3068
+ }
3069
+ const result = await preflightHealth(url, token);
3070
+ if (result.ok) {
3071
+ if (spinner) spinner.stop(import_picocolors2.default.green("Tool-server reachable."));
3072
+ break;
3073
+ }
3074
+ if (spinner) spinner.stop(import_picocolors2.default.red("Verification failed."));
3075
+ const detail = result.error ? ` (${result.error})` : "";
3076
+ if (flags2.yes) {
3077
+ console.error(
3078
+ `Error: pre-flight GET ${url}/tools failed${detail}. Make sure the remote tool-server is running, or pass --no-verify to skip.`
3079
+ );
3080
+ process.exit(1);
3081
+ }
3082
+ R2.error(`pre-flight GET ${url}/tools failed${detail}.`);
3083
+ const action = await xe({
3084
+ message: "How would you like to proceed?",
3085
+ options: [
3086
+ { value: "retry", label: "Retry verification" },
3087
+ { value: "modify", label: "Modify host and port, then retry" },
3088
+ { value: "skip", label: "Skip verification and save anyway" },
3089
+ { value: "cancel", label: "Cancel" }
3090
+ ],
3091
+ initialValue: "retry"
3092
+ });
3093
+ if (q(action) || action === "cancel") {
3094
+ me("Link cancelled.");
3095
+ process.exit(0);
3096
+ }
3097
+ if (action === "skip") break;
3098
+ if (action === "modify") {
3099
+ host = await promptHost(existing, host);
3100
+ port = await promptPort(existing, port);
3101
+ url = formatUrl(host, port);
3102
+ }
3103
+ }
3104
+ }
3105
+ const cfg = {
3106
+ url,
3107
+ host,
3108
+ port,
3109
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3110
+ ...token ? { token } : {}
3111
+ };
3112
+ await writeLinkConfig(cfg);
3113
+ if (existing && existing.url !== url) {
3114
+ console.log(`${import_picocolors2.default.green("\u2713")} Link updated: ${import_picocolors2.default.dim(existing.url)} \u2192 ${import_picocolors2.default.cyan(url)}`);
2922
3115
  } else {
2923
- console.log(`Disabled flag "${parsed.name}" (${parsed.scope}).`);
3116
+ console.log(`${import_picocolors2.default.green("\u2713")} Linked: ${import_picocolors2.default.cyan(url)}`);
2924
3117
  }
3118
+ if (token) console.log(import_picocolors2.default.dim(" auth: token stored in ~/.argent/link.json (0600)"));
3119
+ printSecurityCaveat(host, token, url);
3120
+ if (process.env.ARGENT_TOOLS_URL) {
3121
+ console.log(
3122
+ import_picocolors2.default.yellow(
3123
+ `Note: ARGENT_TOOLS_URL=${process.env.ARGENT_TOOLS_URL} is set in your environment and takes precedence over the link.`
3124
+ )
3125
+ );
3126
+ }
3127
+ printRestartHint();
2925
3128
  }
2926
- function enable(argv, registry = FLAG_REGISTRY) {
2927
- runToggle(argv, "enable", registry);
2928
- }
2929
- function disable(argv, registry = FLAG_REGISTRY) {
2930
- runToggle(argv, "disable", registry);
2931
- }
2932
- function flags(argv, registry = FLAG_REGISTRY) {
2933
- if (argv.includes("--help") || argv.includes("-h")) {
2934
- console.log(`Usage: argent flags [--json]
2935
-
2936
- List the available feature flags and their current state. Flags are
2937
- predefined; project-scoped values override global ones.
2938
-
2939
- Options:
2940
- --json Print machine-readable JSON
3129
+ async function unlink3(argv) {
3130
+ let flags2;
3131
+ try {
3132
+ flags2 = parseUnlinkFlags(argv);
3133
+ } catch (err) {
3134
+ if (err instanceof StartFlagError) {
3135
+ console.error(`Error: ${err.message}
2941
3136
  `);
3137
+ printUnlinkHelp();
3138
+ process.exit(2);
3139
+ }
3140
+ throw err;
3141
+ }
3142
+ if (flags2.help) {
3143
+ printUnlinkHelp();
2942
3144
  return;
2943
3145
  }
2944
- const json = argv.includes("--json");
2945
- const projectFlags = readFlags("project");
2946
- const globalFlags = readFlags("global");
2947
- const effective = {};
2948
- for (const [k2, v2] of Object.entries(globalFlags)) effective[k2] = { value: v2, scope: "global" };
2949
- for (const [k2, v2] of Object.entries(projectFlags)) effective[k2] = { value: v2, scope: "project" };
2950
- const registryView = registry.map((def) => {
2951
- const eff = Object.hasOwn(effective, def.name) ? effective[def.name] : void 0;
2952
- return {
2953
- name: def.name,
2954
- description: def.description,
2955
- enabled: eff?.value ?? false,
2956
- scope: eff?.scope ?? null
2957
- };
2958
- });
2959
- const known = new Set(registry.map((def) => def.name));
2960
- const unrecognized = Object.keys(effective).filter((name) => !known.has(name)).sort().map((name) => ({ name, enabled: effective[name].value, scope: effective[name].scope }));
2961
- if (json) {
2962
- console.log(
2963
- JSON.stringify(
2964
- {
2965
- flags: registryView,
2966
- unrecognized,
2967
- global: globalFlags,
2968
- project: projectFlags,
2969
- effective,
2970
- paths: {
2971
- global: getFlagsPath("global"),
2972
- project: getFlagsPath("project")
2973
- }
2974
- },
2975
- null,
2976
- 2
2977
- )
2978
- );
3146
+ const existing = await readLinkConfig();
3147
+ if (!existing) {
3148
+ console.log("Not currently linked \u2014 already using local tool-server.");
2979
3149
  return;
2980
3150
  }
2981
- if (registryView.length === 0) {
2982
- console.log("No feature flags are defined.");
2983
- } else {
2984
- console.log("Feature flags (project overrides global):");
2985
- const maxName = registryView.reduce((m2, f2) => Math.max(m2, f2.name.length), 0);
2986
- for (const f2 of registryView) {
2987
- const stateLabel = f2.enabled ? "enabled" : "disabled";
2988
- const scopeLabel = f2.scope ? ` (${f2.scope})` : "";
2989
- console.log(` ${f2.name.padEnd(maxName, " ")} ${stateLabel.padEnd(8)}${scopeLabel}`);
2990
- console.log(` ${" ".repeat(maxName)} ${f2.description}`);
3151
+ if (!flags2.yes) {
3152
+ const confirmed = await ue({
3153
+ message: `Remove link to ${import_picocolors2.default.cyan(existing.url)}?`,
3154
+ initialValue: true
3155
+ });
3156
+ if (q(confirmed) || !confirmed) {
3157
+ me("Unlink cancelled.");
3158
+ process.exit(0);
2991
3159
  }
2992
3160
  }
2993
- if (unrecognized.length > 0) {
2994
- console.log("\nStored but no longer recognized (safe to `argent disable`):");
2995
- const maxName = unrecognized.reduce((m2, f2) => Math.max(m2, f2.name.length), 0);
2996
- for (const f2 of unrecognized) {
2997
- const stateLabel = f2.enabled ? "enabled" : "disabled";
2998
- console.log(` ${f2.name.padEnd(maxName, " ")} ${stateLabel.padEnd(8)} (${f2.scope})`);
2999
- }
3161
+ await clearLinkConfig();
3162
+ console.log(`${import_picocolors2.default.green("\u2713")} Unlinked from ${import_picocolors2.default.dim(existing.url)}.`);
3163
+ if (process.env.ARGENT_TOOLS_URL) {
3164
+ console.log(
3165
+ import_picocolors2.default.yellow(
3166
+ `The env var ARGENT_TOOLS_URL is also set in your shell (=${process.env.ARGENT_TOOLS_URL}) and takes precedence \u2014 unset it manually if you want fully local behaviour.`
3167
+ )
3168
+ );
3000
3169
  }
3001
- console.log(`
3002
- Global: ${getFlagsPath("global")}`);
3003
- console.log(` Project: ${getFlagsPath("project")}`);
3170
+ printRestartHint();
3004
3171
  }
3005
3172
  export {
3006
3173
  FLAG_REGISTRY,