@dam-agents/cli 0.2.4-rc1 → 0.2.5-rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.js +1377 -428
  2. package/package.json +2 -2
package/dist/bin.js CHANGED
@@ -10879,6 +10879,15 @@ var TermsStaleAtTransportError = class extends Error {
10879
10879
  host;
10880
10880
  kind = "terms-stale";
10881
10881
  };
10882
+ var ForbiddenAtTransportError = class extends Error {
10883
+ constructor(detail) {
10884
+ super(detail || "access denied");
10885
+ this.detail = detail;
10886
+ this.name = "ForbiddenAtTransportError";
10887
+ }
10888
+ detail;
10889
+ kind = "forbidden";
10890
+ };
10882
10891
  function buildAuthHeaders(deps) {
10883
10892
  return async () => {
10884
10893
  const result = await deps.tokenProvider.getValidAccessToken(deps.host);
@@ -10891,20 +10900,26 @@ function buildAuthHeaders(deps) {
10891
10900
  throw new Error(e2.reason ?? e2.kind);
10892
10901
  };
10893
10902
  }
10894
- function wrapFetchWithTermsGate(host, baseFetch) {
10903
+ function wrapFetchWithServerGates(host, baseFetch) {
10895
10904
  const inner = baseFetch ?? fetch;
10896
10905
  return (async (input, init) => {
10897
10906
  const response = await inner(input, init);
10898
- if (response.status === 412) {
10907
+ if ([401, 403, 412].includes(response.status)) {
10899
10908
  const clone3 = response.clone();
10909
+ let body;
10900
10910
  try {
10901
- const body = await clone3.json();
10902
- if (body.error === "terms_stale") {
10903
- throw new TermsStaleAtTransportError(host);
10904
- }
10905
- } catch (err3) {
10906
- if (err3 instanceof TermsStaleAtTransportError) throw err3;
10907
- }
10911
+ body = await clone3.json();
10912
+ } catch {
10913
+ return response;
10914
+ }
10915
+ if (body.error === "terms_stale")
10916
+ throw new TermsStaleAtTransportError(host);
10917
+ if (body.error === "unauthorized")
10918
+ throw new AuthRequiredAtTransportError(`session expired for ${host}`);
10919
+ if (body.error === "forbidden")
10920
+ throw new ForbiddenAtTransportError(
10921
+ typeof body.message === "string" ? body.message : ""
10922
+ );
10908
10923
  }
10909
10924
  return response;
10910
10925
  });
@@ -10914,7 +10929,7 @@ function createTrpcClient(deps) {
10914
10929
  links: [
10915
10930
  httpBatchLink({
10916
10931
  url: `${deps.host.replace(/\/+$/, "")}/api/trpc`,
10917
- fetch: wrapFetchWithTermsGate(deps.host, deps.fetch),
10932
+ fetch: wrapFetchWithServerGates(deps.host, deps.fetch),
10918
10933
  headers: buildAuthHeaders(deps)
10919
10934
  })
10920
10935
  ]
@@ -10926,7 +10941,7 @@ function createAgentTrpcClient(deps) {
10926
10941
  links: [
10927
10942
  httpBatchLink({
10928
10943
  url: base,
10929
- fetch: wrapFetchWithTermsGate(deps.host, deps.fetch),
10944
+ fetch: wrapFetchWithServerGates(deps.host, deps.fetch),
10930
10945
  headers: buildAuthHeaders(deps)
10931
10946
  })
10932
10947
  ]
@@ -28765,7 +28780,7 @@ var secretTestAnthropicInputSchema = external_exports.object({
28765
28780
  envName: external_exports.enum(["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"])
28766
28781
  });
28767
28782
 
28768
- // ../api-server-api/src/modules/secrets/types.ts
28783
+ // ../api-server-api/src/modules/connections/providers.ts
28769
28784
  var DEFAULT_ENV_PLACEHOLDER = "dummy-placeholder";
28770
28785
  var IBM_LITELLM_HOST = "ete-litellm.ai-models.vpc.res.ibm.com";
28771
28786
  var IBM_LITELLM_BASE_URL = `https://${IBM_LITELLM_HOST}`;
@@ -28809,7 +28824,8 @@ var PROVIDERS = {
28809
28824
  {
28810
28825
  key: "oauth",
28811
28826
  label: "OAuth Token",
28812
- // Claude Code SDK reads CLAUDE_CODE_OAUTH_TOKEN; sends Bearer.
28827
+ templateId: "anthropic-oauth",
28828
+ tokenPrefix: "sk-ant-oat",
28813
28829
  defaultEnvMappings: [
28814
28830
  {
28815
28831
  envName: "CLAUDE_CODE_OAUTH_TOKEN",
@@ -28820,7 +28836,9 @@ var PROVIDERS = {
28820
28836
  {
28821
28837
  key: "api-key",
28822
28838
  label: "API Key",
28823
- // @anthropic-ai/sdk reads ANTHROPIC_API_KEY; sends x-api-key.
28839
+ templateId: "anthropic",
28840
+ tokenPrefix: "sk-ant-api",
28841
+ isDefault: true,
28824
28842
  defaultEnvMappings: [
28825
28843
  {
28826
28844
  envName: "ANTHROPIC_API_KEY",
@@ -28839,6 +28857,7 @@ var PROVIDERS = {
28839
28857
  {
28840
28858
  key: "api-key",
28841
28859
  label: "API Token",
28860
+ templateId: "ibm-litellm",
28842
28861
  defaultEnvMappings: ibmLitellmEnvMappings()
28843
28862
  }
28844
28863
  ]
@@ -28847,14 +28866,13 @@ var PROVIDERS = {
28847
28866
  id: "openai",
28848
28867
  displayName: "OpenAI",
28849
28868
  hostPattern: "api.openai.com",
28850
- // OpenAI's API lives under /v1/*; scoping injection to that path keeps
28851
- // status pages and other host endpoints from getting a spurious
28852
- // Authorization header.
28869
+ // Scope injection to /v1/* so other host endpoints don't get a stray Authorization header.
28853
28870
  pathPattern: "/v1/*",
28854
28871
  modes: [
28855
28872
  {
28856
28873
  key: "api-key",
28857
28874
  label: "API Key",
28875
+ templateId: "openai",
28858
28876
  defaultEnvMappings: [
28859
28877
  { envName: "OPENAI_API_KEY", placeholder: DEFAULT_ENV_PLACEHOLDER }
28860
28878
  ]
@@ -28869,8 +28887,9 @@ var PROVIDERS = {
28869
28887
  {
28870
28888
  key: "api-key",
28871
28889
  label: "API Key",
28890
+ templateId: "bob",
28872
28891
  defaultEnvMappings: bobEnvMappings(),
28873
- // Opaque api-keys go in under `Apikey`; `Bearer` triggers JWT auth.
28892
+ // `Apikey` prefix; `Bearer` would trigger JWT auth.
28874
28893
  injection: {
28875
28894
  headerName: "Authorization",
28876
28895
  valueFormat: "Apikey {value}"
@@ -28885,6 +28904,29 @@ var PROVIDERS = {
28885
28904
  var PROVIDER_PRESET_TYPES = Object.keys(
28886
28905
  PROVIDERS
28887
28906
  );
28907
+ function isProviderPresetType(type) {
28908
+ return type in PROVIDERS;
28909
+ }
28910
+ var TEMPLATE_TO_PROVIDER = new Map(
28911
+ PROVIDER_PRESET_TYPES.flatMap(
28912
+ (type) => PROVIDERS[type].modes.map(
28913
+ (mode) => [mode.templateId, type]
28914
+ )
28915
+ )
28916
+ );
28917
+ var PROVIDER_TEMPLATE_IDS = new Set(
28918
+ TEMPLATE_TO_PROVIDER.keys()
28919
+ );
28920
+ function providerTypeForTemplateId(templateId) {
28921
+ return TEMPLATE_TO_PROVIDER.get(templateId) ?? null;
28922
+ }
28923
+ function templateIdForProvider(type, value) {
28924
+ const modes = PROVIDERS[type].modes;
28925
+ const matched = modes.find(
28926
+ (mode) => mode.tokenPrefix !== void 0 && value.startsWith(mode.tokenPrefix)
28927
+ );
28928
+ return (matched ?? modes.find((mode) => mode.isDefault) ?? modes[0]).templateId;
28929
+ }
28888
28930
 
28889
28931
  // ../agent-runtime-api/src/modules/files/schemas.ts
28890
28932
  var pathSchema = external_exports.string().min(1);
@@ -29192,7 +29234,11 @@ var templateInput = external_exports.object({
29192
29234
  // Marks a config input the form packs into `configInputs` rather than the typed auth fields.
29193
29235
  configInput: external_exports.boolean().optional(),
29194
29236
  label: external_exports.string().optional(),
29195
- hint: external_exports.string().optional()
29237
+ hint: external_exports.string().optional(),
29238
+ // Validation a config input declares, surfaced so clients can validate before submit (the server enforces the same).
29239
+ pattern: external_exports.string().optional(),
29240
+ patternHint: external_exports.string().optional(),
29241
+ enumValues: external_exports.array(external_exports.string()).optional()
29196
29242
  });
29197
29243
  var connectionTemplateView = external_exports.object({
29198
29244
  id: external_exports.string(),
@@ -29219,6 +29265,10 @@ var agentConnections = external_exports.object({
29219
29265
  var connectionIdInputSchema = external_exports.object({
29220
29266
  id: external_exports.string().min(1)
29221
29267
  });
29268
+ var connectionUpdateInputSchema = external_exports.object({
29269
+ id: external_exports.string().min(1),
29270
+ value: external_exports.string().min(1)
29271
+ });
29222
29272
  var connectionStartOAuthInputSchema = external_exports.object({
29223
29273
  connectionId: external_exports.string().min(1),
29224
29274
  returnTo: external_exports.string().regex(
@@ -29680,10 +29730,16 @@ function classifyTrpcError(e2) {
29680
29730
  let cursor = e2;
29681
29731
  for (let depth = 0; cursor && depth < 8; depth++) {
29682
29732
  if (cursor instanceof TermsStaleAtTransportError) throw cursor;
29733
+ if (cursor instanceof ForbiddenAtTransportError) throw cursor;
29683
29734
  if (cursor instanceof AuthRequiredAtTransportError)
29684
29735
  return err2({ kind: "auth-required", reason: cursor.message });
29685
29736
  cursor = cursor.cause;
29686
29737
  }
29738
+ if (e2 instanceof TRPCClientError) {
29739
+ const serverCode = e2.data?.code;
29740
+ if (serverCode)
29741
+ return err2({ kind: "transport", reason: e2.message, serverCode });
29742
+ }
29687
29743
  return err2({
29688
29744
  kind: "transport",
29689
29745
  reason: e2 instanceof Error ? e2.message : typeof e2 === "string" ? e2 : "unknown transport failure"
@@ -29883,68 +29939,46 @@ function sleep2(ms) {
29883
29939
  return new Promise((r) => setTimeout(r, ms));
29884
29940
  }
29885
29941
 
29886
- // src/modules/shared/exit-codes.ts
29887
- var EXIT_SUCCESS = 0;
29888
- var EXIT_RUNTIME_FAILURE = 1;
29889
- var EXIT_INVALID_INPUT = 2;
29890
- var EXIT_BELOW_FLOOR = 3;
29891
- var EXIT_AUTH_STATUS_NO_VALID = 4;
29892
- var EXIT_AGENT_NOT_RESOLVED = 5;
29893
- var EXIT_RULE_NOT_FOUND = 6;
29894
- var EXIT_AGENT_NOT_REACHABLE = 7;
29895
- var EXIT_APPROVAL_NOT_ACTIONABLE = 8;
29896
- var EXIT_SCHEDULE_NOT_FOUND = 9;
29897
- var EXIT_SIGINT = 130;
29942
+ // src/modules/auth/infrastructure/auth-env-reader.ts
29943
+ var DAM_TOKEN_ENV_VAR = "DAM_TOKEN";
29944
+ function createProcessAuthEnvReader(env = process.env) {
29945
+ return {
29946
+ damToken() {
29947
+ const v2 = env[DAM_TOKEN_ENV_VAR];
29948
+ return v2 && v2.length > 0 ? v2 : void 0;
29949
+ }
29950
+ };
29951
+ }
29898
29952
 
29899
- // src/modules/agent/commands/errors.ts
29953
+ // src/modules/shared/auth-message.ts
29954
+ function formatAuthRejection(reason, env = process.env) {
29955
+ const hint = env[DAM_TOKEN_ENV_VAR] ? "DAM_TOKEN was rejected \u2014 check it is valid and unexpired" : "run `dam auth login` first";
29956
+ return `error: not authenticated: ${reason}
29957
+ hint: ${hint}
29958
+ `;
29959
+ }
29960
+
29961
+ // src/modules/shared/trpc/print.ts
29900
29962
  function formatTransportError(reason, host) {
29901
29963
  return `cannot reach server \`${host}\`: ${reason}`;
29902
29964
  }
29903
- function exitCodeForResolveError(error51) {
29904
- return error51.kind === "not-found" || error51.kind === "ambiguous" ? EXIT_AGENT_NOT_RESOLVED : EXIT_RUNTIME_FAILURE;
29905
- }
29906
- function printServiceError(error51, host) {
29965
+ function printServiceError(error51, host, env = process.env) {
29907
29966
  if (error51.kind === "auth-required") {
29908
- process.stderr.write(`error: not authenticated: ${error51.reason}
29967
+ process.stderr.write(formatAuthRejection(error51.reason, env));
29968
+ return;
29969
+ }
29970
+ if (error51.serverCode) {
29971
+ process.stderr.write(`error: ${error51.reason}
29909
29972
  `);
29910
- process.stderr.write("hint: run `dam auth login` first\n");
29911
29973
  return;
29912
29974
  }
29913
29975
  process.stderr.write(`error: ${formatTransportError(error51.reason, host)}
29914
29976
  `);
29915
29977
  }
29916
- function printResolveError(error51, host) {
29917
- switch (error51.kind) {
29918
- case "not-found":
29919
- if (error51.via === "id") {
29920
- process.stderr.write(`error: no agent with id \`${error51.ref}\`
29921
- `);
29922
- } else {
29923
- process.stderr.write(`error: no agent named "${error51.ref}"
29924
- `);
29925
- }
29926
- return;
29927
- case "ambiguous":
29928
- process.stderr.write(`error: multiple agents named "${error51.ref}":
29929
- `);
29930
- for (const m of error51.matches) {
29931
- process.stderr.write(` - \`${m.id}\`
29932
- `);
29933
- }
29934
- process.stderr.write("hint: specify by id instead\n");
29935
- return;
29936
- case "auth-required":
29937
- process.stderr.write(`error: not authenticated: ${error51.reason}
29938
- `);
29939
- process.stderr.write("hint: run `dam auth login` first\n");
29940
- return;
29941
- case "transport":
29942
- process.stderr.write(
29943
- `error: ${formatTransportError(error51.reason, host)}
29944
- `
29945
- );
29946
- return;
29947
- }
29978
+ function printTrpcError(e2, host) {
29979
+ const r = classifyTrpcError(e2);
29980
+ if (r.ok) return;
29981
+ printServiceError(r.error, host);
29948
29982
  }
29949
29983
 
29950
29984
  // src/modules/agent/commands/create-helpers.ts
@@ -29972,6 +30006,19 @@ function validateAgentName(name) {
29972
30006
  return ok2(void 0);
29973
30007
  }
29974
30008
 
30009
+ // src/modules/shared/exit-codes.ts
30010
+ var EXIT_SUCCESS = 0;
30011
+ var EXIT_RUNTIME_FAILURE = 1;
30012
+ var EXIT_INVALID_INPUT = 2;
30013
+ var EXIT_BELOW_FLOOR = 3;
30014
+ var EXIT_AUTH_STATUS_NO_VALID = 4;
30015
+ var EXIT_AGENT_NOT_RESOLVED = 5;
30016
+ var EXIT_RULE_NOT_FOUND = 6;
30017
+ var EXIT_AGENT_NOT_REACHABLE = 7;
30018
+ var EXIT_APPROVAL_NOT_ACTIONABLE = 8;
30019
+ var EXIT_SCHEDULE_NOT_FOUND = 9;
30020
+ var EXIT_SIGINT = 130;
30021
+
29975
30022
  // src/modules/agent/commands/create.ts
29976
30023
  var DEFAULT_TIMEOUT_SECONDS = 120;
29977
30024
  function buildCreateCommand(deps) {
@@ -30099,11 +30146,7 @@ async function runCreate(name, opts, deps) {
30099
30146
  }
30100
30147
  const classified = classifyTrpcError(e2);
30101
30148
  if (!classified.ok && classified.error.kind === "auth-required") {
30102
- process.stderr.write(
30103
- `error: not authenticated: ${classified.error.reason}
30104
- hint: run \`dam auth login\` first
30105
- `
30106
- );
30149
+ printServiceError(classified.error, host);
30107
30150
  process.exit(EXIT_RUNTIME_FAILURE);
30108
30151
  }
30109
30152
  process.stderr.write(`error: failed to create agent: ${errorReason(e2)}
@@ -31149,6 +31192,17 @@ ${c2}
31149
31192
  }
31150
31193
  } }).prompt();
31151
31194
 
31195
+ // src/modules/shared/prompt-secret.ts
31196
+ function promptSecret(message) {
31197
+ return Ce({
31198
+ message,
31199
+ validate(v2) {
31200
+ if (!v2 || v2.trim() === "") return "Required";
31201
+ return void 0;
31202
+ }
31203
+ });
31204
+ }
31205
+
31152
31206
  // src/modules/agent/lib/group-github-pats.ts
31153
31207
  var API_HOST = "api.github.com";
31154
31208
  var GIT_HOST = "github.com";
@@ -31178,8 +31232,119 @@ function groupGithubPats(secrets) {
31178
31232
  return pairs;
31179
31233
  }
31180
31234
 
31235
+ // src/modules/connection/services/connection-service.ts
31236
+ function createConnectionService(deps) {
31237
+ const readIds = async (agentId) => {
31238
+ const res = await deps.trpc.connections.getAgentConnections.query({
31239
+ agentId
31240
+ });
31241
+ return res.connections.map((c2) => c2.connectionId);
31242
+ };
31243
+ return {
31244
+ async list() {
31245
+ return trpcCall(() => deps.trpc.connections.list.query());
31246
+ },
31247
+ async listTemplates() {
31248
+ return trpcCall(() => deps.trpc.connections.listTemplates.query());
31249
+ },
31250
+ async createConnection(input) {
31251
+ return trpcCall(() => deps.trpc.connections.create.mutate(input));
31252
+ },
31253
+ async update(id, value) {
31254
+ return trpcCall(async () => {
31255
+ await deps.trpc.connections.update.mutate({ id, value });
31256
+ });
31257
+ },
31258
+ async startOAuth(connectionId) {
31259
+ return trpcCall(
31260
+ () => deps.trpc.connections.startOAuth.mutate({ connectionId })
31261
+ );
31262
+ },
31263
+ async discoverMcp(url2) {
31264
+ return trpcCall(() => deps.trpc.connections.discoverMcp.mutate({ url: url2 }));
31265
+ },
31266
+ async getConnection(id) {
31267
+ return trpcCall(() => deps.trpc.connections.get.query({ id }));
31268
+ },
31269
+ async agentConnectionIds(agentId) {
31270
+ return trpcCall(() => readIds(agentId));
31271
+ },
31272
+ async grant(agentId, add) {
31273
+ return trpcCall(async () => {
31274
+ const current = await readIds(agentId);
31275
+ const next = Array.from(/* @__PURE__ */ new Set([...current, ...add]));
31276
+ await deps.trpc.connections.setAgentConnections.mutate({
31277
+ agentId,
31278
+ connectionIds: next
31279
+ });
31280
+ return next;
31281
+ });
31282
+ },
31283
+ async revoke(agentId, remove) {
31284
+ return trpcCall(async () => {
31285
+ const drop = new Set(remove);
31286
+ const current = await readIds(agentId);
31287
+ const next = current.filter((id) => !drop.has(id));
31288
+ await deps.trpc.connections.setAgentConnections.mutate({
31289
+ agentId,
31290
+ connectionIds: next
31291
+ });
31292
+ return next;
31293
+ });
31294
+ },
31295
+ async disconnect(id) {
31296
+ return trpcCall(async () => {
31297
+ await deps.trpc.connections.delete.mutate({ id });
31298
+ });
31299
+ }
31300
+ };
31301
+ }
31302
+
31303
+ // src/modules/connection/domain/config-inputs.ts
31304
+ function configInputsOf(template) {
31305
+ return template.inputs.filter((i) => i.configInput);
31306
+ }
31307
+ function validateConfigInputValue(input, value) {
31308
+ const trimmed = value.trim();
31309
+ if (trimmed === "") return void 0;
31310
+ const label = input.label ?? input.name;
31311
+ if (input.enumValues && !input.enumValues.includes(trimmed)) {
31312
+ const opts = input.enumValues.map((v2) => `\`${v2}\``).join(", ");
31313
+ return `${label}: must be one of ${opts}`;
31314
+ }
31315
+ if (input.pattern && !new RegExp(`^(?:${input.pattern})$`).test(trimmed)) {
31316
+ const expected = input.patternHint ?? `value matching ${input.pattern}`;
31317
+ return `${label}: expected ${expected}`;
31318
+ }
31319
+ return void 0;
31320
+ }
31321
+ function resolveConfigInputFlags(template, rawFlags) {
31322
+ const byName2 = new Map(configInputsOf(template).map((i) => [i.name, i]));
31323
+ const out = {};
31324
+ for (const raw of rawFlags) {
31325
+ const eq = raw.indexOf("=");
31326
+ if (eq < 0) return err2({ kind: "missing-equals", input: raw });
31327
+ const key = raw.slice(0, eq);
31328
+ const value = raw.slice(eq + 1).trim();
31329
+ const input = byName2.get(key);
31330
+ if (!input) {
31331
+ return err2({ kind: "unknown-key", key, validKeys: [...byName2.keys()] });
31332
+ }
31333
+ if (value === "") {
31334
+ delete out[key];
31335
+ continue;
31336
+ }
31337
+ const message = validateConfigInputValue(input, value);
31338
+ if (message) return err2({ kind: "invalid-value", key, message });
31339
+ out[key] = value;
31340
+ }
31341
+ return ok2(out);
31342
+ }
31343
+
31181
31344
  // src/modules/agent/commands/create-interactive.ts
31182
31345
  var WAIT_TIMEOUT_SECONDS = 120;
31346
+ var GITHUB_PAT_TEMPLATE_ID = "github-pat";
31347
+ var CONNECTION_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
31183
31348
  var ROLLBACK_CODES = /* @__PURE__ */ new Set([
31184
31349
  "CONFLICT",
31185
31350
  "BAD_REQUEST",
@@ -31200,7 +31365,7 @@ function classifyFailure(e2) {
31200
31365
  }
31201
31366
  async function deleteCreated(trpc, cleanup) {
31202
31367
  let orphanAgent = null;
31203
- const orphanSecrets = [];
31368
+ const orphanConnections = [];
31204
31369
  if (cleanup.agentId) {
31205
31370
  try {
31206
31371
  await trpc.agents.delete.mutate({ id: cleanup.agentId });
@@ -31208,26 +31373,26 @@ async function deleteCreated(trpc, cleanup) {
31208
31373
  orphanAgent = cleanup.agentId;
31209
31374
  }
31210
31375
  }
31211
- for (const id of cleanup.newSecretIds) {
31376
+ for (const id of cleanup.newConnectionIds) {
31212
31377
  try {
31213
- await trpc.secrets.delete.mutate({ id });
31378
+ await trpc.connections.delete.mutate({ id });
31214
31379
  } catch {
31215
- orphanSecrets.push(id);
31380
+ orphanConnections.push(id);
31216
31381
  }
31217
31382
  }
31218
- return { orphanAgent, orphanSecrets };
31383
+ return { orphanAgent, orphanConnections };
31219
31384
  }
31220
- function formatOrphans(orphanAgent, orphanSecrets) {
31221
- if (!orphanAgent && orphanSecrets.length === 0) return null;
31385
+ function formatOrphans(orphanAgent, orphanConnections) {
31386
+ if (!orphanAgent && orphanConnections.length === 0) return null;
31222
31387
  const lines = ["Cleanup partially failed. Manual cleanup needed:"];
31223
31388
  if (orphanAgent) {
31224
31389
  lines.push(
31225
31390
  ` Agent: ${orphanAgent} (delete via web UI or \`dam agent delete\`)`
31226
31391
  );
31227
31392
  }
31228
- if (orphanSecrets.length > 0) {
31393
+ if (orphanConnections.length > 0) {
31229
31394
  lines.push(
31230
- ` Secrets: ${orphanSecrets.join(", ")} (delete via web UI's secrets page)`
31395
+ ` Connections: ${orphanConnections.join(", ")} (delete via \`dam connection disconnect\`)`
31231
31396
  );
31232
31397
  }
31233
31398
  return lines.join("\n");
@@ -31296,7 +31461,7 @@ hint: run \`dam auth login\` first`
31296
31461
  });
31297
31462
  if (q(templateId)) return cancelAndExit();
31298
31463
  const trpc = deps.createTrpcClient(host);
31299
- const cleanup = { newSecretIds: [], agentId: null };
31464
+ const cleanup = { newConnectionIds: [], agentId: null };
31300
31465
  const provider = await pickProvider(trpc, cleanup);
31301
31466
  const githubPat = await pickGithubPat(trpc, cleanup);
31302
31467
  const spin = ft();
@@ -31319,19 +31484,39 @@ hint: run \`dam auth login\` first`
31319
31484
  await handleStage1Failure(trpc, cleanup, e2);
31320
31485
  process.exit(EXIT_RUNTIME_FAILURE);
31321
31486
  }
31322
- spin.message("Granting provider access...");
31323
- const grantedIds = [provider.secretId];
31324
- if (githubPat) grantedIds.push(githubPat.apiSecretId, githubPat.gitSecretId);
31487
+ spin.message("Granting credentials...");
31488
+ const connectionIds = [];
31489
+ const secretIds = [];
31490
+ if (provider.routing.kind === "connection") {
31491
+ connectionIds.push(provider.routing.id);
31492
+ } else {
31493
+ secretIds.push(provider.routing.id);
31494
+ }
31495
+ if (githubPat) {
31496
+ if (githubPat.source === "connection") {
31497
+ connectionIds.push(githubPat.connectionId);
31498
+ } else {
31499
+ secretIds.push(githubPat.apiSecretId, githubPat.gitSecretId);
31500
+ }
31501
+ }
31325
31502
  try {
31326
- await withRetry(
31327
- () => trpc.secrets.setAgentAccess.mutate({
31328
- agentId: cleanup.agentId,
31329
- secretIds: grantedIds
31330
- })
31331
- );
31503
+ await withRetry(async () => {
31504
+ if (connectionIds.length > 0) {
31505
+ await trpc.connections.setAgentConnections.mutate({
31506
+ agentId: cleanup.agentId,
31507
+ connectionIds
31508
+ });
31509
+ }
31510
+ if (secretIds.length > 0) {
31511
+ await trpc.secrets.setAgentAccess.mutate({
31512
+ agentId: cleanup.agentId,
31513
+ secretIds
31514
+ });
31515
+ }
31516
+ });
31332
31517
  } catch (e2) {
31333
31518
  spin.stop("Grant failed");
31334
- R2.error(`Failed to grant provider access: ${errorReason2(e2)}`);
31519
+ R2.error(`Failed to grant credentials: ${errorReason2(e2)}`);
31335
31520
  R2.warn(
31336
31521
  `Agent ${name} was created but the credential grant did not land. Configure access via the web UI, or run \`dam agent delete ${name}\` to start over.`
31337
31522
  );
@@ -31403,9 +31588,9 @@ async function cancelAndCleanup(trpc, cleanup) {
31403
31588
  process.exit(0);
31404
31589
  }
31405
31590
  async function flushCleanup(trpc, cleanup) {
31406
- if (cleanup.agentId === null && cleanup.newSecretIds.length === 0) return;
31407
- const { orphanAgent, orphanSecrets } = await deleteCreated(trpc, cleanup);
31408
- const summary = formatOrphans(orphanAgent, orphanSecrets);
31591
+ if (cleanup.agentId === null && cleanup.newConnectionIds.length === 0) return;
31592
+ const { orphanAgent, orphanConnections } = await deleteCreated(trpc, cleanup);
31593
+ const summary = formatOrphans(orphanAgent, orphanConnections);
31409
31594
  if (summary) R2.warn(summary);
31410
31595
  }
31411
31596
  async function handleStage1Failure(trpc, cleanup, originalError) {
@@ -31418,8 +31603,8 @@ async function handleStage1Failure(trpc, cleanup, originalError) {
31418
31603
  R2.error(`Failed to create agent: ${reason}`);
31419
31604
  const lines = [];
31420
31605
  if (cleanup.agentId) lines.push(` Agent: ${cleanup.agentId}`);
31421
- if (cleanup.newSecretIds.length > 0) {
31422
- lines.push(` Secrets: ${cleanup.newSecretIds.join(", ")}`);
31606
+ if (cleanup.newConnectionIds.length > 0) {
31607
+ lines.push(` Connections: ${cleanup.newConnectionIds.join(", ")}`);
31423
31608
  }
31424
31609
  if (lines.length > 0) {
31425
31610
  R2.warn(
@@ -31430,229 +31615,304 @@ async function handleStage1Failure(trpc, cleanup, originalError) {
31430
31615
  );
31431
31616
  }
31432
31617
  }
31433
- async function pickProvider(trpc, cleanup) {
31434
- let list;
31618
+ async function listCredentials(trpc, cleanup) {
31435
31619
  try {
31436
- list = await trpc.secrets.list.query();
31620
+ return {
31621
+ conns: await trpc.connections.list.query(),
31622
+ secrets: await trpc.secrets.list.query(),
31623
+ templates: await trpc.connections.listTemplates.query()
31624
+ };
31437
31625
  } catch (e2) {
31438
- me(`failed to list secrets: ${errorReason2(e2)}`);
31626
+ me(`failed to list credentials: ${errorReason2(e2)}`);
31439
31627
  await flushCleanup(trpc, cleanup);
31440
31628
  process.exit(EXIT_RUNTIME_FAILURE);
31441
31629
  }
31442
- const existing = list.filter(
31443
- (s) => s.type === "anthropic" || s.type === "ibm-litellm" || s.type === "openai"
31630
+ }
31631
+ async function pickProvider(trpc, cleanup) {
31632
+ const { conns, secrets, templates } = await listCredentials(trpc, cleanup);
31633
+ const existingConns = providerConns(conns);
31634
+ const existingSecrets = secrets.filter(
31635
+ (s) => isProviderPresetType(s.type)
31444
31636
  ).map((s) => ({ id: s.id, name: s.name, type: s.type }));
31445
- if (existing.length === 0) {
31637
+ if (existingConns.length === 0 && existingSecrets.length === 0) {
31446
31638
  R2.info("No model providers configured yet \u2014 let's add one.");
31447
- return addOrReplaceProvider(trpc, cleanup, existing);
31639
+ return addOrReplaceProvider(trpc, cleanup, existingConns, templates);
31448
31640
  }
31449
31641
  const NEW = "__new__";
31450
31642
  const picked = await Ee({
31451
31643
  message: "Model provider",
31452
31644
  options: [
31453
- ...existing.map((s) => ({ value: s.id, label: `${s.name} (${s.type})` })),
31645
+ ...existingConns.map((c2) => ({
31646
+ value: `conn:${c2.id}`,
31647
+ label: `${c2.name} (${c2.type})`
31648
+ })),
31649
+ ...existingSecrets.map((s) => ({
31650
+ value: `secret:${s.id}`,
31651
+ label: `${s.name} (${s.type}) (legacy)`
31652
+ })),
31454
31653
  { value: NEW, label: "Add new..." }
31455
31654
  ]
31456
31655
  });
31457
31656
  if (q(picked)) return cancelAndCleanup(trpc, cleanup);
31458
- if (picked === NEW) return addOrReplaceProvider(trpc, cleanup, existing);
31459
- const found = existing.find((s) => s.id === picked);
31460
- if (!found) {
31461
- me("internal: picked provider not in list");
31462
- await flushCleanup(trpc, cleanup);
31463
- process.exit(EXIT_RUNTIME_FAILURE);
31657
+ if (picked === NEW) {
31658
+ return addOrReplaceProvider(trpc, cleanup, existingConns, templates);
31659
+ }
31660
+ if (picked.startsWith("conn:")) {
31661
+ const found2 = existingConns.find((c2) => `conn:${c2.id}` === picked);
31662
+ return {
31663
+ routing: { kind: "connection", id: found2.id },
31664
+ name: found2.name,
31665
+ type: found2.type
31666
+ };
31464
31667
  }
31668
+ const found = existingSecrets.find((s) => `secret:${s.id}` === picked);
31465
31669
  return {
31466
- secretId: found.id,
31670
+ routing: { kind: "secret", id: found.id },
31467
31671
  name: found.name,
31468
- type: found.type,
31469
- createdNew: false
31672
+ type: found.type
31470
31673
  };
31471
31674
  }
31472
- async function addOrReplaceProvider(trpc, cleanup, existing) {
31675
+ function providerConns(conns) {
31676
+ return conns.filter((c2) => PROVIDER_TEMPLATE_IDS.has(c2.templateId)).map((c2) => {
31677
+ const type = providerTypeForTemplateId(c2.templateId);
31678
+ return type ? { id: c2.id, name: c2.name, templateId: c2.templateId, type } : null;
31679
+ }).filter((c2) => c2 !== null);
31680
+ }
31681
+ async function addOrReplaceProvider(trpc, cleanup, existingConns, templates) {
31473
31682
  while (true) {
31474
31683
  const type = await Ee({
31475
31684
  message: "Provider type",
31476
- options: [
31477
- { value: "anthropic", label: "Anthropic" },
31478
- { value: "ibm-litellm", label: "IBM LiteLLM" },
31479
- { value: "openai", label: "OpenAI" }
31480
- ]
31685
+ options: PROVIDER_PRESET_TYPES.map((t) => ({
31686
+ value: t,
31687
+ label: PROVIDERS[t].displayName
31688
+ }))
31481
31689
  });
31482
31690
  if (q(type)) return cancelAndCleanup(trpc, cleanup);
31483
- const existingOfType = existing.find((s) => s.type === type);
31691
+ const existingOfType = existingConns.find((c2) => c2.type === type);
31484
31692
  if (existingOfType) {
31485
31693
  const replace = await ue({
31486
- message: `A ${PROVIDERS[type].displayName} key already exists. Replace its API key?`,
31694
+ message: `A ${PROVIDERS[type].displayName} connection already exists. Replace its credential?`,
31487
31695
  initialValue: false
31488
31696
  });
31489
31697
  if (q(replace)) return cancelAndCleanup(trpc, cleanup);
31490
31698
  if (!replace) {
31491
31699
  return {
31492
- secretId: existingOfType.id,
31700
+ routing: { kind: "connection", id: existingOfType.id },
31493
31701
  name: existingOfType.name,
31494
- type,
31495
- createdNew: false
31702
+ type
31496
31703
  };
31497
31704
  }
31498
- const apiKey2 = await Ce({
31499
- message: `New ${PROVIDERS[type].displayName} API key`,
31500
- validate(v2) {
31501
- if (!v2 || v2.trim() === "") return "Required";
31502
- return void 0;
31503
- }
31504
- });
31505
- if (q(apiKey2)) return cancelAndCleanup(trpc, cleanup);
31506
- const updateInput = await parseOrExit(
31507
- secretUpdateInputSchema,
31508
- { id: existingOfType.id, value: apiKey2 },
31509
- EXIT_INVALID_INPUT,
31510
- () => flushCleanup(trpc, cleanup)
31705
+ const value2 = await promptSecret(
31706
+ `New ${PROVIDERS[type].displayName} credential`
31511
31707
  );
31708
+ if (q(value2)) return cancelAndCleanup(trpc, cleanup);
31709
+ const templateId2 = templateIdForProvider(type, value2);
31710
+ if (templateId2 !== existingOfType.templateId) {
31711
+ const have = existingOfType.templateId === "anthropic-oauth" ? "an OAuth token" : "an API key";
31712
+ const got = templateId2 === "anthropic-oauth" ? "an OAuth token" : "an API key";
31713
+ R2.error(
31714
+ `This connection expects ${have}, but that looks like ${got}. Paste a matching credential, or disconnect it and add a new one to switch.`
31715
+ );
31716
+ continue;
31717
+ }
31512
31718
  try {
31513
- await trpc.secrets.update.mutate(updateInput);
31719
+ await trpc.connections.update.mutate({
31720
+ id: existingOfType.id,
31721
+ value: value2
31722
+ });
31514
31723
  return {
31515
- secretId: existingOfType.id,
31724
+ routing: { kind: "connection", id: existingOfType.id },
31516
31725
  name: existingOfType.name,
31517
- type,
31518
- createdNew: false
31726
+ type
31519
31727
  };
31520
31728
  } catch (e2) {
31521
- R2.error(`Failed to replace API key: ${errorReason2(e2)}`);
31729
+ R2.error(`Failed to replace credential: ${errorReason2(e2)}`);
31522
31730
  continue;
31523
31731
  }
31524
31732
  }
31525
- const name = PROVIDERS[type].displayName;
31526
- const apiKey = await Ce({
31527
- message: `${PROVIDERS[type].displayName} API key`,
31528
- validate(v2) {
31529
- if (!v2 || v2.trim() === "") return "Required";
31530
- return void 0;
31531
- }
31532
- });
31533
- if (q(apiKey)) return cancelAndCleanup(trpc, cleanup);
31534
- const createInput = await parseOrExit(
31535
- secretCreateInputSchema,
31536
- { type, name, value: apiKey },
31537
- EXIT_INVALID_INPUT,
31538
- () => flushCleanup(trpc, cleanup)
31733
+ const value = await promptSecret(
31734
+ `${PROVIDERS[type].displayName} credential`
31735
+ );
31736
+ if (q(value)) return cancelAndCleanup(trpc, cleanup);
31737
+ const templateId = templateIdForProvider(type, value);
31738
+ const template = templates.find((t) => t.id === templateId);
31739
+ const configInputs = template ? await promptConfigInputs(trpc, cleanup, template) : {};
31740
+ const created = await createProviderConnection(
31741
+ trpc,
31742
+ cleanup,
31743
+ type,
31744
+ templateId,
31745
+ value,
31746
+ configInputs
31539
31747
  );
31748
+ if (created) return created;
31749
+ }
31750
+ }
31751
+ async function promptConfigInputs(trpc, cleanup, template) {
31752
+ const inputs = configInputsOf(template);
31753
+ if (inputs.length === 0) return {};
31754
+ Se("Optional configuration \u2014 press Enter to skip each.");
31755
+ const out = {};
31756
+ for (const input of inputs) {
31757
+ const label = input.label ?? input.name;
31758
+ if (input.enumValues) {
31759
+ const SKIP = "__skip__";
31760
+ const choice = await Ee({
31761
+ message: input.hint ? `${label} \u2014 ${input.hint}` : label,
31762
+ options: [
31763
+ ...input.enumValues.map((v2) => ({ value: v2, label: v2 })),
31764
+ { value: SKIP, label: "Skip" }
31765
+ ],
31766
+ initialValue: SKIP
31767
+ });
31768
+ if (q(choice)) return cancelAndCleanup(trpc, cleanup);
31769
+ if (choice !== SKIP) out[input.name] = choice;
31770
+ continue;
31771
+ }
31772
+ const answer = await Re({
31773
+ message: input.hint ? `${label} \u2014 ${input.hint}` : label,
31774
+ validate: (v2) => validateConfigInputValue(input, v2 ?? "")
31775
+ });
31776
+ if (q(answer)) return cancelAndCleanup(trpc, cleanup);
31777
+ const trimmed = answer.trim();
31778
+ if (trimmed) out[input.name] = trimmed;
31779
+ }
31780
+ return out;
31781
+ }
31782
+ async function createConnectionWithRename(trpc, cleanup, params) {
31783
+ let name = params.name;
31784
+ const hasConfig = params.configInputs && Object.keys(params.configInputs).length > 0;
31785
+ while (true) {
31540
31786
  try {
31541
- const created = await trpc.secrets.create.mutate(createInput);
31542
- cleanup.newSecretIds.push(created.id);
31543
- return {
31544
- secretId: created.id,
31545
- name: created.name,
31546
- type,
31547
- createdNew: true
31548
- };
31787
+ const created = await trpc.connections.create.mutate({
31788
+ templateId: params.templateId,
31789
+ name,
31790
+ authKind: "header",
31791
+ value: params.value,
31792
+ ...hasConfig ? { configInputs: params.configInputs } : {}
31793
+ });
31794
+ cleanup.newConnectionIds.push(created.id);
31795
+ return { id: created.id, name };
31549
31796
  } catch (e2) {
31550
- R2.error(`Failed to create secret: ${errorReason2(e2)}`);
31797
+ if (trpcCode(e2) === "CONFLICT") {
31798
+ const renamed = await Re({
31799
+ message: `A connection named "${name}" already exists. Choose a different name`,
31800
+ validate(v2) {
31801
+ if (!v2 || !CONNECTION_NAME_RE.test(v2)) {
31802
+ return `lowercase letters, digits, and single hyphens (e.g. ${params.nameExample})`;
31803
+ }
31804
+ return void 0;
31805
+ }
31806
+ });
31807
+ if (q(renamed)) return cancelAndCleanup(trpc, cleanup);
31808
+ name = renamed;
31809
+ continue;
31810
+ }
31811
+ R2.error(`Failed to create connection: ${errorReason2(e2)}`);
31812
+ return null;
31551
31813
  }
31552
31814
  }
31553
31815
  }
31816
+ async function createProviderConnection(trpc, cleanup, type, templateId, value, configInputs) {
31817
+ const created = await createConnectionWithRename(trpc, cleanup, {
31818
+ templateId,
31819
+ name: templateId,
31820
+ value,
31821
+ nameExample: "my-anthropic",
31822
+ configInputs
31823
+ });
31824
+ if (!created) return null;
31825
+ return {
31826
+ routing: { kind: "connection", id: created.id },
31827
+ name: created.name,
31828
+ type
31829
+ };
31830
+ }
31831
+ var DEFAULT_GITHUB_PAT_NAME = GITHUB_PAT_TEMPLATE_ID;
31554
31832
  async function pickGithubPat(trpc, cleanup) {
31555
- let list;
31556
- try {
31557
- list = await trpc.secrets.list.query();
31558
- } catch (e2) {
31559
- me(`failed to list secrets: ${errorReason2(e2)}`);
31560
- await flushCleanup(trpc, cleanup);
31561
- process.exit(EXIT_RUNTIME_FAILURE);
31562
- }
31563
- const pairs = groupGithubPats(list);
31564
- if (pairs.length === 0) {
31833
+ const { conns, secrets } = await listCredentials(trpc, cleanup);
31834
+ const patConns = conns.filter((c2) => c2.templateId === GITHUB_PAT_TEMPLATE_ID);
31835
+ const pairs = groupGithubPats(secrets);
31836
+ if (patConns.length === 0 && pairs.length === 0) {
31565
31837
  R2.info("No GitHub PAT configured yet.");
31566
31838
  const add = await ue({ message: "Add one?", initialValue: true });
31567
31839
  if (q(add)) return cancelAndCleanup(trpc, cleanup);
31568
31840
  if (!add) return null;
31569
- return addOrReplaceGithubPat(trpc, cleanup, pairs);
31841
+ return addOrReplaceGithubPat(trpc, cleanup, patConns);
31570
31842
  }
31571
31843
  const NEW = "__new__";
31572
31844
  const SKIP = "__skip__";
31573
31845
  const picked = await Ee({
31574
31846
  message: "GitHub PAT",
31575
31847
  options: [
31576
- ...pairs.map((p2) => ({ value: p2.name, label: p2.name })),
31848
+ ...patConns.map((c2) => ({ value: `conn:${c2.id}`, label: c2.name })),
31849
+ ...pairs.map((p2) => ({
31850
+ value: `secret:${p2.name}`,
31851
+ label: `${p2.name} (legacy)`
31852
+ })),
31577
31853
  { value: NEW, label: "Add new..." },
31578
31854
  { value: SKIP, label: "Skip" }
31579
31855
  ]
31580
31856
  });
31581
31857
  if (q(picked)) return cancelAndCleanup(trpc, cleanup);
31582
31858
  if (picked === SKIP) return null;
31583
- if (picked === NEW) return addOrReplaceGithubPat(trpc, cleanup, pairs);
31584
- const found = pairs.find((p2) => p2.name === picked);
31585
- if (!found) {
31586
- me("internal: picked PAT not in list");
31587
- await flushCleanup(trpc, cleanup);
31588
- process.exit(EXIT_RUNTIME_FAILURE);
31859
+ if (picked === NEW) return addOrReplaceGithubPat(trpc, cleanup, patConns);
31860
+ if (picked.startsWith("conn:")) {
31861
+ const found2 = patConns.find((c2) => `conn:${c2.id}` === picked);
31862
+ return { source: "connection", connectionId: found2.id, name: found2.name };
31589
31863
  }
31590
- return { ...found, createdNew: false };
31864
+ const found = pairs.find((p2) => `secret:${p2.name}` === picked);
31865
+ return {
31866
+ source: "secret",
31867
+ apiSecretId: found.apiSecretId,
31868
+ gitSecretId: found.gitSecretId,
31869
+ name: found.name
31870
+ };
31591
31871
  }
31592
- var DEFAULT_GITHUB_PAT_NAME = "GitHub";
31593
31872
  async function addOrReplaceGithubPat(trpc, cleanup, existing) {
31594
- const collide = existing.find((p2) => p2.name === DEFAULT_GITHUB_PAT_NAME);
31873
+ const collide = existing.find((c2) => c2.name === DEFAULT_GITHUB_PAT_NAME);
31595
31874
  while (true) {
31596
31875
  if (collide) {
31597
31876
  const replace = await ue({
31598
- message: `A GitHub PAT named "${DEFAULT_GITHUB_PAT_NAME}" already exists. Replace its token?`,
31877
+ message: `A GitHub PAT connection named "${DEFAULT_GITHUB_PAT_NAME}" already exists. Replace its token?`,
31599
31878
  initialValue: false
31600
31879
  });
31601
31880
  if (q(replace)) return cancelAndCleanup(trpc, cleanup);
31602
31881
  if (!replace) {
31603
- return { ...collide, createdNew: false };
31882
+ return {
31883
+ source: "connection",
31884
+ connectionId: collide.id,
31885
+ name: collide.name
31886
+ };
31604
31887
  }
31605
- const token2 = await Ce({
31606
- message: "New GitHub personal access token",
31607
- validate(v2) {
31608
- if (!v2 || v2.trim() === "") return "Required";
31609
- return void 0;
31610
- }
31611
- });
31888
+ const token2 = await promptSecret("New GitHub personal access token");
31612
31889
  if (q(token2)) return cancelAndCleanup(trpc, cleanup);
31613
- const updateInput = await parseOrExit(
31614
- secretUpdateGithubPatInputSchema,
31615
- {
31616
- apiSecretId: collide.apiSecretId,
31617
- gitSecretId: collide.gitSecretId,
31618
- token: token2
31619
- },
31620
- EXIT_INVALID_INPUT,
31621
- () => flushCleanup(trpc, cleanup)
31622
- );
31623
31890
  try {
31624
- await trpc.secrets.updateGithubPat.mutate(updateInput);
31625
- return { ...collide, createdNew: false };
31891
+ await trpc.connections.update.mutate({ id: collide.id, value: token2 });
31892
+ return {
31893
+ source: "connection",
31894
+ connectionId: collide.id,
31895
+ name: collide.name
31896
+ };
31626
31897
  } catch (e2) {
31627
31898
  R2.error(`Failed to replace GitHub PAT: ${errorReason2(e2)}`);
31628
31899
  continue;
31629
31900
  }
31630
31901
  }
31631
- const token = await Ce({
31632
- message: "GitHub personal access token",
31633
- validate(v2) {
31634
- if (!v2 || v2.trim() === "") return "Required";
31635
- return void 0;
31636
- }
31637
- });
31902
+ const token = await promptSecret("GitHub personal access token");
31638
31903
  if (q(token)) return cancelAndCleanup(trpc, cleanup);
31639
- const createInput = await parseOrExit(
31640
- secretCreateGithubPatInputSchema,
31641
- { name: DEFAULT_GITHUB_PAT_NAME, token },
31642
- EXIT_INVALID_INPUT,
31643
- () => flushCleanup(trpc, cleanup)
31644
- );
31645
- try {
31646
- const created = await trpc.secrets.createGithubPat.mutate(createInput);
31647
- cleanup.newSecretIds.push(created.apiSecretId, created.gitSecretId);
31904
+ const created = await createConnectionWithRename(trpc, cleanup, {
31905
+ templateId: GITHUB_PAT_TEMPLATE_ID,
31906
+ name: DEFAULT_GITHUB_PAT_NAME,
31907
+ value: token,
31908
+ nameExample: "my-github"
31909
+ });
31910
+ if (created) {
31648
31911
  return {
31649
- name: created.name,
31650
- apiSecretId: created.apiSecretId,
31651
- gitSecretId: created.gitSecretId,
31652
- createdNew: true
31912
+ source: "connection",
31913
+ connectionId: created.id,
31914
+ name: created.name
31653
31915
  };
31654
- } catch (e2) {
31655
- R2.error(`Failed to create GitHub PAT: ${errorReason2(e2)}`);
31656
31916
  }
31657
31917
  }
31658
31918
  }
@@ -31701,6 +31961,37 @@ function createAgentResolver(deps) {
31701
31961
  };
31702
31962
  }
31703
31963
 
31964
+ // src/modules/agent/commands/errors.ts
31965
+ function exitCodeForResolveError(error51) {
31966
+ return error51.kind === "not-found" || error51.kind === "ambiguous" ? EXIT_AGENT_NOT_RESOLVED : EXIT_RUNTIME_FAILURE;
31967
+ }
31968
+ function printResolveError(error51, host) {
31969
+ switch (error51.kind) {
31970
+ case "not-found":
31971
+ if (error51.via === "id") {
31972
+ process.stderr.write(`error: no agent with id \`${error51.ref}\`
31973
+ `);
31974
+ } else {
31975
+ process.stderr.write(`error: no agent named "${error51.ref}"
31976
+ `);
31977
+ }
31978
+ return;
31979
+ case "ambiguous":
31980
+ process.stderr.write(`error: multiple agents named "${error51.ref}":
31981
+ `);
31982
+ for (const m of error51.matches) {
31983
+ process.stderr.write(` - \`${m.id}\`
31984
+ `);
31985
+ }
31986
+ process.stderr.write("hint: specify by id instead\n");
31987
+ return;
31988
+ case "auth-required":
31989
+ case "transport":
31990
+ printServiceError(error51, host);
31991
+ return;
31992
+ }
31993
+ }
31994
+
31704
31995
  // src/modules/shared/prompt.ts
31705
31996
  import { createInterface as createInterface2 } from "readline/promises";
31706
31997
  var DEFAULT_PROMPT_TIMEOUT_MS = 3e4;
@@ -32136,6 +32427,24 @@ function createAgentService(deps) {
32136
32427
  } catch (e2) {
32137
32428
  return notFoundOnMutate(e2, id);
32138
32429
  }
32430
+ },
32431
+ async updateAllowedUserEmails(id, emails) {
32432
+ try {
32433
+ return ok2(
32434
+ await deps.trpc.agents.update.mutate({
32435
+ id,
32436
+ allowedUserEmails: [...emails]
32437
+ })
32438
+ );
32439
+ } catch (e2) {
32440
+ if (e2?.data?.code === "BAD_REQUEST") {
32441
+ return err2({
32442
+ kind: "invalid-input",
32443
+ message: e2 instanceof Error ? e2.message : String(e2)
32444
+ });
32445
+ }
32446
+ return notFoundOnMutate(e2, id);
32447
+ }
32139
32448
  }
32140
32449
  };
32141
32450
  }
@@ -32299,6 +32608,20 @@ function buildDenyCommand(deps) {
32299
32608
  );
32300
32609
  }
32301
32610
 
32611
+ // src/modules/agent/domain/allowed-users.ts
32612
+ function mergeAllowedUserEmails(current, opts) {
32613
+ const removeSet = new Set((opts.remove ?? []).map((e2) => e2.toLowerCase()));
32614
+ const seen = /* @__PURE__ */ new Set();
32615
+ const result = [];
32616
+ for (const email3 of [...current, ...opts.add ?? []]) {
32617
+ const key = email3.toLowerCase();
32618
+ if (removeSet.has(key) || seen.has(key)) continue;
32619
+ seen.add(key);
32620
+ result.push(email3);
32621
+ }
32622
+ return result;
32623
+ }
32624
+
32302
32625
  // src/modules/shared/relative-time.ts
32303
32626
  var MINUTE_MS = 6e4;
32304
32627
  var HOUR_MS = 36e5;
@@ -32819,10 +33142,7 @@ function buildTokenCreateCommand(deps) {
32819
33142
  `);
32820
33143
  }
32821
33144
  } catch (err3) {
32822
- process.stderr.write(
32823
- `error: ${err3 instanceof Error ? err3.message : String(err3)}
32824
- `
32825
- );
33145
+ printTrpcError(err3, resolved.value.server);
32826
33146
  process.exit(EXIT_RUNTIME_FAILURE);
32827
33147
  }
32828
33148
  }
@@ -32863,10 +33183,7 @@ function buildTokenListCommand(deps) {
32863
33183
  );
32864
33184
  }
32865
33185
  } catch (err3) {
32866
- process.stderr.write(
32867
- `error: ${err3 instanceof Error ? err3.message : String(err3)}
32868
- `
32869
- );
33186
+ printTrpcError(err3, resolved.value.server);
32870
33187
  process.exit(EXIT_RUNTIME_FAILURE);
32871
33188
  }
32872
33189
  });
@@ -32890,10 +33207,7 @@ function buildTokenRevokeCommand(deps) {
32890
33207
  process.stdout.write(`\u2713 Revoked ${id}
32891
33208
  `);
32892
33209
  } catch (err3) {
32893
- process.stderr.write(
32894
- `error: ${err3 instanceof Error ? err3.message : String(err3)}
32895
- `
32896
- );
33210
+ printTrpcError(err3, resolved.value.server);
32897
33211
  process.exit(EXIT_RUNTIME_FAILURE);
32898
33212
  }
32899
33213
  });
@@ -32967,17 +33281,6 @@ function createAuthConfigProbe(opts = {}) {
32967
33281
  };
32968
33282
  }
32969
33283
 
32970
- // src/modules/auth/infrastructure/auth-env-reader.ts
32971
- var DAM_TOKEN_ENV_VAR = "DAM_TOKEN";
32972
- function createProcessAuthEnvReader(env = process.env) {
32973
- return {
32974
- damToken() {
32975
- const v2 = env[DAM_TOKEN_ENV_VAR];
32976
- return v2 && v2.length > 0 ? v2 : void 0;
32977
- }
32978
- };
32979
- }
32980
-
32981
33284
  // src/modules/auth/infrastructure/auth-path.ts
32982
33285
  import { homedir } from "os";
32983
33286
  import { join } from "path";
@@ -35339,60 +35642,679 @@ function composeAuthModule(opts) {
35339
35642
  const authService = createAuthService({
35340
35643
  compatService: opts.compatService,
35341
35644
  configService: opts.configService,
35342
- authConfigProbe,
35343
- oidcDiscovery,
35344
- deviceFlowClient,
35345
- tokenEndpointClient,
35346
- revokeClient,
35347
- browserOpener,
35348
- authStore,
35349
- authEnvReader
35350
- });
35351
- const authParent = new Command("auth").description(
35352
- "Authenticate against a Platform deployment (OAuth 2.0 Device Authorization Grant)"
35645
+ authConfigProbe,
35646
+ oidcDiscovery,
35647
+ deviceFlowClient,
35648
+ tokenEndpointClient,
35649
+ revokeClient,
35650
+ browserOpener,
35651
+ authStore,
35652
+ authEnvReader
35653
+ });
35654
+ const authParent = new Command("auth").description(
35655
+ "Authenticate against a Platform deployment (OAuth 2.0 Device Authorization Grant)"
35656
+ );
35657
+ authParent.addCommand(
35658
+ buildLoginCommand({
35659
+ authService,
35660
+ configService: opts.configService,
35661
+ authEnvReader
35662
+ })
35663
+ );
35664
+ authParent.addCommand(
35665
+ buildLogoutCommand({
35666
+ authService,
35667
+ configService: opts.configService
35668
+ })
35669
+ );
35670
+ authParent.addCommand(buildStatusCommand({ authService }));
35671
+ const buildTrpc = (host) => createTrpcClient({ host, tokenProvider });
35672
+ const tokenParent = new Command("token").description(
35673
+ "Manage API keys for headless / CI use"
35674
+ );
35675
+ tokenParent.addCommand(
35676
+ buildTokenCreateCommand({
35677
+ configService: opts.configService,
35678
+ buildTrpc
35679
+ })
35680
+ );
35681
+ tokenParent.addCommand(
35682
+ buildTokenListCommand({
35683
+ configService: opts.configService,
35684
+ buildTrpc
35685
+ })
35686
+ );
35687
+ tokenParent.addCommand(
35688
+ buildTokenRevokeCommand({
35689
+ configService: opts.configService,
35690
+ buildTrpc
35691
+ })
35692
+ );
35693
+ authParent.addCommand(tokenParent);
35694
+ return {
35695
+ commands: [authParent],
35696
+ exports: { tokenProvider },
35697
+ internals: { authStore, authPath, authEnvReader }
35698
+ };
35699
+ }
35700
+
35701
+ // src/modules/channel/commands/allow.ts
35702
+ var collect = (v2, acc) => [...acc, v2];
35703
+ var emailSchema = external_exports.email();
35704
+ function buildAllowCommand(deps) {
35705
+ return new Command("allow").description(
35706
+ "Add one or more users to an Agent's allowed-user list (gates Slack drivers)"
35707
+ ).argument("<agent>", "Agent Ref \u2014 name or 'agent-\u2026' ID").option(
35708
+ "--user <email>",
35709
+ "user email to allow (repeatable)",
35710
+ collect,
35711
+ []
35712
+ ).option(
35713
+ "--server <url>",
35714
+ "override the configured server URL for this call"
35715
+ ).option("--json", "emit { ok, agentId, allowedUserEmails } as JSON").addHelpText(
35716
+ "after",
35717
+ "\nExamples:\n dam channel allow my-agent --user teammate@example.com\n dam channel allow my-agent --user a@x.com --user b@y.com\n"
35718
+ ).action(
35719
+ async (ref, opts) => {
35720
+ const users = opts.user;
35721
+ if (users.length === 0) {
35722
+ process.stderr.write("error: pass at least one --user <email>\n");
35723
+ process.exit(EXIT_INVALID_INPUT);
35724
+ }
35725
+ const invalid = users.filter((u) => !emailSchema.safeParse(u).success);
35726
+ if (invalid.length > 0) {
35727
+ process.stderr.write(
35728
+ `error: invalid email(s): ${invalid.join(", ")}
35729
+ `
35730
+ );
35731
+ process.exit(EXIT_INVALID_INPUT);
35732
+ }
35733
+ const host = await resolveActiveHost(deps, {
35734
+ flag: opts.server ? { server: opts.server } : void 0,
35735
+ exitCodes: {
35736
+ runtimeFailure: EXIT_RUNTIME_FAILURE,
35737
+ belowFloor: EXIT_BELOW_FLOOR
35738
+ }
35739
+ });
35740
+ const svc = deps.createAgentService(host);
35741
+ const resolver = createAgentResolver({ agentService: svc });
35742
+ const resolved = await resolver.resolve(ref);
35743
+ if (!resolved.ok) {
35744
+ printResolveError(resolved.error, host);
35745
+ process.exit(exitCodeForResolveError(resolved.error));
35746
+ }
35747
+ const merged = mergeAllowedUserEmails(
35748
+ resolved.value.allowedUserEmails,
35749
+ {
35750
+ add: users
35751
+ }
35752
+ );
35753
+ const res = await svc.updateAllowedUserEmails(
35754
+ resolved.value.id,
35755
+ merged
35756
+ );
35757
+ if (!res.ok) {
35758
+ if (res.error.kind === "invalid-input") {
35759
+ process.stderr.write(`error: ${res.error.message}
35760
+ `);
35761
+ process.exit(EXIT_INVALID_INPUT);
35762
+ }
35763
+ if (res.error.kind === "not-found") {
35764
+ process.stderr.write(
35765
+ `error: no agent with id \`${resolved.value.id}\`
35766
+ `
35767
+ );
35768
+ process.exit(EXIT_AGENT_NOT_RESOLVED);
35769
+ }
35770
+ printServiceError(res.error, host);
35771
+ process.exit(EXIT_RUNTIME_FAILURE);
35772
+ }
35773
+ if (opts.json) {
35774
+ process.stdout.write(
35775
+ `${JSON.stringify({ ok: true, agentId: resolved.value.id, allowedUserEmails: res.value.allowedUserEmails })}
35776
+ `
35777
+ );
35778
+ } else {
35779
+ process.stdout.write(
35780
+ `\u2713 Allowed ${users.length} user(s) on ${resolved.value.name}. Agent now has ${res.value.allowedUserEmails.length} allowed user(s).
35781
+ `
35782
+ );
35783
+ }
35784
+ process.exit(EXIT_SUCCESS);
35785
+ }
35786
+ );
35787
+ }
35788
+
35789
+ // src/modules/channel/commands/available.ts
35790
+ var PROVIDER_ORDER = [
35791
+ "slack" /* Slack */,
35792
+ "telegram" /* Telegram */
35793
+ ];
35794
+ function buildAvailableCommand(deps) {
35795
+ return new Command("available").description(
35796
+ "List the messenger providers the operator enabled on this host"
35797
+ ).option(
35798
+ "--server <url>",
35799
+ "override the configured server URL for this call"
35800
+ ).option("--json", "emit the raw { slack?, telegram? } object as JSON").addHelpText(
35801
+ "after",
35802
+ "\nExamples:\n dam channel available\n dam channel available --json\n"
35803
+ ).action(async (opts) => {
35804
+ const host = await resolveActiveHost(deps, {
35805
+ flag: opts.server ? { server: opts.server } : void 0,
35806
+ exitCodes: {
35807
+ runtimeFailure: EXIT_RUNTIME_FAILURE,
35808
+ belowFloor: EXIT_BELOW_FLOOR
35809
+ }
35810
+ });
35811
+ const svc = deps.createChannelService(host);
35812
+ const res = await svc.available();
35813
+ if (!res.ok) {
35814
+ printServiceError(res.error, host);
35815
+ process.exit(EXIT_RUNTIME_FAILURE);
35816
+ }
35817
+ if (opts.json) {
35818
+ return writeStdoutAndExit(
35819
+ `${JSON.stringify(res.value)}
35820
+ `,
35821
+ EXIT_SUCCESS
35822
+ );
35823
+ }
35824
+ const enabled = PROVIDER_ORDER.filter((p2) => res.value[p2]);
35825
+ if (enabled.length === 0) {
35826
+ process.stderr.write("No messenger channels enabled on this host.\n");
35827
+ process.exit(EXIT_SUCCESS);
35828
+ }
35829
+ const lines = enabled.map((p2) => `${p2.padEnd(10)}enabled`).join("\n");
35830
+ return writeStdoutAndExit(`${lines}
35831
+ `, EXIT_SUCCESS);
35832
+ });
35833
+ }
35834
+
35835
+ // src/modules/channel/commands/disallow.ts
35836
+ var collect2 = (v2, acc) => [...acc, v2];
35837
+ function buildDisallowCommand(deps) {
35838
+ return new Command("disallow").description(
35839
+ "Remove one or more users from an Agent's allowed-user list. Idempotent."
35840
+ ).argument("<agent>", "Agent Ref \u2014 name or 'agent-\u2026' ID").option(
35841
+ "--user <email>",
35842
+ "user email to remove (repeatable)",
35843
+ collect2,
35844
+ []
35845
+ ).option(
35846
+ "--server <url>",
35847
+ "override the configured server URL for this call"
35848
+ ).option("--json", "emit { ok, agentId, allowedUserEmails } as JSON").addHelpText(
35849
+ "after",
35850
+ "\nExamples:\n dam channel disallow my-agent --user teammate@example.com\n"
35851
+ ).action(
35852
+ async (ref, opts) => {
35853
+ const users = opts.user;
35854
+ if (users.length === 0) {
35855
+ process.stderr.write("error: pass at least one --user <email>\n");
35856
+ process.exit(EXIT_INVALID_INPUT);
35857
+ }
35858
+ const host = await resolveActiveHost(deps, {
35859
+ flag: opts.server ? { server: opts.server } : void 0,
35860
+ exitCodes: {
35861
+ runtimeFailure: EXIT_RUNTIME_FAILURE,
35862
+ belowFloor: EXIT_BELOW_FLOOR
35863
+ }
35864
+ });
35865
+ const svc = deps.createAgentService(host);
35866
+ const resolver = createAgentResolver({ agentService: svc });
35867
+ const resolved = await resolver.resolve(ref);
35868
+ if (!resolved.ok) {
35869
+ printResolveError(resolved.error, host);
35870
+ process.exit(exitCodeForResolveError(resolved.error));
35871
+ }
35872
+ const merged = mergeAllowedUserEmails(
35873
+ resolved.value.allowedUserEmails,
35874
+ {
35875
+ remove: users
35876
+ }
35877
+ );
35878
+ const res = await svc.updateAllowedUserEmails(
35879
+ resolved.value.id,
35880
+ merged
35881
+ );
35882
+ if (!res.ok) {
35883
+ if (res.error.kind === "invalid-input") {
35884
+ process.stderr.write(`error: ${res.error.message}
35885
+ `);
35886
+ process.exit(EXIT_INVALID_INPUT);
35887
+ }
35888
+ if (res.error.kind === "not-found") {
35889
+ process.stderr.write(
35890
+ `error: no agent with id \`${resolved.value.id}\`
35891
+ `
35892
+ );
35893
+ process.exit(EXIT_AGENT_NOT_RESOLVED);
35894
+ }
35895
+ printServiceError(res.error, host);
35896
+ process.exit(EXIT_RUNTIME_FAILURE);
35897
+ }
35898
+ if (opts.json) {
35899
+ process.stdout.write(
35900
+ `${JSON.stringify({ ok: true, agentId: resolved.value.id, allowedUserEmails: res.value.allowedUserEmails })}
35901
+ `
35902
+ );
35903
+ } else {
35904
+ process.stdout.write(
35905
+ `\u2713 Disallowed ${users.length} user(s) from ${resolved.value.name}. Agent now has ${res.value.allowedUserEmails.length} allowed user(s).
35906
+ `
35907
+ );
35908
+ }
35909
+ process.exit(EXIT_SUCCESS);
35910
+ }
35911
+ );
35912
+ }
35913
+
35914
+ // src/modules/channel/commands/list.ts
35915
+ function renderHuman(channels, allowedUserEmails) {
35916
+ const channelsBlock = channels.length === 0 ? "No channels connected.\n" : renderTable([
35917
+ ["TYPE", "IDENTIFIER"],
35918
+ ...channels.map(
35919
+ (c2) => (
35920
+ // Telegram has no readable identifier — the bot token is write-only.
35921
+ c2.type === "slack" /* Slack */ ? ["slack", c2.slackChannelId] : ["telegram", "\u2014"]
35922
+ )
35923
+ )
35924
+ ]);
35925
+ const allowed = allowedUserEmails.length === 0 ? " (unrestricted)\n" : `${allowedUserEmails.map((e2) => ` ${e2}`).join("\n")}
35926
+ `;
35927
+ return `${channelsBlock}
35928
+ Allowed users:
35929
+ ${allowed}`;
35930
+ }
35931
+ function buildListCommand3(deps) {
35932
+ return new Command("list").description("List an Agent's connected channels and its Slack allow-list").argument("<agent>", "Agent Ref \u2014 name or 'agent-\u2026' ID").option(
35933
+ "--server <url>",
35934
+ "override the configured server URL for this call"
35935
+ ).option(
35936
+ "--json",
35937
+ "emit { channels, allowedUserEmails } as JSON instead of the default tables"
35938
+ ).addHelpText(
35939
+ "after",
35940
+ "\nExamples:\n dam channel list my-agent\n dam channel list my-agent --json\n"
35941
+ ).action(async (ref, opts) => {
35942
+ const host = await resolveActiveHost(deps, {
35943
+ flag: opts.server ? { server: opts.server } : void 0,
35944
+ exitCodes: {
35945
+ runtimeFailure: EXIT_RUNTIME_FAILURE,
35946
+ belowFloor: EXIT_BELOW_FLOOR
35947
+ }
35948
+ });
35949
+ const resolver = createAgentResolver({
35950
+ agentService: deps.createAgentService(host)
35951
+ });
35952
+ const resolved = await resolver.resolve(ref);
35953
+ if (!resolved.ok) {
35954
+ printResolveError(resolved.error, host);
35955
+ process.exit(exitCodeForResolveError(resolved.error));
35956
+ }
35957
+ const { channels, allowedUserEmails } = resolved.value;
35958
+ if (opts.json) {
35959
+ return writeStdoutAndExit(
35960
+ `${JSON.stringify({ channels, allowedUserEmails })}
35961
+ `,
35962
+ EXIT_SUCCESS
35963
+ );
35964
+ }
35965
+ return writeStdoutAndExit(
35966
+ renderHuman(channels, allowedUserEmails),
35967
+ EXIT_SUCCESS
35968
+ );
35969
+ });
35970
+ }
35971
+
35972
+ // src/modules/channel/commands/precheck.ts
35973
+ var UNAVAILABLE_MESSAGE = {
35974
+ ["slack" /* Slack */]: "Slack app token not configured",
35975
+ ["telegram" /* Telegram */]: "Telegram channel not enabled"
35976
+ };
35977
+ async function ensureProviderAvailable(svc, provider, host) {
35978
+ const res = await svc.available();
35979
+ if (!res.ok) {
35980
+ printServiceError(res.error, host);
35981
+ process.exit(EXIT_RUNTIME_FAILURE);
35982
+ }
35983
+ if (!res.value[provider]) {
35984
+ process.stderr.write(`error: ${UNAVAILABLE_MESSAGE[provider]}
35985
+ `);
35986
+ process.exit(EXIT_INVALID_INPUT);
35987
+ }
35988
+ }
35989
+
35990
+ // src/modules/channel/commands/slack-connect.ts
35991
+ function buildSlackConnectCommand(deps) {
35992
+ return new Command("connect").description("Bind a Slack channel to an Agent").argument("<agent>", "Agent Ref \u2014 name or 'agent-\u2026' ID").requiredOption("--channel-id <id>", "Slack channel id (e.g. C0123ABCD)").option(
35993
+ "--server <url>",
35994
+ "override the configured server URL for this call"
35995
+ ).option("--json", "emit the updated ChannelConfig[] as JSON").addHelpText(
35996
+ "after",
35997
+ "\nExamples:\n dam channel slack connect my-agent --channel-id C0123ABCD\n"
35998
+ ).action(
35999
+ async (ref, opts) => {
36000
+ const channelId = opts.channelId.trim();
36001
+ if (channelId.length === 0) {
36002
+ process.stderr.write("error: --channel-id must not be empty\n");
36003
+ process.exit(EXIT_INVALID_INPUT);
36004
+ }
36005
+ const host = await resolveActiveHost(deps, {
36006
+ flag: opts.server ? { server: opts.server } : void 0,
36007
+ exitCodes: {
36008
+ runtimeFailure: EXIT_RUNTIME_FAILURE,
36009
+ belowFloor: EXIT_BELOW_FLOOR
36010
+ }
36011
+ });
36012
+ const resolver = createAgentResolver({
36013
+ agentService: deps.createAgentService(host)
36014
+ });
36015
+ const resolved = await resolver.resolve(ref);
36016
+ if (!resolved.ok) {
36017
+ printResolveError(resolved.error, host);
36018
+ process.exit(exitCodeForResolveError(resolved.error));
36019
+ }
36020
+ const svc = deps.createChannelService(host);
36021
+ await ensureProviderAvailable(svc, "slack" /* Slack */, host);
36022
+ const res = await svc.connectSlack(resolved.value.id, channelId);
36023
+ if (!res.ok) {
36024
+ if (res.error.kind === "channel-conflict") {
36025
+ process.stderr.write(
36026
+ "error: Slack channel already bound to another agent\n"
36027
+ );
36028
+ process.exit(EXIT_INVALID_INPUT);
36029
+ }
36030
+ if (res.error.kind === "channel-precondition" || res.error.kind === "invalid-input") {
36031
+ process.stderr.write(`error: ${res.error.message}
36032
+ `);
36033
+ process.exit(EXIT_INVALID_INPUT);
36034
+ }
36035
+ printServiceError(res.error, host);
36036
+ process.exit(EXIT_RUNTIME_FAILURE);
36037
+ }
36038
+ if (opts.json) {
36039
+ process.stdout.write(`${JSON.stringify(res.value)}
36040
+ `);
36041
+ } else {
36042
+ process.stdout.write(
36043
+ `\u2713 Slack channel ${channelId} connected to ${resolved.value.name}.
36044
+ `
36045
+ );
36046
+ process.stderr.write(
36047
+ "hint: users must run `/platform login` inside Slack before they can drive this agent\n"
36048
+ );
36049
+ }
36050
+ process.exit(EXIT_SUCCESS);
36051
+ }
36052
+ );
36053
+ }
36054
+
36055
+ // src/modules/channel/commands/slack-disconnect.ts
36056
+ function buildSlackDisconnectCommand(deps) {
36057
+ return new Command("disconnect").description(
36058
+ "Unbind an Agent's Slack channel. Idempotent \u2014 a not-connected agent exits 0."
36059
+ ).argument("<agent>", "Agent Ref \u2014 name or 'agent-\u2026' ID").option(
36060
+ "--server <url>",
36061
+ "override the configured server URL for this call"
36062
+ ).option("--json", "emit the updated ChannelConfig[] as JSON").addHelpText(
36063
+ "after",
36064
+ "\nExamples:\n dam channel slack disconnect my-agent\n"
36065
+ ).action(async (ref, opts) => {
36066
+ const host = await resolveActiveHost(deps, {
36067
+ flag: opts.server ? { server: opts.server } : void 0,
36068
+ exitCodes: {
36069
+ runtimeFailure: EXIT_RUNTIME_FAILURE,
36070
+ belowFloor: EXIT_BELOW_FLOOR
36071
+ }
36072
+ });
36073
+ const resolver = createAgentResolver({
36074
+ agentService: deps.createAgentService(host)
36075
+ });
36076
+ const resolved = await resolver.resolve(ref);
36077
+ if (!resolved.ok) {
36078
+ printResolveError(resolved.error, host);
36079
+ process.exit(exitCodeForResolveError(resolved.error));
36080
+ }
36081
+ const svc = deps.createChannelService(host);
36082
+ const res = await svc.disconnectSlack(resolved.value.id);
36083
+ if (!res.ok) {
36084
+ printServiceError(res.error, host);
36085
+ process.exit(EXIT_RUNTIME_FAILURE);
36086
+ }
36087
+ if (opts.json) {
36088
+ process.stdout.write(`${JSON.stringify(res.value)}
36089
+ `);
36090
+ } else {
36091
+ process.stdout.write(
36092
+ `\u2713 Slack disconnected from ${resolved.value.name}.
36093
+ `
36094
+ );
36095
+ }
36096
+ process.exit(EXIT_SUCCESS);
36097
+ });
36098
+ }
36099
+
36100
+ // src/modules/channel/commands/telegram-connect.ts
36101
+ function buildTelegramConnectCommand(deps) {
36102
+ return new Command("connect").description("Bind a Telegram bot to an Agent").argument("<agent>", "Agent Ref \u2014 name or 'agent-\u2026' ID").option(
36103
+ "--bot-token <token>",
36104
+ "bot token from @BotFather (prompted, masked, if omitted on a TTY)"
36105
+ ).option(
36106
+ "--server <url>",
36107
+ "override the configured server URL for this call"
36108
+ ).option("--json", "emit the updated ChannelConfig[] as JSON").addHelpText(
36109
+ "after",
36110
+ "\nExamples:\n dam channel telegram connect my-agent\n dam channel telegram connect my-agent --bot-token 123:ABC\n"
36111
+ ).action(
36112
+ async (ref, opts) => {
36113
+ const host = await resolveActiveHost(deps, {
36114
+ flag: opts.server ? { server: opts.server } : void 0,
36115
+ exitCodes: {
36116
+ runtimeFailure: EXIT_RUNTIME_FAILURE,
36117
+ belowFloor: EXIT_BELOW_FLOOR
36118
+ }
36119
+ });
36120
+ const resolver = createAgentResolver({
36121
+ agentService: deps.createAgentService(host)
36122
+ });
36123
+ const resolved = await resolver.resolve(ref);
36124
+ if (!resolved.ok) {
36125
+ printResolveError(resolved.error, host);
36126
+ process.exit(exitCodeForResolveError(resolved.error));
36127
+ }
36128
+ const token = await resolveToken(opts);
36129
+ const svc = deps.createChannelService(host);
36130
+ await ensureProviderAvailable(svc, "telegram" /* Telegram */, host);
36131
+ const res = await svc.connectTelegram(resolved.value.id, token);
36132
+ if (!res.ok) {
36133
+ if (res.error.kind === "channel-precondition" || res.error.kind === "invalid-input") {
36134
+ process.stderr.write(`error: ${res.error.message}
36135
+ `);
36136
+ process.exit(EXIT_INVALID_INPUT);
36137
+ }
36138
+ printServiceError(res.error, host);
36139
+ process.exit(EXIT_RUNTIME_FAILURE);
36140
+ }
36141
+ if (opts.json) {
36142
+ process.stdout.write(`${JSON.stringify(res.value)}
36143
+ `);
36144
+ } else {
36145
+ process.stdout.write(
36146
+ `\u2713 Telegram bot connected to ${resolved.value.name}.
36147
+ `
36148
+ );
36149
+ }
36150
+ process.exit(EXIT_SUCCESS);
36151
+ }
36152
+ );
36153
+ }
36154
+ async function resolveToken(opts) {
36155
+ const flag = opts.botToken?.trim();
36156
+ if (flag) return flag;
36157
+ if (!process.stdin.isTTY) {
36158
+ process.stderr.write("error: --bot-token is required on a non-TTY\n");
36159
+ process.exit(EXIT_INVALID_INPUT);
36160
+ }
36161
+ const answer = await Ce({
36162
+ message: "Telegram bot token (from @BotFather)",
36163
+ validate: (v2) => v2 && v2.trim().length > 0 ? void 0 : "Required"
36164
+ });
36165
+ if (q(answer)) exitCancelled({ json: opts.json });
36166
+ return String(answer).trim();
36167
+ }
36168
+
36169
+ // src/modules/channel/commands/telegram-disconnect.ts
36170
+ function buildTelegramDisconnectCommand(deps) {
36171
+ return new Command("disconnect").description(
36172
+ "Unbind an Agent's Telegram bot (deletes its k8s Secret). Idempotent \u2014 a not-connected agent exits 0."
36173
+ ).argument("<agent>", "Agent Ref \u2014 name or 'agent-\u2026' ID").option(
36174
+ "--server <url>",
36175
+ "override the configured server URL for this call"
36176
+ ).option("--json", "emit the updated ChannelConfig[] as JSON").addHelpText(
36177
+ "after",
36178
+ "\nExamples:\n dam channel telegram disconnect my-agent\n"
36179
+ ).action(async (ref, opts) => {
36180
+ const host = await resolveActiveHost(deps, {
36181
+ flag: opts.server ? { server: opts.server } : void 0,
36182
+ exitCodes: {
36183
+ runtimeFailure: EXIT_RUNTIME_FAILURE,
36184
+ belowFloor: EXIT_BELOW_FLOOR
36185
+ }
36186
+ });
36187
+ const resolver = createAgentResolver({
36188
+ agentService: deps.createAgentService(host)
36189
+ });
36190
+ const resolved = await resolver.resolve(ref);
36191
+ if (!resolved.ok) {
36192
+ printResolveError(resolved.error, host);
36193
+ process.exit(exitCodeForResolveError(resolved.error));
36194
+ }
36195
+ const svc = deps.createChannelService(host);
36196
+ const res = await svc.disconnectTelegram(resolved.value.id);
36197
+ if (!res.ok) {
36198
+ printServiceError(res.error, host);
36199
+ process.exit(EXIT_RUNTIME_FAILURE);
36200
+ }
36201
+ if (opts.json) {
36202
+ process.stdout.write(`${JSON.stringify(res.value)}
36203
+ `);
36204
+ } else {
36205
+ process.stdout.write(
36206
+ `\u2713 Telegram disconnected from ${resolved.value.name}.
36207
+ `
36208
+ );
36209
+ }
36210
+ process.exit(EXIT_SUCCESS);
36211
+ });
36212
+ }
36213
+
36214
+ // src/modules/channel/services/channel-service.ts
36215
+ function createChannelService(deps) {
36216
+ return {
36217
+ async available() {
36218
+ return trpcCall(() => deps.trpc.channels.available.query());
36219
+ },
36220
+ async connectSlack(id, slackChannelId) {
36221
+ try {
36222
+ const agent = await deps.trpc.agents.connectSlack.mutate({
36223
+ id,
36224
+ slackChannelId
36225
+ });
36226
+ return ok2(agent.channels);
36227
+ } catch (e2) {
36228
+ const code = e2?.data?.code;
36229
+ const message = e2 instanceof Error ? e2.message : String(e2);
36230
+ if (code === "CONFLICT")
36231
+ return err2({ kind: "channel-conflict", message });
36232
+ if (code === "PRECONDITION_FAILED")
36233
+ return err2({ kind: "channel-precondition", message });
36234
+ if (code === "BAD_REQUEST")
36235
+ return err2({ kind: "invalid-input", message });
36236
+ return classifyTrpcError(e2);
36237
+ }
36238
+ },
36239
+ async disconnectSlack(id) {
36240
+ return trpcCall(async () => {
36241
+ const agent = await deps.trpc.agents.disconnectSlack.mutate({ id });
36242
+ return agent.channels;
36243
+ });
36244
+ },
36245
+ async connectTelegram(id, botToken) {
36246
+ try {
36247
+ const agent = await deps.trpc.agents.connectTelegram.mutate({
36248
+ id,
36249
+ botToken
36250
+ });
36251
+ return ok2(agent.channels);
36252
+ } catch (e2) {
36253
+ const code = e2?.data?.code;
36254
+ const message = e2 instanceof Error ? e2.message : String(e2);
36255
+ if (code === "PRECONDITION_FAILED")
36256
+ return err2({ kind: "channel-precondition", message });
36257
+ if (code === "BAD_REQUEST")
36258
+ return err2({ kind: "invalid-input", message });
36259
+ return classifyTrpcError(e2);
36260
+ }
36261
+ },
36262
+ async disconnectTelegram(id) {
36263
+ return trpcCall(async () => {
36264
+ const agent = await deps.trpc.agents.disconnectTelegram.mutate({ id });
36265
+ return agent.channels;
36266
+ });
36267
+ }
36268
+ };
36269
+ }
36270
+
36271
+ // src/modules/channel/compose.ts
36272
+ function composeChannelModule(opts) {
36273
+ const buildTrpc = (host) => createTrpcClient({ host, tokenProvider: opts.tokenProvider });
36274
+ const createService = (host) => createChannelService({ trpc: buildTrpc(host) });
36275
+ const agentScoped = {
36276
+ compatService: opts.compatService,
36277
+ configService: opts.configService,
36278
+ createAgentService: opts.createAgentService,
36279
+ createChannelService: createService
36280
+ };
36281
+ const parent = new Command("channel").description(
36282
+ "Manage messenger channel bindings (Slack, Telegram)"
35353
36283
  );
35354
- authParent.addCommand(
35355
- buildLoginCommand({
35356
- authService,
36284
+ parent.addCommand(
36285
+ buildAvailableCommand({
36286
+ compatService: opts.compatService,
35357
36287
  configService: opts.configService,
35358
- authEnvReader
35359
- })
35360
- );
35361
- authParent.addCommand(
35362
- buildLogoutCommand({
35363
- authService,
35364
- configService: opts.configService
36288
+ createChannelService: createService
35365
36289
  })
35366
36290
  );
35367
- authParent.addCommand(buildStatusCommand({ authService }));
35368
- const buildTrpc = (host) => createTrpcClient({ host, tokenProvider });
35369
- const tokenParent = new Command("token").description(
35370
- "Manage API keys for headless / CI use"
35371
- );
35372
- tokenParent.addCommand(
35373
- buildTokenCreateCommand({
36291
+ parent.addCommand(
36292
+ buildListCommand3({
36293
+ compatService: opts.compatService,
35374
36294
  configService: opts.configService,
35375
- buildTrpc
36295
+ createAgentService: opts.createAgentService
35376
36296
  })
35377
36297
  );
35378
- tokenParent.addCommand(
35379
- buildTokenListCommand({
35380
- configService: opts.configService,
35381
- buildTrpc
35382
- })
36298
+ const slack = new Command("slack").description(
36299
+ "Bind or unbind an Agent's Slack channel"
35383
36300
  );
35384
- tokenParent.addCommand(
35385
- buildTokenRevokeCommand({
35386
- configService: opts.configService,
35387
- buildTrpc
35388
- })
36301
+ slack.addCommand(buildSlackConnectCommand(agentScoped));
36302
+ slack.addCommand(buildSlackDisconnectCommand(agentScoped));
36303
+ parent.addCommand(slack);
36304
+ const telegram = new Command("telegram").description(
36305
+ "Bind or unbind an Agent's Telegram bot"
35389
36306
  );
35390
- authParent.addCommand(tokenParent);
35391
- return {
35392
- commands: [authParent],
35393
- exports: { tokenProvider },
35394
- internals: { authStore, authPath, authEnvReader }
36307
+ telegram.addCommand(buildTelegramConnectCommand(agentScoped));
36308
+ telegram.addCommand(buildTelegramDisconnectCommand(agentScoped));
36309
+ parent.addCommand(telegram);
36310
+ const allowScoped = {
36311
+ compatService: opts.compatService,
36312
+ configService: opts.configService,
36313
+ createAgentService: opts.createAgentService
35395
36314
  };
36315
+ parent.addCommand(buildAllowCommand(allowScoped));
36316
+ parent.addCommand(buildDisallowCommand(allowScoped));
36317
+ return { commands: [parent], exports: { createService } };
35396
36318
  }
35397
36319
 
35398
36320
  // src/modules/chat/compose.ts
@@ -37890,7 +38812,7 @@ function createProcessEnvReader() {
37890
38812
 
37891
38813
  // src/modules/cli/infrastructure/package-version.ts
37892
38814
  function readPackageVersion() {
37893
- return true ? "0.2.4-rc1" : DEV_VERSION;
38815
+ return true ? "0.2.5-rc1" : DEV_VERSION;
37894
38816
  }
37895
38817
 
37896
38818
  // src/modules/cli/infrastructure/version-probe.ts
@@ -38121,6 +39043,11 @@ function buildConnectCommand(deps) {
38121
39043
  "--env-name <name>",
38122
39044
  "input: expose the credential to the agent as this env var (custom header credentials)"
38123
39045
  ).option("--value <value>", "input: header secret value").option(
39046
+ "-c, --config <key=value>",
39047
+ "set an optional template config input (e.g. -c model=premium-shell), repeatable",
39048
+ (val, prev) => [...prev, val],
39049
+ []
39050
+ ).option(
38124
39051
  "--server <url>",
38125
39052
  "override the configured server URL for this call"
38126
39053
  ).option(
@@ -38132,7 +39059,7 @@ function buildConnectCommand(deps) {
38132
39059
  String(DEFAULT_TIMEOUT_SECONDS3)
38133
39060
  ).addHelpText(
38134
39061
  "after",
38135
- "\nExamples:\n dam connection connect github\n dam connection connect github --client-id Iv1.\u2026 --client-secret \u2026 # use your own OAuth app\n dam connection connect github --no-browser\n dam connection connect my-api --header-name X-API-Key --value sk-\u2026\n dam connection connect https://mcp.example.com\n dam connection connect https://mcp.example.com --auth none\n"
39062
+ "\nExamples:\n dam connection connect github\n dam connection connect github --client-id Iv1.\u2026 --client-secret \u2026 # use your own OAuth app\n dam connection connect github --no-browser\n dam connection connect my-api --header-name X-API-Key --value sk-\u2026\n dam connection connect bob --value sk-\u2026 --config model=premium-shell --config chatMode=code\n dam connection connect https://mcp.example.com\n dam connection connect https://mcp.example.com --auth none\n"
38136
39063
  ).action(async (providerOrUrl, opts) => {
38137
39064
  const json2 = opts.json ?? false;
38138
39065
  const timeoutSeconds = parseTimeout2(opts.timeout);
@@ -38171,7 +39098,18 @@ function buildConnectCommand(deps) {
38171
39098
  opts,
38172
39099
  json: json2
38173
39100
  });
38174
- const payload = buildPayload(template, name, values);
39101
+ const configRes = resolveConfigInputFlags(template, opts.config ?? []);
39102
+ if (!configRes.ok) {
39103
+ process.stderr.write(
39104
+ `error: ${formatConfigFlagError(configRes.error)}
39105
+ `
39106
+ );
39107
+ process.exit(EXIT_INVALID_INPUT);
39108
+ }
39109
+ const payload = buildPayload(template, name, {
39110
+ ...values,
39111
+ ...configRes.value
39112
+ });
38175
39113
  if ("error" in payload) {
38176
39114
  process.stderr.write(`error: ${payload.error}
38177
39115
  `);
@@ -38471,6 +39409,18 @@ var FIELD_LABELS = {
38471
39409
  function labelFor(key) {
38472
39410
  return FIELD_LABELS[key] ?? key;
38473
39411
  }
39412
+ function formatConfigFlagError(e2) {
39413
+ switch (e2.kind) {
39414
+ case "missing-equals":
39415
+ return `invalid --config value \`${e2.input}\`; expected key=value`;
39416
+ case "unknown-key": {
39417
+ const accepts = e2.validKeys.map((k2) => `\`${k2}\``).join(", ");
39418
+ return accepts ? `unknown --config key \`${e2.key}\`; this template accepts: ${accepts}` : `unknown --config key \`${e2.key}\`; this template has no config inputs`;
39419
+ }
39420
+ case "invalid-value":
39421
+ return e2.message;
39422
+ }
39423
+ }
38474
39424
  function formatPresetNote(inputs) {
38475
39425
  const fields = inputs.map((i) => labelFor(i.name)).join(", ");
38476
39426
  return `Using preset values (${fields}). Pass ${flagListFor(inputs)} to use your own.
@@ -38539,12 +39489,12 @@ function buildDisconnectCommand(deps) {
38539
39489
  }
38540
39490
 
38541
39491
  // src/modules/connection/commands/grant.ts
38542
- var collect = (v2, acc) => [...acc, v2];
39492
+ var collect3 = (v2, acc) => [...acc, v2];
38543
39493
  function buildGrantCommand(deps) {
38544
39494
  return new Command("grant").description("Grant one or more connections to an Agent").argument("<agent>", "Agent Ref \u2014 name or 'agent-\u2026' ID").option(
38545
39495
  "--connection <id-or-name>",
38546
39496
  "connection id or unique name to grant (repeatable)",
38547
- collect,
39497
+ collect3,
38548
39498
  []
38549
39499
  ).option(
38550
39500
  "--server <url>",
@@ -38643,7 +39593,7 @@ function tableFor(views) {
38643
39593
  ])
38644
39594
  ]);
38645
39595
  }
38646
- function buildListCommand3(deps) {
39596
+ function buildListCommand4(deps) {
38647
39597
  return new Command("list").description(
38648
39598
  "List team connections, or the connections granted to one Agent"
38649
39599
  ).argument("[agent]", "Agent Ref \u2014 name or 'agent-\u2026' ID (optional)").option(
@@ -38740,14 +39690,14 @@ function buildListCommand3(deps) {
38740
39690
  }
38741
39691
 
38742
39692
  // src/modules/connection/commands/revoke.ts
38743
- var collect2 = (v2, acc) => [...acc, v2];
39693
+ var collect4 = (v2, acc) => [...acc, v2];
38744
39694
  function buildRevokeCommand(deps) {
38745
39695
  return new Command("revoke").description(
38746
39696
  "Revoke one or more connections from an Agent. Idempotent \u2014 revoking an ungranted id exits 0."
38747
39697
  ).argument("<agent>", "Agent Ref \u2014 name or 'agent-\u2026' ID").option(
38748
39698
  "--connection <id-or-name>",
38749
39699
  "connection id or unique name to revoke (repeatable)",
38750
- collect2,
39700
+ collect4,
38751
39701
  []
38752
39702
  ).option(
38753
39703
  "--server <url>",
@@ -38885,67 +39835,87 @@ function buildTemplatesCommand(deps) {
38885
39835
  });
38886
39836
  }
38887
39837
 
38888
- // src/modules/connection/services/connection-service.ts
38889
- function createConnectionService(deps) {
38890
- const readIds = async (agentId) => {
38891
- const res = await deps.trpc.connections.getAgentConnections.query({
38892
- agentId
38893
- });
38894
- return res.connections.map((c2) => c2.connectionId);
38895
- };
38896
- return {
38897
- async list() {
38898
- return trpcCall(() => deps.trpc.connections.list.query());
38899
- },
38900
- async listTemplates() {
38901
- return trpcCall(() => deps.trpc.connections.listTemplates.query());
38902
- },
38903
- async createConnection(input) {
38904
- return trpcCall(() => deps.trpc.connections.create.mutate(input));
38905
- },
38906
- async startOAuth(connectionId) {
38907
- return trpcCall(
38908
- () => deps.trpc.connections.startOAuth.mutate({ connectionId })
38909
- );
38910
- },
38911
- async discoverMcp(url2) {
38912
- return trpcCall(() => deps.trpc.connections.discoverMcp.mutate({ url: url2 }));
38913
- },
38914
- async getConnection(id) {
38915
- return trpcCall(() => deps.trpc.connections.get.query({ id }));
38916
- },
38917
- async agentConnectionIds(agentId) {
38918
- return trpcCall(() => readIds(agentId));
38919
- },
38920
- async grant(agentId, add) {
38921
- return trpcCall(async () => {
38922
- const current = await readIds(agentId);
38923
- const next = Array.from(/* @__PURE__ */ new Set([...current, ...add]));
38924
- await deps.trpc.connections.setAgentConnections.mutate({
38925
- agentId,
38926
- connectionIds: next
38927
- });
38928
- return next;
38929
- });
38930
- },
38931
- async revoke(agentId, remove) {
38932
- return trpcCall(async () => {
38933
- const drop = new Set(remove);
38934
- const current = await readIds(agentId);
38935
- const next = current.filter((id) => !drop.has(id));
38936
- await deps.trpc.connections.setAgentConnections.mutate({
38937
- agentId,
38938
- connectionIds: next
38939
- });
38940
- return next;
38941
- });
38942
- },
38943
- async disconnect(id) {
38944
- return trpcCall(async () => {
38945
- await deps.trpc.connections.delete.mutate({ id });
39838
+ // src/modules/connection/commands/update.ts
39839
+ function buildUpdateCommand(deps) {
39840
+ return new Command("update").description("Replace a connection's stored credential value").argument(
39841
+ "<id-or-name>",
39842
+ "Connection id ('conn-\u2026') or unique name (from `dam connection list`)"
39843
+ ).option(
39844
+ "--value <value>",
39845
+ "the new credential value (prompts securely if omitted)"
39846
+ ).option(
39847
+ "--server <url>",
39848
+ "override the configured server URL for this call"
39849
+ ).option("--json", "emit { ok, id, name } as JSON").addHelpText(
39850
+ "after",
39851
+ "\nExamples:\n dam connection update conn-61cc7b9137b0 --value sk-ant-...\n dam connection update anthropic # prompts for the value\n"
39852
+ ).action(
39853
+ async (ref, opts) => {
39854
+ const host = await resolveActiveHost(deps, {
39855
+ flag: opts.server ? { server: opts.server } : void 0,
39856
+ exitCodes: {
39857
+ runtimeFailure: EXIT_RUNTIME_FAILURE,
39858
+ belowFloor: EXIT_BELOW_FLOOR
39859
+ }
38946
39860
  });
39861
+ const svc = deps.createConnectionService(host);
39862
+ const listed = await svc.list();
39863
+ if (!listed.ok) {
39864
+ printServiceError(listed.error, host);
39865
+ process.exit(EXIT_RUNTIME_FAILURE);
39866
+ }
39867
+ const match = resolveConnectionRef(listed.value, ref);
39868
+ if (!match) {
39869
+ process.stderr.write(
39870
+ `error: no connection with id or name '${ref}'
39871
+ `
39872
+ );
39873
+ process.stderr.write(
39874
+ "hint: run `dam connection list` to see ids and names\n"
39875
+ );
39876
+ process.exit(EXIT_INVALID_INPUT);
39877
+ }
39878
+ if (match.authKind !== "header") {
39879
+ process.stderr.write(
39880
+ `error: only header-credential connections can be updated; '${match.name}' uses ${match.authKind} auth
39881
+ `
39882
+ );
39883
+ process.exit(EXIT_INVALID_INPUT);
39884
+ }
39885
+ let value = opts.value;
39886
+ if (value === void 0) {
39887
+ if (!process.stdin.isTTY) {
39888
+ process.stderr.write(
39889
+ "error: pass --value <value> when not running interactively\n"
39890
+ );
39891
+ process.exit(EXIT_INVALID_INPUT);
39892
+ }
39893
+ const entered = await promptSecret(
39894
+ `New credential value for ${match.name}`
39895
+ );
39896
+ if (q(entered)) {
39897
+ me("Cancelled");
39898
+ process.exit(EXIT_SUCCESS);
39899
+ }
39900
+ value = entered;
39901
+ }
39902
+ const result = await svc.update(match.id, value);
39903
+ if (!result.ok) {
39904
+ printServiceError(result.error, host);
39905
+ process.exit(EXIT_RUNTIME_FAILURE);
39906
+ }
39907
+ if (opts.json) {
39908
+ process.stdout.write(
39909
+ `${JSON.stringify({ ok: true, id: match.id, name: match.name })}
39910
+ `
39911
+ );
39912
+ } else {
39913
+ process.stdout.write(`\u2713 Updated ${match.name} (${match.id}).
39914
+ `);
39915
+ }
39916
+ process.exit(EXIT_SUCCESS);
38947
39917
  }
38948
- };
39918
+ );
38949
39919
  }
38950
39920
 
38951
39921
  // src/modules/connection/compose.ts
@@ -38961,7 +39931,7 @@ function composeConnectionModule(opts) {
38961
39931
  const parent = new Command("connection").description(
38962
39932
  "Manage OAuth connections and agent connection grants"
38963
39933
  );
38964
- parent.addCommand(buildListCommand3(agentScoped));
39934
+ parent.addCommand(buildListCommand4(agentScoped));
38965
39935
  parent.addCommand(
38966
39936
  buildTemplatesCommand({
38967
39937
  compatService: opts.compatService,
@@ -38984,6 +39954,13 @@ function composeConnectionModule(opts) {
38984
39954
  createConnectionService: createService
38985
39955
  })
38986
39956
  );
39957
+ parent.addCommand(
39958
+ buildUpdateCommand({
39959
+ compatService: opts.compatService,
39960
+ configService: opts.configService,
39961
+ createConnectionService: createService
39962
+ })
39963
+ );
38987
39964
  parent.addCommand(buildGrantCommand(agentScoped));
38988
39965
  parent.addCommand(buildRevokeCommand(agentScoped));
38989
39966
  return { commands: [parent], exports: { createService } };
@@ -39125,7 +40102,7 @@ function buildCreateCommand2(deps) {
39125
40102
  }
39126
40103
 
39127
40104
  // src/modules/egress/commands/list.ts
39128
- function buildListCommand4(deps) {
40105
+ function buildListCommand5(deps) {
39129
40106
  return new Command("list").description("List network access rules for an Agent").argument("<agent>", "Agent Ref \u2014 name or 'agent-\u2026' ID").option(
39130
40107
  "--server <url>",
39131
40108
  "override the configured server URL for this call"
@@ -39294,7 +40271,7 @@ function buildTrustedHostsCommand(deps) {
39294
40271
  }
39295
40272
 
39296
40273
  // src/modules/egress/commands/update.ts
39297
- function buildUpdateCommand(deps) {
40274
+ function buildUpdateCommand2(deps) {
39298
40275
  return new Command("update").description(
39299
40276
  "Update a network access rule; flips source to 'manual'. Partial \u2014 pass only the fields you want to change."
39300
40277
  ).argument("<rule-id>", "Rule UUID (copy from `dam network list`)").option("--method <m>", "new HTTP method").option("--path <p>", "new path pattern").addOption(
@@ -39425,9 +40402,9 @@ function composeEgressModule(opts) {
39425
40402
  const parent = new Command("network").description(
39426
40403
  "Manage per-Agent network access rules (the pre-approvals that let an Agent reach external hosts)"
39427
40404
  );
39428
- parent.addCommand(buildListCommand4(agentScoped));
40405
+ parent.addCommand(buildListCommand5(agentScoped));
39429
40406
  parent.addCommand(buildCreateCommand2(agentScoped));
39430
- parent.addCommand(buildUpdateCommand(idScoped));
40407
+ parent.addCommand(buildUpdateCommand2(idScoped));
39431
40408
  parent.addCommand(buildRevokeCommand2(idScoped));
39432
40409
  parent.addCommand(buildPresetCommand(agentScoped));
39433
40410
  parent.addCommand(buildApplyPresetCommand(agentScoped));
@@ -39536,15 +40513,6 @@ async function fileExists(path2) {
39536
40513
  }
39537
40514
  }
39538
40515
  function printTrpcReadError(e2, path2, host) {
39539
- const classified = classifyTrpcError(e2);
39540
- if (!classified.ok && classified.error.kind === "auth-required") {
39541
- process.stderr.write(
39542
- `error: not authenticated: ${classified.error.reason}
39543
- hint: run \`dam auth login\` first
39544
- `
39545
- );
39546
- return;
39547
- }
39548
40516
  const code = e2 instanceof TRPCClientError ? e2.data?.code : void 0;
39549
40517
  if (code === "NOT_FOUND") {
39550
40518
  process.stderr.write(`error: not found: ${path2}
@@ -39558,9 +40526,7 @@ hint: run \`dam auth login\` first
39558
40526
  );
39559
40527
  return;
39560
40528
  }
39561
- const msg = e2 instanceof Error ? e2.message : String(e2);
39562
- process.stderr.write(`error: cannot reach server \`${host}\`: ${msg}
39563
- `);
40529
+ printTrpcError(e2, host);
39564
40530
  }
39565
40531
 
39566
40532
  // src/modules/file/commands/list.ts
@@ -39623,20 +40589,6 @@ function buildFileListCommand(deps) {
39623
40589
  }
39624
40590
  );
39625
40591
  }
39626
- function printTrpcError(e2, host) {
39627
- const classified = classifyTrpcError(e2);
39628
- if (!classified.ok && classified.error.kind === "auth-required") {
39629
- process.stderr.write(
39630
- `error: not authenticated: ${classified.error.reason}
39631
- hint: run \`dam auth login\` first
39632
- `
39633
- );
39634
- return;
39635
- }
39636
- const msg = e2 instanceof Error ? e2.message : String(e2);
39637
- process.stderr.write(`error: cannot reach server \`${host}\`: ${msg}
39638
- `);
39639
- }
39640
40592
 
39641
40593
  // src/modules/file/commands/put.ts
39642
40594
  import { open as open2 } from "fs/promises";
@@ -39721,15 +40673,6 @@ function buildFilePutCommand(deps) {
39721
40673
  );
39722
40674
  }
39723
40675
  function printTrpcUploadError(e2, remotePath, host) {
39724
- const classified = classifyTrpcError(e2);
39725
- if (!classified.ok && classified.error.kind === "auth-required") {
39726
- process.stderr.write(
39727
- `error: not authenticated: ${classified.error.reason}
39728
- hint: run \`dam auth login\` first
39729
- `
39730
- );
39731
- return;
39732
- }
39733
40676
  const trpcErr = e2 instanceof TRPCClientError ? e2 : void 0;
39734
40677
  const code = trpcErr?.data?.code;
39735
40678
  const message = trpcErr?.data?.message ?? "";
@@ -39752,9 +40695,7 @@ hint: run \`dam auth login\` first
39752
40695
  );
39753
40696
  return;
39754
40697
  }
39755
- const msg = e2 instanceof Error ? e2.message : String(e2);
39756
- process.stderr.write(`error: cannot reach server \`${host}\`: ${msg}
39757
- `);
40698
+ printTrpcError(e2, host);
39758
40699
  }
39759
40700
 
39760
40701
  // src/modules/file/compose.ts
@@ -40016,9 +40957,7 @@ async function uploadAndReport(args) {
40016
40957
  const e2 = tokenResult.error;
40017
40958
  if (e2.kind === "not-logged-in" || e2.kind === "session-expired") {
40018
40959
  const reason = e2.kind === "not-logged-in" ? `not logged in to ${e2.host}` : `session expired for ${e2.host}`;
40019
- process.stderr.write(`error: not authenticated: ${reason}
40020
- `);
40021
- process.stderr.write("hint: run `dam auth login` first\n");
40960
+ process.stderr.write(formatAuthRejection(reason));
40022
40961
  } else {
40023
40962
  process.stderr.write(`error: ${e2.reason}
40024
40963
  `);
@@ -40071,11 +41010,7 @@ async function uploadAndReport(args) {
40071
41010
  const serverMessage = extractServerError(body) ?? res.statusText;
40072
41011
  switch (res.status) {
40073
41012
  case 401:
40074
- process.stderr.write(
40075
- `error: not authenticated: ${serverMessage}
40076
- run "dam auth login" first
40077
- `
40078
- );
41013
+ process.stderr.write(formatAuthRejection(serverMessage));
40079
41014
  return EXIT_RUNTIME_FAILURE;
40080
41015
  case 404:
40081
41016
  process.stderr.write("error: agent no longer exists\n");
@@ -40096,7 +41031,7 @@ async function uploadAndReport(args) {
40096
41031
  return EXIT_RUNTIME_FAILURE;
40097
41032
  default:
40098
41033
  process.stderr.write(
40099
- `error: cannot reach server: HTTP ${res.status} ${serverMessage}
41034
+ `error: ${serverMessage || `the server rejected the request (HTTP ${res.status})`}
40100
41035
  `
40101
41036
  );
40102
41037
  return EXIT_RUNTIME_FAILURE;
@@ -40524,7 +41459,7 @@ function buildGetCommand2(deps) {
40524
41459
  function recurrenceText(view) {
40525
41460
  return view.rrule !== null ? rruleToText(view.rrule) : view.cron ?? "";
40526
41461
  }
40527
- function buildListCommand5(deps) {
41462
+ function buildListCommand6(deps) {
40528
41463
  return new Command("list").description("List the schedules attached to an Agent").argument("<agent>", "Agent Ref \u2014 name or 'agent-\u2026' ID").option(
40529
41464
  "--server <url>",
40530
41465
  "override the configured server URL for this call"
@@ -40655,7 +41590,7 @@ function buildResetSessionCommand(deps) {
40655
41590
  }
40656
41591
 
40657
41592
  // src/modules/schedule/commands/update.ts
40658
- function buildUpdateCommand2(deps) {
41593
+ function buildUpdateCommand3(deps) {
40659
41594
  return new Command("update").description(
40660
41595
  "Update an RRULE schedule (read-merge-write). Partial \u2014 pass only the fields to change. Legacy cron schedules are read-only."
40661
41596
  ).argument("<schedule-id>", "Schedule id (from `dam schedule list`)").option("--name <name>", "new schedule name").option("--task <task>", "new task prompt").option("--daily <HH:MM>", "rebuild recurrence: daily at HH:MM").option("--every <interval>", "rebuild recurrence: every Nm/Nh").option("--rrule <body>", "rebuild recurrence: raw RFC 5545 RRULE body").option(
@@ -40885,10 +41820,10 @@ function composeScheduleModule(opts) {
40885
41820
  const parent = new Command("schedule").description(
40886
41821
  "Manage time-triggered tasks (schedules) attached to an Agent"
40887
41822
  );
40888
- parent.addCommand(buildListCommand5(agentScoped));
41823
+ parent.addCommand(buildListCommand6(agentScoped));
40889
41824
  parent.addCommand(buildGetCommand2(idScoped));
40890
41825
  parent.addCommand(buildCreateCommand3(agentScoped));
40891
- parent.addCommand(buildUpdateCommand2(idScoped));
41826
+ parent.addCommand(buildUpdateCommand3(idScoped));
40892
41827
  parent.addCommand(buildEnableCommand(idScoped));
40893
41828
  parent.addCommand(buildDisableCommand(idScoped));
40894
41829
  parent.addCommand(buildDeleteCommand2(idScoped));
@@ -41167,7 +42102,7 @@ function buildInstallCommand(deps) {
41167
42102
  // src/modules/skill/commands/list.ts
41168
42103
  var INSTALLED_HEADER = ["SOURCE", "NAME", "VERSION"];
41169
42104
  var STANDALONE_HEADER = ["NAME", "PUBLISHED"];
41170
- function buildListCommand6(deps) {
42105
+ function buildListCommand7(deps) {
41171
42106
  return new Command("list").description("List the skills on an Agent \u2014 installed and standalone").argument("<agent>", "Agent Ref \u2014 name or 'agent-\u2026' ID").option(
41172
42107
  "--server <url>",
41173
42108
  "override the configured server URL for this call"
@@ -41925,7 +42860,7 @@ function composeSkillModule(opts) {
41925
42860
  sourceGroup.addCommand(buildSourceRefreshCommand(shared));
41926
42861
  parent.addCommand(sourceGroup);
41927
42862
  parent.addCommand(buildCatalogCommand(shared));
41928
- parent.addCommand(buildListCommand6(shared));
42863
+ parent.addCommand(buildListCommand7(shared));
41929
42864
  parent.addCommand(buildInstallCommand(shared));
41930
42865
  parent.addCommand(buildUninstallCommand(shared));
41931
42866
  parent.addCommand(buildPublishCommand(shared));
@@ -42490,7 +43425,7 @@ function composeSshModule(opts) {
42490
43425
 
42491
43426
  // src/modules/template/commands/list.ts
42492
43427
  var DESCRIPTION_MAX = 60;
42493
- function buildListCommand7(deps) {
43428
+ function buildListCommand8(deps) {
42494
43429
  return new Command("list").description("List agent templates available on the active host").option(
42495
43430
  "--server <url>",
42496
43431
  "override the configured server URL for this call"
@@ -42575,7 +43510,7 @@ function composeTemplateModule(opts) {
42575
43510
  "Discover agent templates on the active host"
42576
43511
  );
42577
43512
  parent.addCommand(
42578
- buildListCommand7({
43513
+ buildListCommand8({
42579
43514
  compatService: opts.compatService,
42580
43515
  configService: opts.configService,
42581
43516
  createTemplateService: createService
@@ -42668,6 +43603,12 @@ function compose(opts = {}) {
42668
43603
  createAgentService: agent.exports.createService,
42669
43604
  createEgressService: egress.exports.createService
42670
43605
  });
43606
+ const channel = composeChannelModule({
43607
+ tokenProvider: auth.exports.tokenProvider,
43608
+ configService: cli.services.configService,
43609
+ compatService: cli.services.compatService,
43610
+ createAgentService: agent.exports.createService
43611
+ });
42671
43612
  const program3 = new Command();
42672
43613
  program3.name("dam").description("Command-line client for a Platform deployment").version(cli.cliVersion);
42673
43614
  for (const command of cli.commands) program3.addCommand(command);
@@ -42683,6 +43624,7 @@ function compose(opts = {}) {
42683
43624
  for (const command of schedule.commands) program3.addCommand(command);
42684
43625
  for (const command of skill.commands) program3.addCommand(command);
42685
43626
  for (const command of ssh.commands) program3.addCommand(command);
43627
+ for (const command of channel.commands) program3.addCommand(command);
42686
43628
  return program3;
42687
43629
  }
42688
43630
 
@@ -42695,6 +43637,13 @@ try {
42695
43637
  process.stderr.write(
42696
43638
  `error: Terms of Use acceptance required
42697
43639
  hint: open ${err3.host} to accept
43640
+ `
43641
+ );
43642
+ process.exit(1);
43643
+ }
43644
+ if (err3 instanceof ForbiddenAtTransportError) {
43645
+ process.stderr.write(
43646
+ `error: access denied${err3.detail ? `: ${err3.detail}` : ""}
42698
43647
  `
42699
43648
  );
42700
43649
  process.exit(1);