@zleap-ai/sag-cli 0.3.0 → 0.4.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.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { confirm, password, select } from "@inquirer/prompts";
4
+ import { confirm, input, select } from "@inquirer/prompts";
5
5
 
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@zleap-ai/sag-cli",
9
- version: "0.3.0",
9
+ version: "0.4.0",
10
10
  description: "Command-line client and diagnostics for SAG knowledge bases",
11
11
  type: "module",
12
12
  bin: {
@@ -177,7 +177,7 @@ var claudeEntrySchema = z.object({
177
177
  function invalidSpec() {
178
178
  return new CliError(
179
179
  "INVALID_ARGUMENT",
180
- "Claude Code v0.2 supports Docker stdio MCP connections only",
180
+ "The Claude Code adapter supports Docker stdio MCP connections only",
181
181
  {
182
182
  exitCode: exitCodes.invalidArgument
183
183
  }
@@ -206,6 +206,12 @@ var ClaudeCodeAdapter = class {
206
206
  }
207
207
  runner;
208
208
  id = "claude-code";
209
+ capabilities = {
210
+ transports: ["stdio"],
211
+ scopes: ["user"],
212
+ structuredRead: true,
213
+ projectApproval: false
214
+ };
209
215
  readConfig;
210
216
  async detect() {
211
217
  try {
@@ -294,7 +300,7 @@ var CODEX_STATUS_FIELDS = /* @__PURE__ */ new Set(["name", "disabled_reason", "a
294
300
  function invalidSpec2() {
295
301
  return new CliError(
296
302
  "INVALID_ARGUMENT",
297
- "Codex v0.2 supports Docker stdio MCP connections only",
303
+ "The Codex adapter supports Docker stdio MCP connections only",
298
304
  {
299
305
  exitCode: exitCodes.invalidArgument
300
306
  }
@@ -318,6 +324,12 @@ var CodexAdapter = class {
318
324
  }
319
325
  runner;
320
326
  id = "codex";
327
+ capabilities = {
328
+ transports: ["stdio"],
329
+ scopes: ["user"],
330
+ structuredRead: true,
331
+ projectApproval: false
332
+ };
321
333
  async detect() {
322
334
  try {
323
335
  const result = await this.runner.run({
@@ -534,9 +546,9 @@ var ManagedConnectionStore = class {
534
546
  await this.#saveUnlocked(state);
535
547
  });
536
548
  }
537
- async list(input) {
549
+ async list(input2) {
538
550
  return Object.values((await this.load()).connections).filter(
539
- (connection) => (!input?.agent || connection.agent === input.agent) && (!input?.scope || connection.scope === input.scope)
551
+ (connection) => (!input2?.agent || connection.agent === input2.agent) && (!input2?.scope || connection.scope === input2.scope)
540
552
  );
541
553
  }
542
554
  };
@@ -558,12 +570,12 @@ function validateProfileName(name) {
558
570
  }
559
571
  return name;
560
572
  }
561
- function normalizeOrigin(input) {
573
+ function normalizeOrigin(input2) {
562
574
  let url;
563
575
  try {
564
- url = new URL(input);
576
+ url = new URL(input2);
565
577
  } catch (cause) {
566
- throw new CliError("INVALID_ARGUMENT", `Invalid SAG URL: ${input}`, {
578
+ throw new CliError("INVALID_ARGUMENT", `Invalid SAG URL: ${input2}`, {
567
579
  exitCode: exitCodes.invalidArgument,
568
580
  cause,
569
581
  hint: "Use an absolute HTTP or HTTPS URL such as http://localhost:8000."
@@ -1090,16 +1102,6 @@ var McpVerifier = class {
1090
1102
  }
1091
1103
  cliVersion;
1092
1104
  async verify(spec, options) {
1093
- if (spec.transport !== "stdio") {
1094
- throw new CliError(
1095
- "INVALID_ARGUMENT",
1096
- "HTTP MCP is not supported in SAG CLI v0.2",
1097
- {
1098
- exitCode: exitCodes.invalidArgument,
1099
- hint: "Use a local Docker stdio connection."
1100
- }
1101
- );
1102
- }
1103
1105
  const client = new Client(
1104
1106
  {
1105
1107
  name: "@zleap-ai/sag-cli",
@@ -1191,12 +1193,12 @@ var McpVerifier = class {
1191
1193
  // src/process/runner.ts
1192
1194
  import { spawn } from "child_process";
1193
1195
  var NodeProcessRunner = class {
1194
- async run(input) {
1196
+ async run(input2) {
1195
1197
  return new Promise((resolve, reject) => {
1196
- const child = spawn(input.command, input.args, {
1198
+ const child = spawn(input2.command, input2.args, {
1197
1199
  shell: false,
1198
- env: input.env ?? process.env,
1199
- stdio: [input.stdin === "pipe" ? "pipe" : "ignore", "pipe", "pipe"]
1200
+ env: input2.env ?? process.env,
1201
+ stdio: [input2.stdin === "pipe" ? "pipe" : "ignore", "pipe", "pipe"]
1200
1202
  });
1201
1203
  const childStdout = child.stdout;
1202
1204
  const childStderr = child.stderr;
@@ -1210,7 +1212,7 @@ var NodeProcessRunner = class {
1210
1212
  forceKill = setTimeout(() => {
1211
1213
  child.kill("SIGKILL");
1212
1214
  }, 250);
1213
- }, input.timeoutMs);
1215
+ }, input2.timeoutMs);
1214
1216
  childStdout?.setEncoding("utf8");
1215
1217
  childStderr?.setEncoding("utf8");
1216
1218
  childStdout?.on("data", (chunk) => {
@@ -1223,10 +1225,10 @@ var NodeProcessRunner = class {
1223
1225
  clearTimeout(timeout);
1224
1226
  if (forceKill) clearTimeout(forceKill);
1225
1227
  reject(
1226
- new CliError("DEPENDENCY_MISSING", `Unable to start ${input.command}`, {
1228
+ new CliError("DEPENDENCY_MISSING", `Unable to start ${input2.command}`, {
1227
1229
  exitCode: exitCodes.dependencyMissing,
1228
1230
  cause,
1229
- hint: `Install ${input.command} and ensure it is available on PATH.`
1231
+ hint: `Install ${input2.command} and ensure it is available on PATH.`
1230
1232
  })
1231
1233
  );
1232
1234
  });
@@ -1235,9 +1237,9 @@ var NodeProcessRunner = class {
1235
1237
  if (forceKill) clearTimeout(forceKill);
1236
1238
  if (timedOut) {
1237
1239
  reject(
1238
- new CliError("DEPENDENCY_MISSING", `${input.command} timed out`, {
1240
+ new CliError("DEPENDENCY_MISSING", `${input2.command} timed out`, {
1239
1241
  exitCode: exitCodes.dependencyMissing,
1240
- hint: `Check that ${input.command} is responsive and try again.`
1242
+ hint: `Check that ${input2.command} is responsive and try again.`
1241
1243
  })
1242
1244
  );
1243
1245
  return;
@@ -1274,6 +1276,10 @@ var userSchema = z6.object({
1274
1276
  name: z6.string(),
1275
1277
  created_at: dateTime.optional()
1276
1278
  }).passthrough();
1279
+ var loginResponseSchema = z6.object({
1280
+ access_token: z6.string().min(1),
1281
+ user: userSchema
1282
+ }).passthrough();
1277
1283
  var sourceSchema = z6.object({
1278
1284
  id: z6.string().min(1),
1279
1285
  name: z6.string(),
@@ -1423,6 +1429,14 @@ var SagClient = class {
1423
1429
  me() {
1424
1430
  return this.#request("/api/v1/auth/me", userSchema);
1425
1431
  }
1432
+ async login(name) {
1433
+ const response = await this.#request("/api/v1/auth/login", loginResponseSchema, {
1434
+ authenticated: false,
1435
+ method: "POST",
1436
+ body: { name }
1437
+ });
1438
+ return { accessToken: response.access_token, user: response.user };
1439
+ }
1426
1440
  mcpDescriptor() {
1427
1441
  return this.#request("/api/v1/system/mcp", mcpDescriptorSchema);
1428
1442
  }
@@ -1447,10 +1461,10 @@ var SagClient = class {
1447
1461
  documentSchema
1448
1462
  );
1449
1463
  }
1450
- search(input) {
1464
+ search(input2) {
1451
1465
  return this.#request("/api/v1/search", searchResponseSchema, {
1452
1466
  method: "POST",
1453
- body: input
1467
+ body: input2
1454
1468
  });
1455
1469
  }
1456
1470
  outline(sourceId, documentId) {
@@ -1603,81 +1617,98 @@ function hostFailure3(message, cause, hint) {
1603
1617
  hint: hint ?? "Inspect the Agent MCP configuration and run the command again."
1604
1618
  });
1605
1619
  }
1606
- function determinePlan(input) {
1620
+ function determinePlan(input2) {
1607
1621
  let action;
1608
- if (!input.hostFingerprint || !input.hostSpecFingerprint) {
1609
- if (input.managed) {
1622
+ if (!input2.hostFingerprint || !input2.hostSpecFingerprint) {
1623
+ if (input2.managed) {
1610
1624
  throw conflict("Managed state exists but the Agent MCP entry is missing");
1611
1625
  }
1612
1626
  action = "create";
1613
- } else if (!input.managed) {
1627
+ } else if (!input2.managed) {
1614
1628
  throw conflict("The Agent MCP name is already owned by the user");
1615
- } else if (input.managed.fingerprint !== input.hostFingerprint) {
1629
+ } else if (input2.managed.fingerprint !== input2.hostFingerprint) {
1616
1630
  throw conflict("The managed Agent MCP entry was changed outside SAG CLI");
1617
- } else if (input.hostSpecFingerprint === input.desiredFingerprint) {
1631
+ } else if (input2.hostSpecFingerprint === input2.desiredFingerprint) {
1618
1632
  action = "unchanged";
1619
1633
  } else {
1620
1634
  action = "update";
1621
1635
  }
1622
1636
  return {
1623
1637
  action,
1624
- agent: input.agent,
1625
- serverName: input.serverName,
1626
- scope: input.scope,
1638
+ agent: input2.agent,
1639
+ serverName: input2.serverName,
1640
+ scope: input2.scope,
1627
1641
  provider: "docker-stdio",
1628
- target: input.target.name,
1629
- sourceId: input.sourceId ?? null,
1630
- ...input.hostSpecFingerprint ? { currentFingerprint: input.hostSpecFingerprint } : {},
1631
- desiredFingerprint: input.desiredFingerprint
1642
+ target: input2.target.name,
1643
+ sourceId: input2.sourceId ?? null,
1644
+ ...input2.hostSpecFingerprint ? { currentFingerprint: input2.hostSpecFingerprint } : {},
1645
+ desiredFingerprint: input2.desiredFingerprint
1632
1646
  };
1633
1647
  }
1634
- function managedConnection(input, resolved, fingerprint, existing) {
1648
+ function managedConnection(input2, resolved, fingerprint, existing) {
1635
1649
  return {
1636
- agent: input.agent,
1650
+ agent: input2.agent,
1637
1651
  provider: "docker-stdio",
1638
- profile: input.profile,
1639
- serverName: input.serverName,
1652
+ profile: input2.profile,
1653
+ serverName: input2.serverName,
1640
1654
  docker: {
1641
1655
  ...resolved.target.composeProject ? { composeProject: resolved.target.composeProject } : {},
1642
1656
  ...resolved.target.composeService ? { composeService: resolved.target.composeService } : {},
1643
1657
  containerName: resolved.target.name
1644
1658
  },
1645
- sourceId: input.sourceId ?? null,
1646
- scope: input.scope,
1659
+ sourceId: input2.sourceId ?? null,
1660
+ scope: input2.scope,
1647
1661
  fingerprint,
1648
- createdAt: existing?.createdAt ?? input.now().toISOString()
1662
+ createdAt: existing?.createdAt ?? input2.now().toISOString()
1649
1663
  };
1650
1664
  }
1651
- async function rollbackHost(input) {
1652
- if (input.previousSpec) {
1665
+ async function rollbackHost(input2) {
1666
+ if (input2.previousSpec) {
1653
1667
  try {
1654
- await input.adapter.remove(input.serverName, input.scope);
1668
+ await input2.adapter.remove(input2.serverName, input2.scope);
1655
1669
  } catch {
1656
1670
  }
1657
- await input.adapter.add(input.serverName, input.scope, input.previousSpec);
1658
- const restored = await input.adapter.read(input.serverName, input.scope);
1659
- if (!restored || restored.fingerprint !== (input.previousFingerprint ?? fingerprintConnection(input.previousSpec))) {
1671
+ await input2.adapter.add(input2.serverName, input2.scope, input2.previousSpec);
1672
+ const restored = await input2.adapter.read(input2.serverName, input2.scope);
1673
+ if (!restored || restored.fingerprint !== (input2.previousFingerprint ?? fingerprintConnection(input2.previousSpec))) {
1660
1674
  throw new Error("Previous Agent MCP entry was not restored");
1661
1675
  }
1662
1676
  return;
1663
1677
  }
1664
- await input.adapter.remove(input.serverName, input.scope);
1678
+ await input2.adapter.remove(input2.serverName, input2.scope);
1665
1679
  }
1666
- async function connectAgent(input) {
1667
- validateServerName(input.serverName);
1668
- const resolved = await input.resolveConnection();
1669
- await input.verifier.verify(resolved.spec, { timeoutMs: input.timeoutMs });
1670
- await input.adapter.detect();
1671
- const host = await input.adapter.read(input.serverName, input.scope);
1672
- const key = connectionKey(input.agent, input.scope, input.serverName);
1673
- const managed = await input.state.get(key);
1680
+ async function connectAgent(input2) {
1681
+ validateServerName(input2.serverName);
1682
+ const resolved = await input2.resolveConnection();
1683
+ if (!input2.adapter.capabilities.transports.includes(resolved.spec.transport)) {
1684
+ throw new CliError(
1685
+ "INVALID_ARGUMENT",
1686
+ `${input2.agent} adapter does not support ${resolved.spec.transport} MCP connections`,
1687
+ {
1688
+ exitCode: exitCodes.invalidArgument,
1689
+ hint: "Choose a provider supported by the installed Agent adapter."
1690
+ }
1691
+ );
1692
+ }
1693
+ if (!input2.adapter.capabilities.scopes.includes(input2.scope)) {
1694
+ throw new CliError(
1695
+ "INVALID_ARGUMENT",
1696
+ `${input2.agent} adapter does not support ${input2.scope} scope`,
1697
+ { exitCode: exitCodes.invalidArgument }
1698
+ );
1699
+ }
1700
+ await input2.verifier.verify(resolved.spec, { timeoutMs: input2.timeoutMs });
1701
+ await input2.adapter.detect();
1702
+ const host = await input2.adapter.read(input2.serverName, input2.scope);
1703
+ const key = connectionKey(input2.agent, input2.scope, input2.serverName);
1704
+ const managed = await input2.state.get(key);
1674
1705
  const desiredFingerprint = fingerprintConnection(resolved.spec);
1675
1706
  const plan = determinePlan({
1676
- agent: input.agent,
1677
- serverName: input.serverName,
1678
- scope: input.scope,
1707
+ agent: input2.agent,
1708
+ serverName: input2.serverName,
1709
+ scope: input2.scope,
1679
1710
  target: resolved.target,
1680
- ...input.sourceId ? { sourceId: input.sourceId } : {},
1711
+ ...input2.sourceId ? { sourceId: input2.sourceId } : {},
1681
1712
  desiredFingerprint,
1682
1713
  ...host ? { hostFingerprint: host.fingerprint } : {},
1683
1714
  ...host ? { hostSpecFingerprint: fingerprintConnection(host.spec) } : {},
@@ -1685,9 +1716,9 @@ async function connectAgent(input) {
1685
1716
  });
1686
1717
  if (plan.action === "unchanged") {
1687
1718
  return {
1688
- agent: input.agent,
1689
- serverName: input.serverName,
1690
- scope: input.scope,
1719
+ agent: input2.agent,
1720
+ serverName: input2.serverName,
1721
+ scope: input2.scope,
1691
1722
  action: "unchanged",
1692
1723
  changed: false,
1693
1724
  verified: true,
@@ -1695,11 +1726,11 @@ async function connectAgent(input) {
1695
1726
  plan
1696
1727
  };
1697
1728
  }
1698
- if (input.dryRun) {
1729
+ if (input2.dryRun) {
1699
1730
  return {
1700
- agent: input.agent,
1701
- serverName: input.serverName,
1702
- scope: input.scope,
1731
+ agent: input2.agent,
1732
+ serverName: input2.serverName,
1733
+ scope: input2.scope,
1703
1734
  action: plan.action,
1704
1735
  changed: false,
1705
1736
  verified: true,
@@ -1707,13 +1738,13 @@ async function connectAgent(input) {
1707
1738
  plan
1708
1739
  };
1709
1740
  }
1710
- if (!input.yes && !await input.confirm(
1711
- `${plan.action === "create" ? "Add" : "Update"} ${input.serverName} in ${input.agent}?`
1741
+ if (!input2.yes && !await input2.confirm(
1742
+ `${plan.action === "create" ? "Add" : "Update"} ${input2.serverName} in ${input2.agent}?`
1712
1743
  )) {
1713
1744
  return {
1714
- agent: input.agent,
1715
- serverName: input.serverName,
1716
- scope: input.scope,
1745
+ agent: input2.agent,
1746
+ serverName: input2.serverName,
1747
+ scope: input2.scope,
1717
1748
  action: "cancelled",
1718
1749
  changed: false,
1719
1750
  verified: true,
@@ -1723,23 +1754,23 @@ async function connectAgent(input) {
1723
1754
  }
1724
1755
  try {
1725
1756
  if (plan.action === "update") {
1726
- await input.adapter.remove(input.serverName, input.scope);
1757
+ await input2.adapter.remove(input2.serverName, input2.scope);
1727
1758
  }
1728
- await input.adapter.add(input.serverName, input.scope, resolved.spec);
1729
- const readBack = await input.adapter.read(input.serverName, input.scope);
1759
+ await input2.adapter.add(input2.serverName, input2.scope, resolved.spec);
1760
+ const readBack = await input2.adapter.read(input2.serverName, input2.scope);
1730
1761
  if (!readBack || fingerprintConnection(readBack.spec) !== desiredFingerprint) {
1731
1762
  throw hostFailure3("Agent MCP read-back did not match the requested connection");
1732
1763
  }
1733
- await input.verifier.verify(readBack.spec, { timeoutMs: input.timeoutMs });
1734
- await input.state.set(
1735
- managedConnection(input, resolved, readBack.fingerprint, managed)
1764
+ await input2.verifier.verify(readBack.spec, { timeoutMs: input2.timeoutMs });
1765
+ await input2.state.set(
1766
+ managedConnection(input2, resolved, readBack.fingerprint, managed)
1736
1767
  );
1737
1768
  } catch (cause) {
1738
1769
  try {
1739
1770
  await rollbackHost({
1740
- adapter: input.adapter,
1741
- serverName: input.serverName,
1742
- scope: input.scope,
1771
+ adapter: input2.adapter,
1772
+ serverName: input2.serverName,
1773
+ scope: input2.scope,
1743
1774
  ...host ? { previousSpec: host.spec } : {},
1744
1775
  ...host ? { previousFingerprint: host.fingerprint } : {}
1745
1776
  });
@@ -1747,7 +1778,7 @@ async function connectAgent(input) {
1747
1778
  throw hostFailure3(
1748
1779
  "Agent MCP configuration failed and rollback also failed",
1749
1780
  cause,
1750
- `Inspect the ${input.agent} MCP entry \`${input.serverName}\` before retrying.`
1781
+ `Inspect the ${input2.agent} MCP entry \`${input2.serverName}\` before retrying.`
1751
1782
  );
1752
1783
  }
1753
1784
  throw hostFailure3(
@@ -1756,9 +1787,9 @@ async function connectAgent(input) {
1756
1787
  );
1757
1788
  }
1758
1789
  return {
1759
- agent: input.agent,
1760
- serverName: input.serverName,
1761
- scope: input.scope,
1790
+ agent: input2.agent,
1791
+ serverName: input2.serverName,
1792
+ scope: input2.scope,
1762
1793
  action: plan.action,
1763
1794
  changed: true,
1764
1795
  verified: true,
@@ -1766,15 +1797,15 @@ async function connectAgent(input) {
1766
1797
  plan
1767
1798
  };
1768
1799
  }
1769
- async function disconnectAgent(input) {
1770
- validateServerName(input.serverName);
1771
- await input.adapter.detect();
1772
- const key = connectionKey(input.agent, input.scope, input.serverName);
1773
- const managed = await input.state.get(key);
1800
+ async function disconnectAgent(input2) {
1801
+ validateServerName(input2.serverName);
1802
+ await input2.adapter.detect();
1803
+ const key = connectionKey(input2.agent, input2.scope, input2.serverName);
1804
+ const managed = await input2.state.get(key);
1774
1805
  if (!managed) {
1775
1806
  throw conflict("The Agent MCP entry is not managed by SAG CLI");
1776
1807
  }
1777
- const host = await input.adapter.read(input.serverName, input.scope);
1808
+ const host = await input2.adapter.read(input2.serverName, input2.scope);
1778
1809
  if (!host) {
1779
1810
  throw conflict("Managed state exists but the Agent MCP entry is missing");
1780
1811
  }
@@ -1783,20 +1814,20 @@ async function disconnectAgent(input) {
1783
1814
  }
1784
1815
  const plan = {
1785
1816
  action: "unchanged",
1786
- agent: input.agent,
1787
- serverName: input.serverName,
1788
- scope: input.scope,
1817
+ agent: input2.agent,
1818
+ serverName: input2.serverName,
1819
+ scope: input2.scope,
1789
1820
  provider: "docker-stdio",
1790
1821
  target: managed.docker.containerName,
1791
1822
  sourceId: managed.sourceId,
1792
1823
  currentFingerprint: host.fingerprint,
1793
1824
  desiredFingerprint: host.fingerprint
1794
1825
  };
1795
- if (!input.yes && !await input.confirm(`Remove ${input.serverName} from ${input.agent}?`)) {
1826
+ if (!input2.yes && !await input2.confirm(`Remove ${input2.serverName} from ${input2.agent}?`)) {
1796
1827
  return {
1797
- agent: input.agent,
1798
- serverName: input.serverName,
1799
- scope: input.scope,
1828
+ agent: input2.agent,
1829
+ serverName: input2.serverName,
1830
+ scope: input2.scope,
1800
1831
  action: "cancelled",
1801
1832
  changed: false,
1802
1833
  verified: true,
@@ -1804,12 +1835,12 @@ async function disconnectAgent(input) {
1804
1835
  plan
1805
1836
  };
1806
1837
  }
1807
- await input.adapter.remove(input.serverName, input.scope);
1838
+ await input2.adapter.remove(input2.serverName, input2.scope);
1808
1839
  try {
1809
- await input.state.delete(key);
1840
+ await input2.state.delete(key);
1810
1841
  } catch (cause) {
1811
1842
  try {
1812
- await input.adapter.add(input.serverName, input.scope, host.spec);
1843
+ await input2.adapter.add(input2.serverName, input2.scope, host.spec);
1813
1844
  } catch {
1814
1845
  throw hostFailure3(
1815
1846
  "Managed state cleanup failed and the Agent entry could not be restored",
@@ -1822,9 +1853,9 @@ async function disconnectAgent(input) {
1822
1853
  );
1823
1854
  }
1824
1855
  return {
1825
- agent: input.agent,
1826
- serverName: input.serverName,
1827
- scope: input.scope,
1856
+ agent: input2.agent,
1857
+ serverName: input2.serverName,
1858
+ scope: input2.scope,
1828
1859
  action: "disconnect",
1829
1860
  changed: true,
1830
1861
  verified: true,
@@ -1837,21 +1868,21 @@ async function disconnectAgent(input) {
1837
1868
  var SOURCE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
1838
1869
  var DockerStdioProvider = class {
1839
1870
  id = "docker-stdio";
1840
- async inspect(input) {
1871
+ async inspect(input2) {
1841
1872
  return {
1842
1873
  provider: this.id,
1843
1874
  ready: true,
1844
- target: input.target.name
1875
+ target: input2.target.name
1845
1876
  };
1846
1877
  }
1847
- async createSpec(input) {
1848
- if (input.sourceId && !SOURCE_ID_PATTERN.test(input.sourceId)) {
1878
+ async createSpec(input2) {
1879
+ if (input2.sourceId && !SOURCE_ID_PATTERN.test(input2.sourceId)) {
1849
1880
  throw new CliError("INVALID_ARGUMENT", "Invalid SAG source id", {
1850
1881
  exitCode: exitCodes.invalidArgument,
1851
1882
  hint: "Use 1-128 letters, numbers, underscores, or dashes."
1852
1883
  });
1853
1884
  }
1854
- const sourceArguments = input.sourceId ? ["-e", `SAG_MCP_SOURCE_ID=${input.sourceId}`] : [];
1885
+ const sourceArguments = input2.sourceId ? ["-e", `SAG_MCP_SOURCE_ID=${input2.sourceId}`] : [];
1855
1886
  return {
1856
1887
  transport: "stdio",
1857
1888
  command: "docker",
@@ -1859,7 +1890,7 @@ var DockerStdioProvider = class {
1859
1890
  "exec",
1860
1891
  "-i",
1861
1892
  ...sourceArguments,
1862
- input.target.name,
1893
+ input2.target.name,
1863
1894
  "python",
1864
1895
  "-m",
1865
1896
  "sag_api.mcp.server"
@@ -1909,13 +1940,13 @@ async function validateTarget(docker, container) {
1909
1940
  }
1910
1941
  return toTarget(inspection);
1911
1942
  }
1912
- async function discoverSagContainer(input) {
1913
- await input.docker.version();
1914
- if (input.container) {
1915
- validateContainer(input.container);
1916
- return validateTarget(input.docker, input.container);
1943
+ async function discoverSagContainer(input2) {
1944
+ await input2.docker.version();
1945
+ if (input2.container) {
1946
+ validateContainer(input2.container);
1947
+ return validateTarget(input2.docker, input2.container);
1917
1948
  }
1918
- const containers = await input.docker.listRunningContainers();
1949
+ const containers = await input2.docker.listRunningContainers();
1919
1950
  const apiCandidates = containers.filter(
1920
1951
  (container) => container.labels["com.docker.compose.service"] === "api"
1921
1952
  );
@@ -1930,7 +1961,7 @@ async function discoverSagContainer(input) {
1930
1961
  let preferredFailure;
1931
1962
  for (const candidate of apiCandidates) {
1932
1963
  try {
1933
- targets.push(await validateTarget(input.docker, candidate.id));
1964
+ targets.push(await validateTarget(input2.docker, candidate.id));
1934
1965
  } catch (error) {
1935
1966
  firstFailure ??= error;
1936
1967
  if (candidate.labels["com.docker.compose.project"] === "sag") {
@@ -1946,13 +1977,13 @@ async function discoverSagContainer(input) {
1946
1977
  if (rankedTargets.length === 1) {
1947
1978
  return rankedTargets[0];
1948
1979
  }
1949
- if (!input.interactive || !input.select) {
1980
+ if (!input2.interactive || !input2.select) {
1950
1981
  throw new CliError("CONFIG_CONFLICT", "Multiple SAG API containers were found", {
1951
1982
  exitCode: exitCodes.configConflict,
1952
1983
  hint: "Pass `--container <name-or-id>` to select one."
1953
1984
  });
1954
1985
  }
1955
- const selectedId = await input.select(rankedTargets);
1986
+ const selectedId = await input2.select(rankedTargets);
1956
1987
  const selected = rankedTargets.find(
1957
1988
  (target) => target.id === selectedId || target.name === selectedId
1958
1989
  );
@@ -1965,34 +1996,34 @@ async function discoverSagContainer(input) {
1965
1996
  }
1966
1997
 
1967
1998
  // src/commands/agent.ts
1968
- async function connectLocalAgent(runtime2, input) {
1999
+ async function connectLocalAgent(runtime2, input2) {
1969
2000
  return connectAgent({
1970
- agent: input.agent,
1971
- serverName: input.serverName,
2001
+ agent: input2.agent,
2002
+ serverName: input2.serverName,
1972
2003
  scope: "user",
1973
- profile: input.profile,
1974
- ...input.sourceId ? { sourceId: input.sourceId } : {},
1975
- timeoutMs: input.timeoutMs,
1976
- dryRun: input.dryRun,
1977
- yes: input.yes,
2004
+ profile: input2.profile,
2005
+ ...input2.sourceId ? { sourceId: input2.sourceId } : {},
2006
+ timeoutMs: input2.timeoutMs,
2007
+ dryRun: input2.dryRun,
2008
+ yes: input2.yes,
1978
2009
  resolveConnection: async () => {
1979
2010
  const target = await discoverSagContainer({
1980
2011
  docker: runtime2.docker,
1981
- ...input.container ? { container: input.container } : {},
2012
+ ...input2.container ? { container: input2.container } : {},
1982
2013
  interactive: runtime2.interactive,
1983
2014
  select: runtime2.selectContainer
1984
2015
  });
1985
2016
  const spec = await new DockerStdioProvider().createSpec({
1986
2017
  target,
1987
- ...input.sourceId ? { sourceId: input.sourceId } : {}
2018
+ ...input2.sourceId ? { sourceId: input2.sourceId } : {}
1988
2019
  });
1989
2020
  return { target, spec };
1990
2021
  },
1991
2022
  verifier: runtime2.verifier,
1992
- adapter: runtime2.adapters[input.agent],
2023
+ adapter: runtime2.adapters[input2.agent],
1993
2024
  state: runtime2.state,
1994
- confirm: input.confirm,
1995
- now: input.now
2025
+ confirm: input2.confirm,
2026
+ now: input2.now
1996
2027
  });
1997
2028
  }
1998
2029
  async function agentStatus(runtime2, agent, serverName) {
@@ -2071,10 +2102,10 @@ async function agentStatus(runtime2, agent, serverName) {
2071
2102
  };
2072
2103
  }
2073
2104
  }
2074
- async function statusLocalAgents(runtime2, input) {
2075
- const agents = input.agent ? [input.agent] : ["codex", "claude-code"];
2105
+ async function statusLocalAgents(runtime2, input2) {
2106
+ const agents = input2.agent ? [input2.agent] : ["codex", "claude-code"];
2076
2107
  return Promise.all(
2077
- agents.map((agent) => agentStatus(runtime2, agent, input.serverName))
2108
+ agents.map((agent) => agentStatus(runtime2, agent, input2.serverName))
2078
2109
  );
2079
2110
  }
2080
2111
  async function listAgentHosts(runtime2) {
@@ -2095,58 +2126,69 @@ async function listAgentHosts(runtime2) {
2095
2126
  })
2096
2127
  );
2097
2128
  }
2098
- async function disconnectLocalAgent(runtime2, input) {
2129
+ async function disconnectLocalAgent(runtime2, input2) {
2099
2130
  return disconnectAgent({
2100
- agent: input.agent,
2101
- serverName: input.serverName,
2131
+ agent: input2.agent,
2132
+ serverName: input2.serverName,
2102
2133
  scope: "user",
2103
- adapter: runtime2.adapters[input.agent],
2134
+ adapter: runtime2.adapters[input2.agent],
2104
2135
  state: runtime2.state,
2105
- yes: input.yes,
2106
- confirm: input.confirm
2136
+ yes: input2.yes,
2137
+ confirm: input2.confirm
2107
2138
  });
2108
2139
  }
2109
2140
 
2110
2141
  // src/commands/auth.ts
2111
- async function login(input) {
2112
- const token = input.environmentToken?.trim() || await input.promptToken();
2113
- if (!token.trim()) {
2114
- throw new CliError("AUTH_REQUIRED", "A SAG token is required", {
2115
- exitCode: exitCodes.authRequired,
2116
- hint: "Paste a token when prompted or set SAG_TOKEN."
2142
+ async function login(input2) {
2143
+ const environmentToken = input2.environmentToken?.trim();
2144
+ if (environmentToken) {
2145
+ const user2 = await input2.validate(environmentToken);
2146
+ await input2.store.set(input2.credentialRef, environmentToken);
2147
+ return {
2148
+ authenticated: true,
2149
+ credentialRef: input2.credentialRef,
2150
+ credentialStore: input2.store.kind,
2151
+ user: user2
2152
+ };
2153
+ }
2154
+ const name = (input2.name ?? await input2.promptName()).trim();
2155
+ if (!name) {
2156
+ throw new CliError("INVALID_ARGUMENT", "A SAG user name is required", {
2157
+ exitCode: exitCodes.invalidArgument,
2158
+ hint: "Pass `--name <name>` or enter a name when prompted."
2117
2159
  });
2118
2160
  }
2119
- const user = await input.validate(token);
2120
- await input.store.set(input.credentialRef, token);
2161
+ const { accessToken, user } = await input2.authenticate(name);
2162
+ await input2.store.set(input2.credentialRef, accessToken);
2121
2163
  return {
2122
2164
  authenticated: true,
2123
- credentialRef: input.credentialRef,
2124
- credentialStore: input.store.kind,
2165
+ credentialRef: input2.credentialRef,
2166
+ credentialStore: input2.store.kind,
2125
2167
  user
2126
2168
  };
2127
2169
  }
2128
- async function status(input) {
2129
- const token = input.environmentToken?.trim() || await input.store.get(input.credentialRef);
2170
+ async function status(input2) {
2171
+ const token = input2.environmentToken?.trim() || await input2.store.get(input2.credentialRef);
2130
2172
  if (!token) {
2131
2173
  return {
2132
2174
  authenticated: false,
2133
- credentialRef: input.credentialRef,
2134
- credentialStore: input.store.kind
2175
+ credentialRef: input2.credentialRef,
2176
+ credentialStore: input2.store.kind
2135
2177
  };
2136
2178
  }
2137
- const user = await input.validate(token);
2179
+ const user = await input2.validate(token);
2138
2180
  return {
2139
2181
  authenticated: true,
2140
- credentialRef: input.credentialRef,
2141
- credentialStore: input.store.kind,
2182
+ credentialRef: input2.credentialRef,
2183
+ credentialStore: input2.store.kind,
2142
2184
  user
2143
2185
  };
2144
2186
  }
2145
- async function logout(input) {
2146
- await input.store.delete(input.credentialRef);
2187
+ async function logout(input2) {
2188
+ await input2.store.delete(input2.credentialRef);
2147
2189
  return {
2148
2190
  authenticated: false,
2149
- credentialRef: input.credentialRef
2191
+ credentialRef: input2.credentialRef
2150
2192
  };
2151
2193
  }
2152
2194
 
@@ -2246,24 +2288,24 @@ async function getEntityContext(client, sourceId, name) {
2246
2288
  }
2247
2289
 
2248
2290
  // src/commands/mcp.ts
2249
- async function testLocalMcp(runtime2, input) {
2291
+ async function testLocalMcp(runtime2, input2) {
2250
2292
  const container = await discoverSagContainer({
2251
2293
  docker: runtime2.docker,
2252
- ...input.container ? { container: input.container } : {},
2294
+ ...input2.container ? { container: input2.container } : {},
2253
2295
  interactive: runtime2.interactive,
2254
2296
  select: runtime2.selectContainer
2255
2297
  });
2256
2298
  const spec = await new DockerStdioProvider().createSpec({
2257
2299
  target: container,
2258
- ...input.sourceId ? { sourceId: input.sourceId } : {}
2300
+ ...input2.sourceId ? { sourceId: input2.sourceId } : {}
2259
2301
  });
2260
2302
  const verification = await runtime2.verifier.verify(spec, {
2261
- timeoutMs: input.timeoutMs
2303
+ timeoutMs: input2.timeoutMs
2262
2304
  });
2263
2305
  return {
2264
2306
  provider: "docker-stdio",
2265
2307
  container,
2266
- sourceId: input.sourceId ?? null,
2308
+ sourceId: input2.sourceId ?? null,
2267
2309
  ...verification
2268
2310
  };
2269
2311
  }
@@ -2275,9 +2317,9 @@ function skipped(name, detail) {
2275
2317
  detail
2276
2318
  };
2277
2319
  }
2278
- async function localMcpDoctorChecks(runtime2, input) {
2320
+ async function localMcpDoctorChecks(runtime2, input2) {
2279
2321
  try {
2280
- const report = await testLocalMcp(runtime2, input);
2322
+ const report = await testLocalMcp(runtime2, input2);
2281
2323
  return [
2282
2324
  {
2283
2325
  name: "docker",
@@ -2474,16 +2516,16 @@ function directCredentialRef(url) {
2474
2516
  const fingerprint = createHash2("sha256").update(url).digest("hex").slice(0, 16);
2475
2517
  return `sag-cli/direct-${fingerprint}`;
2476
2518
  }
2477
- async function createRuntimeContext(input) {
2478
- const config = await input.configStore.load();
2479
- const connection = resolveConnection(input.options, input.environment, config);
2519
+ async function createRuntimeContext(input2) {
2520
+ const config = await input2.configStore.load();
2521
+ const connection = resolveConnection(input2.options, input2.environment, config);
2480
2522
  const credentialRef = connection.credentialRef ?? directCredentialRef(connection.url);
2481
- const token = connection.environmentToken ?? await input.credentialStore.get(credentialRef) ?? void 0;
2523
+ const token = connection.environmentToken ?? await input2.credentialStore.get(credentialRef) ?? void 0;
2482
2524
  return {
2483
2525
  connection,
2484
2526
  credentialRef,
2485
2527
  ...token ? { token } : {},
2486
- client: input.createClient({
2528
+ client: input2.createClient({
2487
2529
  origin: connection.url,
2488
2530
  ...token ? { token } : {}
2489
2531
  })
@@ -2587,7 +2629,7 @@ function documentDetail(total, statuses) {
2587
2629
  const parts = displayOrder.filter((status2) => statuses[status2] > 0).map((status2) => `${statuses[status2]} ${status2}`);
2588
2630
  return `${total} ${total === 1 ? "document" : "documents"}${parts.length ? `: ${parts.join(", ")}` : ""}`;
2589
2631
  }
2590
- async function runDoctor(client, input, localMcpChecks) {
2632
+ async function runDoctor(client, input2, localMcpChecks) {
2591
2633
  const checks = [];
2592
2634
  let sagVersion;
2593
2635
  let authenticated = false;
@@ -2702,8 +2744,8 @@ async function runDoctor(client, input, localMcpChecks) {
2702
2744
  }
2703
2745
  return {
2704
2746
  status: overallStatus(checks),
2705
- url: input.url,
2706
- ...input.profileName ? { profileName: input.profileName } : {},
2747
+ url: input2.url,
2748
+ ...input2.profileName ? { profileName: input2.profileName } : {},
2707
2749
  ...sagVersion ? { sagVersion } : {},
2708
2750
  checks,
2709
2751
  summary: {
@@ -3010,12 +3052,14 @@ function addProfileCommands(program, dependencies2) {
3010
3052
  }
3011
3053
  function addAuthCommands(program, dependencies2) {
3012
3054
  const auth = program.command("auth").description("Manage SAG credentials");
3013
- auth.command("login").action(async () => {
3055
+ auth.command("login").option("--name <name>", "SAG user name for the existing local login").action(async (options) => {
3014
3056
  const context = await runtime(program, dependencies2);
3015
3057
  const result = await login({
3016
3058
  credentialRef: context.credentialRef,
3017
3059
  ...context.connection.environmentToken ? { environmentToken: context.connection.environmentToken } : {},
3018
- promptToken: dependencies2.promptToken,
3060
+ ...options.name ? { name: options.name } : {},
3061
+ promptName: dependencies2.promptName,
3062
+ authenticate: (name) => dependencies2.createClient({ origin: context.connection.url }).login(name),
3019
3063
  validate: async (token) => dependencies2.createClient({ origin: context.connection.url, token }).me(),
3020
3064
  store: dependencies2.credentialStore
3021
3065
  });
@@ -3284,10 +3328,7 @@ var dependencies = {
3284
3328
  configStore: new ConfigStore(defaultConfigPath()),
3285
3329
  credentialStore: credentialSelection.store,
3286
3330
  createClient: defaultClientFactory,
3287
- promptToken: () => password({
3288
- message: "SAG Token",
3289
- mask: "*"
3290
- }),
3331
+ promptName: () => input({ message: "SAG user name" }),
3291
3332
  confirm: (message) => confirm({ message, default: false }),
3292
3333
  writeStdout: (value) => process.stdout.write(value),
3293
3334
  writeStderr: (value) => process.stderr.write(value),