@topolo/cli 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +421 -93
  2. package/package.json +11 -6
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { platform as platform2 } from "os";
9
9
  import { createInterface } from "readline/promises";
10
10
  import { stdin as input } from "process";
11
11
 
12
- // node_modules/@topolo/sdk/dist/index.js
12
+ // node_modules/.pnpm/@topolo+sdk@0.17.1/node_modules/@topolo/sdk/dist/index.js
13
13
  function applyAuthHeaders(headers, credential) {
14
14
  if (credential.kind === "api_key") {
15
15
  headers.set("X-Api-Key", credential.apiKey);
@@ -173,10 +173,10 @@ var TopoloPermissionError = class extends TopoloSdkError {
173
173
  };
174
174
  function extractTopoloServiceErrorCode(body) {
175
175
  if (!body || typeof body !== "object" || Array.isArray(body)) return null;
176
- const record = body;
177
- const topLevel = canonicalTopoloErrorCode(record.code);
176
+ const record2 = body;
177
+ const topLevel = canonicalTopoloErrorCode(record2.code);
178
178
  if (topLevel) return topLevel;
179
- const nested = record.error;
179
+ const nested = record2.error;
180
180
  const nestedString = canonicalTopoloErrorCode(nested);
181
181
  if (nestedString) return nestedString;
182
182
  if (nested && typeof nested === "object" && !Array.isArray(nested)) {
@@ -191,14 +191,14 @@ function serviceErrorCode(body, fallback) {
191
191
  }
192
192
  function extractTopoloServiceErrorDetails(body) {
193
193
  if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
194
- const record = body;
195
- let candidate = record.details;
196
- const nested = record.error;
194
+ const record2 = body;
195
+ let candidate = record2.details;
196
+ const nested = record2.error;
197
197
  if (nested && typeof nested === "object" && !Array.isArray(nested)) {
198
198
  const nestedRecord = nested;
199
199
  if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
200
200
  }
201
- if (candidate === void 0) candidate = record;
201
+ if (candidate === void 0) candidate = record2;
202
202
  if (Array.isArray(candidate)) {
203
203
  const paths = canonicalTopoloValidationPaths(candidate);
204
204
  return paths.length ? { paths } : void 0;
@@ -250,8 +250,8 @@ function canonicalTopoloValidationPaths(issues) {
250
250
  const paths = [];
251
251
  for (const issue of issues) {
252
252
  if (!issue || typeof issue !== "object" || Array.isArray(issue)) continue;
253
- const record = issue;
254
- const path = canonicalTopoloValidationPath(record.field) ?? canonicalTopoloValidationPath(record.path);
253
+ const record2 = issue;
254
+ const path = canonicalTopoloValidationPath(record2.field) ?? canonicalTopoloValidationPath(record2.path);
255
255
  if (path && !paths.includes(path)) paths.push(path);
256
256
  if (paths.length === 32) break;
257
257
  }
@@ -718,6 +718,222 @@ function isMultipleOf(value, divisor) {
718
718
  const quotient = value / divisor;
719
719
  return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * Math.max(1, Math.abs(quotient)) * 4;
720
720
  }
721
+ var ACTOR_ENVELOPE_VERSION = 1;
722
+ var TOPOLO_ACTOR_ENVELOPE_HEADER = "X-Topolo-Actor-Envelope";
723
+ var PRINCIPAL_TYPES = /* @__PURE__ */ new Set([
724
+ "human",
725
+ "managed_agent",
726
+ "external_agent",
727
+ "service",
728
+ "browser_automation"
729
+ ]);
730
+ var ACCOUNTABLE_SUBJECT_TYPES = /* @__PURE__ */ new Set(["human", "organization"]);
731
+ var CHANNELS = /* @__PURE__ */ new Set(["web", "mobile", "cli", "mcp", "api", "workflow", "browser_automation"]);
732
+ var ASSURANCE_LEVELS = /* @__PURE__ */ new Set(["authenticated", "workload_identity", "user_verified"]);
733
+ var HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
734
+ var ActorEnvelopeValidationError = class extends Error {
735
+ path;
736
+ constructor(path, message2) {
737
+ super(`${path}: ${message2}`);
738
+ this.name = "ActorEnvelopeValidationError";
739
+ this.path = path;
740
+ }
741
+ };
742
+ function parseActorEnvelope(value) {
743
+ const envelope = record(value, "actorEnvelope");
744
+ exactKeys(envelope, [
745
+ "schemaVersion",
746
+ "organizationId",
747
+ "accountableSubject",
748
+ "performer",
749
+ "delegation",
750
+ "channel",
751
+ "assurance",
752
+ "action",
753
+ "issuedAt",
754
+ "expiresAt"
755
+ ], "actorEnvelope");
756
+ if (envelope.schemaVersion !== ACTOR_ENVELOPE_VERSION) {
757
+ fail("actorEnvelope.schemaVersion", `must equal ${ACTOR_ENVELOPE_VERSION}`);
758
+ }
759
+ const accountableSubject = parseAccountableSubject(envelope.accountableSubject);
760
+ const performer = parseIdentity(envelope.performer, "actorEnvelope.performer");
761
+ const organizationId = text(envelope.organizationId, "actorEnvelope.organizationId");
762
+ const channel = member(envelope.channel, CHANNELS, "actorEnvelope.channel");
763
+ const issuedAt = timestamp(envelope.issuedAt, "actorEnvelope.issuedAt");
764
+ const expiresAt = envelope.expiresAt === void 0 ? void 0 : timestamp(envelope.expiresAt, "actorEnvelope.expiresAt");
765
+ if (expiresAt && Date.parse(expiresAt) <= Date.parse(issuedAt)) {
766
+ fail("actorEnvelope.expiresAt", "must be later than issuedAt");
767
+ }
768
+ const assuranceValue = record(envelope.assurance, "actorEnvelope.assurance");
769
+ exactKeys(assuranceValue, ["level", "methods"], "actorEnvelope.assurance");
770
+ const assurance = {
771
+ level: member(assuranceValue.level, ASSURANCE_LEVELS, "actorEnvelope.assurance.level"),
772
+ methods: textArray(assuranceValue.methods, "actorEnvelope.assurance.methods")
773
+ };
774
+ const actionValue = record(envelope.action, "actorEnvelope.action");
775
+ exactKeys(actionValue, ["requestId", "actionId", "resourceType", "resourceId", "inputHash"], "actorEnvelope.action");
776
+ const inputHash = text(actionValue.inputHash, "actorEnvelope.action.inputHash");
777
+ if (!HASH_PATTERN.test(inputHash)) fail("actorEnvelope.action.inputHash", "must be a lowercase sha256 digest");
778
+ const action = {
779
+ requestId: text(actionValue.requestId, "actorEnvelope.action.requestId"),
780
+ actionId: text(actionValue.actionId, "actorEnvelope.action.actionId"),
781
+ ...actionValue.resourceType === void 0 ? {} : { resourceType: text(actionValue.resourceType, "actorEnvelope.action.resourceType") },
782
+ ...actionValue.resourceId === void 0 ? {} : { resourceId: text(actionValue.resourceId, "actorEnvelope.action.resourceId") },
783
+ inputHash
784
+ };
785
+ if (action.resourceType === void 0 !== (action.resourceId === void 0)) {
786
+ fail("actorEnvelope.action", "resourceType and resourceId must be provided together");
787
+ }
788
+ const delegation = parseDelegation(envelope.delegation);
789
+ const directHuman = performer.principalType === "human" && performer.principalId === accountableSubject.principalId;
790
+ if (directHuman && delegation !== null) fail("actorEnvelope.delegation", "must be null for direct human action");
791
+ if (!directHuman && delegation === null) fail("actorEnvelope.delegation", "is required when performer differs from accountable subject");
792
+ if (delegation) {
793
+ const finalHop = delegation.lineage.at(-1);
794
+ if (!finalHop || finalHop.principalId !== performer.principalId || finalHop.principalType !== performer.principalType) {
795
+ fail("actorEnvelope.delegation.lineage", "must end with the performer");
796
+ }
797
+ }
798
+ if (performer.principalType === "external_agent" && (!performer.installationId || !performer.provider)) {
799
+ fail("actorEnvelope.performer", "external_agent requires installationId and provider");
800
+ }
801
+ if (performer.principalType === "managed_agent" && !performer.agentInstanceId) {
802
+ fail("actorEnvelope.performer.agentInstanceId", "is required for managed_agent");
803
+ }
804
+ if (performer.principalType === "browser_automation" && (!performer.installationId || !performer.browserSessionId)) {
805
+ fail("actorEnvelope.performer", "browser_automation requires installationId and browserSessionId");
806
+ }
807
+ return {
808
+ schemaVersion: ACTOR_ENVELOPE_VERSION,
809
+ organizationId,
810
+ accountableSubject,
811
+ performer,
812
+ delegation,
813
+ channel,
814
+ assurance,
815
+ action,
816
+ issuedAt,
817
+ ...expiresAt ? { expiresAt } : {}
818
+ };
819
+ }
820
+ function canonicalizeActorEnvelope(value) {
821
+ return canonicalJson(parseActorEnvelope(value));
822
+ }
823
+ function encodeActorEnvelopeHeader(value) {
824
+ const bytes = new TextEncoder().encode(canonicalizeActorEnvelope(value));
825
+ const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join("");
826
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
827
+ }
828
+ function canonicalJson(value) {
829
+ if (value === null) return "null";
830
+ if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
831
+ if (typeof value === "number") {
832
+ if (!Number.isFinite(value)) throw new TypeError("Canonical JSON accepts finite numbers only.");
833
+ return JSON.stringify(value);
834
+ }
835
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
836
+ if (typeof value !== "object" || !value || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
837
+ throw new TypeError("Canonical JSON accepts JSON objects, arrays, and primitives only.");
838
+ }
839
+ const entries = Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right));
840
+ return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
841
+ }
842
+ function parseAccountableSubject(value) {
843
+ const path = "actorEnvelope.accountableSubject";
844
+ const subject = record(value, path);
845
+ exactKeys(subject, ["principalType", "principalId", "displayName"], path);
846
+ return {
847
+ principalType: member(subject.principalType, ACCOUNTABLE_SUBJECT_TYPES, `${path}.principalType`),
848
+ principalId: text(subject.principalId, `${path}.principalId`),
849
+ ...subject.displayName === void 0 ? {} : { displayName: text(subject.displayName, `${path}.displayName`) }
850
+ };
851
+ }
852
+ async function sha256CanonicalValue(value) {
853
+ const bytes = new TextEncoder().encode(canonicalJson(value));
854
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
855
+ return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
856
+ }
857
+ function parseIdentity(value, path) {
858
+ const identity = record(value, path);
859
+ exactKeys(identity, [
860
+ "principalType",
861
+ "principalId",
862
+ "displayName",
863
+ "installationId",
864
+ "agentInstanceId",
865
+ "browserSessionId",
866
+ "provider",
867
+ "model"
868
+ ], path);
869
+ return {
870
+ principalType: member(identity.principalType, PRINCIPAL_TYPES, `${path}.principalType`),
871
+ principalId: text(identity.principalId, `${path}.principalId`),
872
+ ...optionalTextFields(identity, path, [
873
+ "displayName",
874
+ "installationId",
875
+ "agentInstanceId",
876
+ "browserSessionId",
877
+ "provider",
878
+ "model"
879
+ ])
880
+ };
881
+ }
882
+ function parseDelegation(value) {
883
+ if (value === null) return null;
884
+ const delegation = record(value, "actorEnvelope.delegation");
885
+ exactKeys(delegation, ["taskId", "runId", "lineage"], "actorEnvelope.delegation");
886
+ if (!Array.isArray(delegation.lineage) || delegation.lineage.length === 0 || delegation.lineage.length > 16) {
887
+ fail("actorEnvelope.delegation.lineage", "must contain between 1 and 16 hops");
888
+ }
889
+ return {
890
+ taskId: text(delegation.taskId, "actorEnvelope.delegation.taskId"),
891
+ runId: text(delegation.runId, "actorEnvelope.delegation.runId"),
892
+ lineage: delegation.lineage.map((value2, index) => {
893
+ const hop = record(value2, `actorEnvelope.delegation.lineage[${index}]`);
894
+ exactKeys(hop, ["principalType", "principalId"], `actorEnvelope.delegation.lineage[${index}]`);
895
+ return {
896
+ principalType: member(hop.principalType, PRINCIPAL_TYPES, `actorEnvelope.delegation.lineage[${index}].principalType`),
897
+ principalId: text(hop.principalId, `actorEnvelope.delegation.lineage[${index}].principalId`)
898
+ };
899
+ })
900
+ };
901
+ }
902
+ function record(value, path) {
903
+ if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
904
+ return value;
905
+ }
906
+ function exactKeys(value, allowed, path) {
907
+ const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
908
+ if (unknown.length) fail(path, `contains unknown field ${unknown[0]}`);
909
+ }
910
+ function text(value, path) {
911
+ if (typeof value !== "string" || value.trim() !== value || value.length === 0 || value.length > 256) {
912
+ fail(path, "must be a non-empty trimmed string of at most 256 characters");
913
+ }
914
+ return value;
915
+ }
916
+ function timestamp(value, path) {
917
+ const result = text(value, path);
918
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(result) || Number.isNaN(Date.parse(result))) {
919
+ fail(path, "must be an RFC 3339 UTC timestamp");
920
+ }
921
+ return result;
922
+ }
923
+ function member(value, values, path) {
924
+ if (typeof value !== "string" || !values.has(value)) fail(path, "contains an unsupported value");
925
+ return value;
926
+ }
927
+ function textArray(value, path) {
928
+ if (!Array.isArray(value) || value.length === 0 || value.length > 16) fail(path, "must contain between 1 and 16 values");
929
+ return value.map((item, index) => text(item, `${path}[${index}]`));
930
+ }
931
+ function optionalTextFields(value, path, keys) {
932
+ return Object.fromEntries(keys.flatMap((key) => value[key] === void 0 ? [] : [[key, text(value[key], `${path}.${key}`)]]));
933
+ }
934
+ function fail(path, message2) {
935
+ throw new ActorEnvelopeValidationError(path, message2);
936
+ }
721
937
  function parseTopoloActionUploadContract(value) {
722
938
  if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
723
939
  throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
@@ -922,6 +1138,7 @@ var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
922
1138
  var TopoloClient = class _TopoloClient {
923
1139
  credential;
924
1140
  agent;
1141
+ actorContext;
925
1142
  serviceUrls;
926
1143
  environment;
927
1144
  appIds;
@@ -952,6 +1169,11 @@ var TopoloClient = class _TopoloClient {
952
1169
  if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
953
1170
  this.credential = options.credential;
954
1171
  this.agent = options.agent;
1172
+ this.actorContext = options.actorContext ? {
1173
+ channel: options.actorContext.channel,
1174
+ taskId: options.actorContext.taskId ?? `task_${generateRequestId()}`,
1175
+ runId: options.actorContext.runId ?? `run_${generateRequestId()}`
1176
+ } : void 0;
955
1177
  this.environment = options.environment;
956
1178
  this.serviceUrls = options.environment ? topoloServiceOrigins(options.serviceUrls, options.environment) : options.serviceUrls;
957
1179
  this.appIds = options.appIds;
@@ -1005,11 +1227,15 @@ var TopoloClient = class _TopoloClient {
1005
1227
  const platformAppId = resolveConfiguredAppId(resolvedService.serviceKey, this.appIds);
1006
1228
  if (platformAppId) headers.set("X-App-ID", platformAppId);
1007
1229
  applyAuthHeaders(headers, this.credential);
1008
- const requestId = generateRequestId();
1230
+ const actorEnvelope = opts.actorEnvelope === void 0 ? void 0 : parseActorEnvelope(opts.actorEnvelope);
1231
+ const requestId = actorEnvelope?.action.requestId ?? generateRequestId();
1009
1232
  applyAuditHeaders(headers, this.agent, requestId);
1010
1233
  if (opts.headers) {
1011
1234
  for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
1012
1235
  }
1236
+ if (actorEnvelope) {
1237
+ headers.set(TOPOLO_ACTOR_ENVELOPE_HEADER, encodeActorEnvelopeHeader(actorEnvelope));
1238
+ }
1013
1239
  if (opts.resource) {
1014
1240
  const resourceType = opts.resource.resourceType.trim();
1015
1241
  const resourceId = opts.resource.resourceId.trim();
@@ -1456,14 +1682,14 @@ var TopoloClient = class _TopoloClient {
1456
1682
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
1457
1683
  throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is unavailable.");
1458
1684
  }
1459
- const record = raw;
1460
- if (record.algorithm !== "sha256-v1" || typeof record.digest !== "string" || !/^[a-f0-9]{64}$/.test(record.digest) || !Number.isSafeInteger(record.actionCount) || Number(record.actionCount) < 0) {
1685
+ const record2 = raw;
1686
+ if (record2.algorithm !== "sha256-v1" || typeof record2.digest !== "string" || !/^[a-f0-9]{64}$/.test(record2.digest) || !Number.isSafeInteger(record2.actionCount) || Number(record2.actionCount) < 0) {
1461
1687
  throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is invalid.");
1462
1688
  }
1463
1689
  return {
1464
1690
  algorithm: "sha256-v1",
1465
- digest: record.digest,
1466
- actionCount: Number(record.actionCount)
1691
+ digest: record2.digest,
1692
+ actionCount: Number(record2.actionCount)
1467
1693
  };
1468
1694
  }
1469
1695
  /** @deprecated Prefer searchActions() so callers retain the continuation cursor. */
@@ -1663,6 +1889,20 @@ var TopoloClient = class _TopoloClient {
1663
1889
  );
1664
1890
  }
1665
1891
  const mapped = mapActionInput(entry, input2);
1892
+ const resolvedActorEnvelope = options.actorEnvelope ?? await this.buildVerifiedAgentActorEnvelope(entry.actionId, input2, options.resource);
1893
+ if (resolvedActorEnvelope) {
1894
+ const actorEnvelope = parseActorEnvelope(resolvedActorEnvelope);
1895
+ if (actorEnvelope.action.actionId !== entry.actionId) {
1896
+ throw new TopoloSdkError("actor_envelope_action_mismatch", "Actor envelope actionId does not match the executed action.");
1897
+ }
1898
+ if (actorEnvelope.action.inputHash !== await sha256CanonicalValue(input2)) {
1899
+ throw new TopoloSdkError("actor_envelope_input_mismatch", "Actor envelope inputHash does not match the executed action input.");
1900
+ }
1901
+ const envelopeResource = actorEnvelope.action.resourceType && actorEnvelope.action.resourceId ? { resourceType: actorEnvelope.action.resourceType, resourceId: actorEnvelope.action.resourceId } : void 0;
1902
+ if (envelopeResource?.resourceType !== options.resource?.resourceType || envelopeResource?.resourceId !== options.resource?.resourceId) {
1903
+ throw new TopoloSdkError("actor_envelope_resource_mismatch", "Actor envelope resource does not match the executed action resource.");
1904
+ }
1905
+ }
1666
1906
  const service = entry.appSlug || entry.appId;
1667
1907
  const result = await this.request({
1668
1908
  service,
@@ -1672,6 +1912,7 @@ var TopoloClient = class _TopoloClient {
1672
1912
  ...mapped.query ? { query: mapped.query } : {},
1673
1913
  ...mapped.body !== void 0 ? { body: mapped.body } : {},
1674
1914
  ...options.resource ? { resource: options.resource } : {},
1915
+ ...resolvedActorEnvelope ? { actorEnvelope: resolvedActorEnvelope } : {},
1675
1916
  headers: {
1676
1917
  [TOPOLO_ACTION_ID_HEADER]: entry.actionId,
1677
1918
  [TOPOLO_ACTION_NAME_HEADER]: entry.name
@@ -1680,6 +1921,56 @@ var TopoloClient = class _TopoloClient {
1680
1921
  });
1681
1922
  return redactActionResult(result);
1682
1923
  }
1924
+ async buildVerifiedAgentActorEnvelope(actionId, input2, resource) {
1925
+ if (!this.actorContext) return void 0;
1926
+ const identity = await this.introspect();
1927
+ const principalType = identity.user.principalType;
1928
+ if (principalType !== "agent_employee" && principalType !== "external_agent") return void 0;
1929
+ if (!identity.organization || !identity.user.accountableUserId || !identity.user.principalId) {
1930
+ throw new TopoloSdkError(
1931
+ "incomplete_agent_identity",
1932
+ "Verified agent credentials must resolve an organization, accountable user, and principal id."
1933
+ );
1934
+ }
1935
+ const performer = principalType === "external_agent" ? {
1936
+ principalType: "external_agent",
1937
+ principalId: identity.user.principalId,
1938
+ installationId: identity.user.installationId ?? void 0,
1939
+ provider: identity.user.provider ?? void 0,
1940
+ model: identity.user.model ?? void 0
1941
+ } : {
1942
+ principalType: "managed_agent",
1943
+ principalId: identity.user.principalId,
1944
+ agentInstanceId: identity.user.agentInstanceId ?? void 0,
1945
+ model: identity.user.model ?? void 0
1946
+ };
1947
+ if (performer.principalType === "external_agent" && (!performer.installationId || !performer.provider) || performer.principalType === "managed_agent" && !performer.agentInstanceId) {
1948
+ throw new TopoloSdkError(
1949
+ "incomplete_agent_identity",
1950
+ "Verified agent credentials are missing the performer identity required for provenance."
1951
+ );
1952
+ }
1953
+ return parseActorEnvelope({
1954
+ schemaVersion: 1,
1955
+ organizationId: identity.organization.id,
1956
+ accountableSubject: { principalType: "human", principalId: identity.user.accountableUserId },
1957
+ performer,
1958
+ delegation: {
1959
+ taskId: this.actorContext.taskId,
1960
+ runId: this.actorContext.runId,
1961
+ lineage: [{ principalType: performer.principalType, principalId: performer.principalId }]
1962
+ },
1963
+ channel: this.actorContext.channel,
1964
+ assurance: { level: "workload_identity", methods: ["client_credentials"] },
1965
+ action: {
1966
+ requestId: generateRequestId(),
1967
+ actionId,
1968
+ ...resource ? resource : {},
1969
+ inputHash: await sha256CanonicalValue(input2)
1970
+ },
1971
+ issuedAt: (/* @__PURE__ */ new Date()).toISOString()
1972
+ });
1973
+ }
1683
1974
  /**
1684
1975
  * Introspect the current credential. Returns the caller's resolved identity,
1685
1976
  * organization, and permission set. Used by CLI `whoami` and by MCP to gate
@@ -1707,7 +1998,18 @@ var TopoloClient = class _TopoloClient {
1707
1998
  email: user.email,
1708
1999
  name: user.name ?? null,
1709
2000
  role: user.role ?? (kind === "api_key" ? "service" : "member"),
1710
- permissions: payload.permissions ?? user.permissions ?? []
2001
+ permissions: payload.permissions ?? user.permissions ?? [],
2002
+ ...normalizeAgentPrincipalType(user.principalType) ? {
2003
+ principalId: readOptionalString(user.principalId),
2004
+ principalType: normalizeAgentPrincipalType(user.principalType),
2005
+ accountableUserId: readOptionalString(user.accountableUserId),
2006
+ agentInstanceId: readOptionalString(user.agentInstanceId),
2007
+ installationId: readOptionalString(user.installationId),
2008
+ provider: readOptionalString(user.provider),
2009
+ model: readOptionalString(user.model),
2010
+ serviceScopes: stringArray(user.serviceScopes),
2011
+ policyRef: readOptionalString(user.policyRef)
2012
+ } : {}
1711
2013
  },
1712
2014
  organization: organization ? {
1713
2015
  id: organization.id,
@@ -2072,6 +2374,9 @@ function normalizeCredentialAuthorizationMode(value, kind) {
2072
2374
  if (value === "context_rbac" || value === "oauth_scopes" || value === "session_rbac") return value;
2073
2375
  return "unknown";
2074
2376
  }
2377
+ function normalizeAgentPrincipalType(value) {
2378
+ return value === "agent_employee" || value === "external_agent" ? value : null;
2379
+ }
2075
2380
  function describeError(body, fallback) {
2076
2381
  if (body && typeof body === "object") {
2077
2382
  const maybe = body;
@@ -2096,10 +2401,10 @@ function extractRequired(body) {
2096
2401
  if (Array.isArray(maybe.required)) return canonicalTopoloRequired(maybe.required);
2097
2402
  for (const nested of [maybe.details, maybe.error]) {
2098
2403
  if (!nested || typeof nested !== "object" || Array.isArray(nested)) continue;
2099
- const record = nested;
2100
- if (Array.isArray(record.required)) return canonicalTopoloRequired(record.required);
2101
- if (record.details && typeof record.details === "object" && !Array.isArray(record.details)) {
2102
- const required = record.details.required;
2404
+ const record2 = nested;
2405
+ if (Array.isArray(record2.required)) return canonicalTopoloRequired(record2.required);
2406
+ if (record2.details && typeof record2.details === "object" && !Array.isArray(record2.details)) {
2407
+ const required = record2.details.required;
2103
2408
  if (Array.isArray(required)) return canonicalTopoloRequired(required);
2104
2409
  }
2105
2410
  }
@@ -2174,8 +2479,8 @@ function compactResultValue(value, depth, budget) {
2174
2479
  return value.length > ACTION_RESULT_MAX_ITEMS ? { count: value.length, items, omittedItems: value.length - ACTION_RESULT_MAX_ITEMS } : items;
2175
2480
  }
2176
2481
  if (typeof value === "object") {
2177
- const record = value;
2178
- const allKeys = Object.keys(record);
2482
+ const record2 = value;
2483
+ const allKeys = Object.keys(record2);
2179
2484
  const ranked = allKeys.sort((left, right) => resultKeyPriority(left) - resultKeyPriority(right));
2180
2485
  const prioritized = ranked.filter((key) => resultKeyPriority(key) < 2);
2181
2486
  const ordinary = ranked.filter((key) => resultKeyPriority(key) === 2);
@@ -2184,7 +2489,7 @@ function compactResultValue(value, depth, budget) {
2184
2489
  ...prioritized,
2185
2490
  ...ordinary.slice(0, ordinaryLimit)
2186
2491
  ].slice(0, ACTION_RESULT_MAX_KEYS);
2187
- const compact = Object.fromEntries(keys.map((key) => [key, compactResultValue(record[key], depth + 1, budget)]));
2492
+ const compact = Object.fromEntries(keys.map((key) => [key, compactResultValue(record2[key], depth + 1, budget)]));
2188
2493
  if (allKeys.length > keys.length) compact["_omittedKeys"] = allKeys.length - keys.length;
2189
2494
  return compact;
2190
2495
  }
@@ -2948,7 +3253,7 @@ function createTopolo(options) {
2948
3253
  };
2949
3254
  }
2950
3255
 
2951
- // node_modules/@topolo/sdk/dist/node/credential-store.js
3256
+ // node_modules/.pnpm/@topolo+sdk@0.17.1/node_modules/@topolo/sdk/dist/node/credential-store.js
2952
3257
  import { mkdir, readFile, writeFile, chmod, rm, stat } from "fs/promises";
2953
3258
  import { existsSync } from "fs";
2954
3259
  import { homedir, platform } from "os";
@@ -3071,10 +3376,10 @@ var TopoloAuthError2 = class extends TopoloSdkError2 {
3071
3376
  };
3072
3377
  function extractTopoloServiceErrorCode2(body) {
3073
3378
  if (!body || typeof body !== "object" || Array.isArray(body)) return null;
3074
- const record = body;
3075
- const topLevel = canonicalTopoloErrorCode2(record.code);
3379
+ const record2 = body;
3380
+ const topLevel = canonicalTopoloErrorCode2(record2.code);
3076
3381
  if (topLevel) return topLevel;
3077
- const nested = record.error;
3382
+ const nested = record2.error;
3078
3383
  const nestedString = canonicalTopoloErrorCode2(nested);
3079
3384
  if (nestedString) return nestedString;
3080
3385
  if (nested && typeof nested === "object" && !Array.isArray(nested)) {
@@ -3089,14 +3394,14 @@ function serviceErrorCode2(body, fallback) {
3089
3394
  }
3090
3395
  function extractTopoloServiceErrorDetails2(body) {
3091
3396
  if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
3092
- const record = body;
3093
- let candidate = record.details;
3094
- const nested = record.error;
3397
+ const record2 = body;
3398
+ let candidate = record2.details;
3399
+ const nested = record2.error;
3095
3400
  if (nested && typeof nested === "object" && !Array.isArray(nested)) {
3096
3401
  const nestedRecord = nested;
3097
3402
  if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
3098
3403
  }
3099
- if (candidate === void 0) candidate = record;
3404
+ if (candidate === void 0) candidate = record2;
3100
3405
  if (Array.isArray(candidate)) {
3101
3406
  const paths = canonicalTopoloValidationPaths2(candidate);
3102
3407
  return paths.length ? { paths } : void 0;
@@ -3148,8 +3453,8 @@ function canonicalTopoloValidationPaths2(issues) {
3148
3453
  const paths = [];
3149
3454
  for (const issue of issues) {
3150
3455
  if (!issue || typeof issue !== "object" || Array.isArray(issue)) continue;
3151
- const record = issue;
3152
- const path = canonicalTopoloValidationPath2(record.field) ?? canonicalTopoloValidationPath2(record.path);
3456
+ const record2 = issue;
3457
+ const path = canonicalTopoloValidationPath2(record2.field) ?? canonicalTopoloValidationPath2(record2.path);
3153
3458
  if (path && !paths.includes(path)) paths.push(path);
3154
3459
  if (paths.length === 32) break;
3155
3460
  }
@@ -3725,7 +4030,7 @@ function installOutputErrorHandlers(stdout = process.stdout, stderr = process.st
3725
4030
  stdout.on("error", handler);
3726
4031
  stderr.on("error", handler);
3727
4032
  }
3728
- function fail(err, mode) {
4033
+ function fail2(err, mode) {
3729
4034
  const payload = normalizeError(err);
3730
4035
  if (mode === "json") {
3731
4036
  process.stderr.write(JSON.stringify({ error: payload }, null, 2) + "\n");
@@ -3834,7 +4139,7 @@ Visit ${apiKeyIssuanceUrl(environment)} to mint one, or configure an OAuth clien
3834
4139
  openBrowser: opts.browser !== false
3835
4140
  });
3836
4141
  } catch (err) {
3837
- fail(err, mode);
4142
+ fail2(err, mode);
3838
4143
  }
3839
4144
  }
3840
4145
  );
@@ -3850,7 +4155,7 @@ Visit ${apiKeyIssuanceUrl(environment)} to mint one, or configure an OAuth clien
3850
4155
  () => `Logged out of Topolo ${environmentLabel(environment)}.`
3851
4156
  );
3852
4157
  } catch (err) {
3853
- fail(err, mode);
4158
+ fail2(err, mode);
3854
4159
  }
3855
4160
  });
3856
4161
  auth.command("status").description("Show which credential source is in use (env vs config file)").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (opts) => {
@@ -3876,7 +4181,7 @@ Visit ${apiKeyIssuanceUrl(environment)} to mint one, or configure an OAuth clien
3876
4181
  ].join("\n")
3877
4182
  );
3878
4183
  } catch (err) {
3879
- fail(err, mode);
4184
+ fail2(err, mode);
3880
4185
  }
3881
4186
  });
3882
4187
  }
@@ -4068,6 +4373,7 @@ async function buildClient(options) {
4068
4373
  return createTopolo({
4069
4374
  credential,
4070
4375
  environment,
4376
+ actorContext: { channel: "cli" },
4071
4377
  agent: {
4072
4378
  clientName: "topolo-cli",
4073
4379
  clientVersion: CLI_VERSION,
@@ -4125,7 +4431,7 @@ function registerWhoamiCommand(program2) {
4125
4431
  const info = await topolo.identity.whoami();
4126
4432
  emit(info, mode, () => formatPretty(info));
4127
4433
  } catch (err) {
4128
- fail(err, mode);
4434
+ fail2(err, mode);
4129
4435
  }
4130
4436
  });
4131
4437
  }
@@ -4206,7 +4512,7 @@ function registerApiCommand(program2) {
4206
4512
  });
4207
4513
  emit(result, mode, () => JSON.stringify(result, null, 2));
4208
4514
  } catch (err) {
4209
- fail(err, mode);
4515
+ fail2(err, mode);
4210
4516
  }
4211
4517
  }
4212
4518
  );
@@ -4270,7 +4576,7 @@ function registerActionsCommand(program2) {
4270
4576
  const output = opts.detail ? { ...result, actions: actions2 } : { actions: actions2.map(compactActionCatalogEntry), page: result.page };
4271
4577
  emit(output, mode, () => formatActionRows(actions2, result.page.nextCursor));
4272
4578
  } catch (err) {
4273
- fail(err, mode);
4579
+ fail2(err, mode);
4274
4580
  }
4275
4581
  });
4276
4582
  actions.command("get <action>").description("Describe one Topolo action by exact action id").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
@@ -4283,7 +4589,7 @@ function registerActionsCommand(program2) {
4283
4589
  const entry = await topolo.client.getAction(action);
4284
4590
  emit(entry, mode, () => formatActionDetail(entry));
4285
4591
  } catch (err) {
4286
- fail(err, mode);
4592
+ fail2(err, mode);
4287
4593
  }
4288
4594
  });
4289
4595
  actions.command("call <action>").description("Call a permission-scoped Topolo action").option("-d, --data <json-or-@file>", "JSON action input or @path to a JSON file", "{}").option("--resource-type <type>", "App-local resource alias, such as workspace").option("--resource-id <id>", "Resource id for the selected resource type").option("--confirm", "Acknowledge a mutating action").option("--detail", "Return the complete action response instead of a bounded agent projection").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
@@ -4302,7 +4608,7 @@ function registerActionsCommand(program2) {
4302
4608
  const output = detailRequested(opts.detail, command) ? result : compactActionResult(action, result);
4303
4609
  emit(output, mode, () => JSON.stringify(output, null, 2));
4304
4610
  } catch (err) {
4305
- fail(err, mode);
4611
+ fail2(err, mode);
4306
4612
  }
4307
4613
  });
4308
4614
  actions.command("upload <action>").description("Upload one local file through an action-declared upload contract").requiredOption("--file <path>", "Local file to upload").option("--content-type <type>", "MIME type; inferred for common file extensions").option("-d, --data <json-or-@file>", "Additional JSON action input or @path to a JSON file", "{}").option("--complete-data <json-or-@file>", "Additional input for a declared completion action", "{}").option("--resource-type <type>", "App-local resource alias, such as workspace").option("--resource-id <id>", "Resource id for the selected resource type").option("--confirm", "Acknowledge a mutating upload action").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
@@ -4326,7 +4632,7 @@ function registerActionsCommand(program2) {
4326
4632
  );
4327
4633
  emit(result, mode, () => JSON.stringify(result, null, 2));
4328
4634
  } catch (err) {
4329
- fail(err, mode);
4635
+ fail2(err, mode);
4330
4636
  }
4331
4637
  });
4332
4638
  actions.command("validate <action>").description("Validate action input locally against the published JSON Schema").option("-d, --data <json-or-@file>", "JSON action input or @path to a JSON file", "{}").option("--detail", "Include the full action contract").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
@@ -4337,7 +4643,7 @@ function registerActionsCommand(program2) {
4337
4643
  const output = detailRequested(opts.detail, command) ? result : compactActionValidation(result.action.actionId, result.validation);
4338
4644
  emit(output, mode, () => result.validation.valid ? `Valid input for ${result.action.actionId}.` : result.validation.errors.map((issue) => `${issue.path}: ${issue.message}`).join("\n"));
4339
4645
  } catch (err) {
4340
- fail(err, mode);
4646
+ fail2(err, mode);
4341
4647
  }
4342
4648
  });
4343
4649
  actions.command("prepare <action>").description("Prepare one action with identity, example, validation, exact request, verification, and rollback").option("-d, --data <json-or-@file>", "JSON action input or @path; defaults to one valid published example").option("--resource-type <type>", "App-local resource alias, such as workspace").option("--resource-id <id>", "Resource id for the selected resource type").option("--confirm", "Include confirmation in the plan without executing it").option("--json", "Output compact JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
@@ -4353,7 +4659,7 @@ function registerActionsCommand(program2) {
4353
4659
  const output = compactActionPreparation(preparation, await actionExecutionContext(topolo));
4354
4660
  emit(output, mode, () => formatActionPreparation(output));
4355
4661
  } catch (err) {
4356
- fail(err, mode);
4662
+ fail2(err, mode);
4357
4663
  }
4358
4664
  });
4359
4665
  actions.command("plan <action>").description("Validate and preview the exact request, confirmation, verification, and rollback plan").option("-d, --data <json-or-@file>", "JSON action input or @path to a JSON file", "{}").option("--resource-type <type>", "App-local resource alias, such as workspace").option("--resource-id <id>", "Resource id for the selected resource type").option("--confirm", "Include confirmation in the plan without executing it").option("--detail", "Include the full action contract").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
@@ -4367,7 +4673,7 @@ function registerActionsCommand(program2) {
4367
4673
  });
4368
4674
  emit(detailRequested(opts.detail, command) ? plan : compactActionPlan(plan), mode, () => formatActionPlan(plan));
4369
4675
  } catch (err) {
4370
- fail(err, mode);
4676
+ fail2(err, mode);
4371
4677
  }
4372
4678
  });
4373
4679
  actions.command("examples <action>").description("Show published examples and public docs for one action").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
@@ -4382,7 +4688,7 @@ function registerActionsCommand(program2) {
4382
4688
  };
4383
4689
  emit(result, mode, () => result.examples.length ? JSON.stringify(result, null, 2) : `No inline examples published. Docs: ${result.docsUrl || "not published"}`);
4384
4690
  } catch (err) {
4385
- fail(err, mode);
4691
+ fail2(err, mode);
4386
4692
  }
4387
4693
  });
4388
4694
  actions.command("capabilities").description("List every published action for one application (its capability contract)").option("-s, --service <service>", "App id, slug, or name").option("--summary", "Return compact capability counts without action rows").option("--detail", "Include full schemas and agent contracts").option("--json", "Output JSON").option("--no-json", "Output human-readable table").action(async (opts, command) => {
@@ -4417,7 +4723,7 @@ function registerActionsCommand(program2) {
4417
4723
  const output = opts.summary ? summarizeActionCapabilities(resolvedService, discovered) : detailRequested(opts.detail, command) ? result : { actions: discovered.map(compactActionCatalogEntry), page: result.page };
4418
4724
  emit(output, mode, () => opts.summary ? formatActionCapabilitySummary(summarizeActionCapabilities(resolvedService, discovered)) : formatActionRows(discovered));
4419
4725
  } catch (err) {
4420
- fail(err, mode);
4726
+ fail2(err, mode);
4421
4727
  }
4422
4728
  });
4423
4729
  }
@@ -4605,7 +4911,7 @@ function registerAppsCommand(program2) {
4605
4911
  const applications = publicApplicationRows(result.services);
4606
4912
  emit({ applications, page: result.page }, mode, () => formatPublicApplicationRows(applications));
4607
4913
  } catch (error) {
4608
- fail(error, mode);
4914
+ fail2(error, mode);
4609
4915
  }
4610
4916
  });
4611
4917
  apps.command("get <application>").description("Show one application available to the current Topolo credential").option("--json", "Output JSON", true).option("--no-json", "Output human-readable details").action(async (application, opts) => {
@@ -4617,7 +4923,7 @@ function registerAppsCommand(program2) {
4617
4923
  if (!selected) throw new Error(`Application "${application}" is not a customer application.`);
4618
4924
  emit({ application: selected }, mode, () => formatPublicApplication(selected));
4619
4925
  } catch (error) {
4620
- fail(error, mode);
4926
+ fail2(error, mode);
4621
4927
  }
4622
4928
  });
4623
4929
  }
@@ -4676,7 +4982,7 @@ function registerServicesCommand(program2) {
4676
4982
  const rows = buildRows(result.services, config.serviceUrls, environment);
4677
4983
  emit({ environment, services: rows, page: result.page }, mode, () => formatRows(rows, environment, result.page.nextCursor));
4678
4984
  } catch (err) {
4679
- fail(err, mode);
4985
+ fail2(err, mode);
4680
4986
  }
4681
4987
  });
4682
4988
  }
@@ -4769,7 +5075,7 @@ function registerOAuthCommands(program2) {
4769
5075
  return clients2.map((client) => `${client.client_id} ${client.name} ${client.status}`).join("\n");
4770
5076
  });
4771
5077
  } catch (err) {
4772
- fail(err, mode);
5078
+ fail2(err, mode);
4773
5079
  }
4774
5080
  });
4775
5081
  clients.command("create").description("Create a Login with Topolo OAuth client.").requiredOption("--name <name>", "Client display name").requiredOption("--developer-id <id>", "Topolo developer id that owns this client").option("--developer-app-id <id>", "Developer app id to associate with this client").option("--description <description>", "Client description").option("--redirect-uri <uri...>", "Allowed callback URI. Repeat or pass a space-separated list.").option("--scope <scope...>", "Allowed scope. Defaults to openid profile email.").option("--public", "Create a public PKCE client instead of a confidential client").option("--homepage-url <url>", "Client homepage URL").option("--logo-url <url>", "Client logo URL").option("--json", "Emit JSON output").action(async (options) => {
@@ -4789,7 +5095,7 @@ function registerOAuthCommands(program2) {
4789
5095
  ].join("\n");
4790
5096
  });
4791
5097
  } catch (err) {
4792
- fail(err, mode);
5098
+ fail2(err, mode);
4793
5099
  }
4794
5100
  });
4795
5101
  clients.command("revoke").description("Revoke an OAuth client for all users.").argument("<id>", "OAuth client record id, for example oac_...").option("--json", "Emit JSON output").action(async (id, options) => {
@@ -4800,7 +5106,7 @@ function registerOAuthCommands(program2) {
4800
5106
  });
4801
5107
  emit(payload, mode, () => `Revoked OAuth client ${id}`);
4802
5108
  } catch (err) {
4803
- fail(err, mode);
5109
+ fail2(err, mode);
4804
5110
  }
4805
5111
  });
4806
5112
  clients.command("rotate-secret").description("Rotate a confidential OAuth client secret.").argument("<id>", "OAuth client record id, for example oac_...").option("--json", "Emit JSON output").action(async (id, options) => {
@@ -4811,7 +5117,7 @@ function registerOAuthCommands(program2) {
4811
5117
  });
4812
5118
  emit(payload, mode, () => `New client secret: ${payload.data?.client_secret ?? ""}`);
4813
5119
  } catch (err) {
4814
- fail(err, mode);
5120
+ fail2(err, mode);
4815
5121
  }
4816
5122
  });
4817
5123
  }
@@ -4856,8 +5162,8 @@ async function authFetchJson(path, init = {}) {
4856
5162
  }
4857
5163
  headers.set("Authorization", `Bearer ${credential.accessToken}`);
4858
5164
  const response = await fetch(`${authBaseUrl}${path}`, { ...init, headers });
4859
- const text = await response.text();
4860
- const payload = text ? JSON.parse(text) : {};
5165
+ const text2 = await response.text();
5166
+ const payload = text2 ? JSON.parse(text2) : {};
4861
5167
  if (!response.ok || payload.success === false) {
4862
5168
  const err = new Error(payload.message || payload.error_description || payload.error || "Topolo Auth request failed");
4863
5169
  err.code = payload.code || payload.error;
@@ -4883,7 +5189,7 @@ function registerIntegrationsCommand(program2) {
4883
5189
  const prompt = buildLoginWithTopoloPrompt(framework);
4884
5190
  emit({ integration, framework, prompt }, mode, () => prompt);
4885
5191
  } catch (err) {
4886
- fail(err, mode);
5192
+ fail2(err, mode);
4887
5193
  }
4888
5194
  });
4889
5195
  integrations.command("check").description("Check whether a repo has the Login with Topolo integration pieces.").argument("<integration>", "Integration check id, currently login-with-topolo").option("--repo-root <path>", "Repository root to inspect", ".").option("--json", "Emit JSON output").action(async (integration, options) => {
@@ -4894,7 +5200,7 @@ function registerIntegrationsCommand(program2) {
4894
5200
  emit(result, mode, () => formatCheckResult(result));
4895
5201
  if (!result.passed) process.exitCode = 1;
4896
5202
  } catch (err) {
4897
- fail(err, mode);
5203
+ fail2(err, mode);
4898
5204
  }
4899
5205
  });
4900
5206
  }
@@ -5320,9 +5626,9 @@ async function readTomlRegistrations(path, agent) {
5320
5626
  return [...registrations.values()].filter((registration) => registration.command);
5321
5627
  }
5322
5628
  async function readGooseRegistrations(path) {
5323
- const text = await readFile3(path, "utf8");
5629
+ const text2 = await readFile3(path, "utf8");
5324
5630
  const registrations = [];
5325
- const lines = text.split(/\r?\n/);
5631
+ const lines = text2.split(/\r?\n/);
5326
5632
  let inExtensions = false;
5327
5633
  let inTopolo = false;
5328
5634
  let current;
@@ -5543,8 +5849,8 @@ async function unregisterAgentConfig(agent, homeDir, name, projectDir) {
5543
5849
  if (config.format === "json") {
5544
5850
  await unregisterJson(config, agent, name);
5545
5851
  } else if (config.format === "toml") {
5546
- const text = withoutMcpServerSections(await readFile4(config.path, "utf8"), name);
5547
- await atomicWrite(config.path, text ? text + "\n" : "");
5852
+ const text2 = withoutMcpServerSections(await readFile4(config.path, "utf8"), name);
5853
+ await atomicWrite(config.path, text2 ? text2 + "\n" : "");
5548
5854
  } else {
5549
5855
  await unregisterGoose(config.path, name);
5550
5856
  }
@@ -5604,12 +5910,12 @@ async function unregisterJson(config, agent, name) {
5604
5910
  }
5605
5911
  async function registerToml(path, agent, mcp, registration) {
5606
5912
  const existing = existsSync3(path) ? await readFile4(path, "utf8") : "";
5607
- const text = withoutMcpServerSections(existing, registration.name);
5913
+ const text2 = withoutMcpServerSections(existing, registration.name);
5608
5914
  const args = mcp.args.length ? "\nargs = [" + mcp.args.map((item) => JSON.stringify(item)).join(", ") + "]" : "";
5609
5915
  const environment = registrationEnvironment(agent, registration);
5610
5916
  const environmentText = Object.entries(environment).map(([key, value]) => JSON.stringify(key) + " = " + JSON.stringify(value)).join(", ");
5611
5917
  const block = agent === "grok" ? "\n\n[mcp_servers." + registration.name + "]\ncommand = " + JSON.stringify(mcp.command) + args + "\nenv = { " + environmentText + " }\n" : "\n\n[mcp_servers." + registration.name + "]\ncommand = " + JSON.stringify(mcp.command) + args + "\n\n[mcp_servers." + registration.name + ".env]\n" + Object.entries(environment).map(([key, value]) => key + " = " + JSON.stringify(value)).join("\n") + "\n";
5612
- await atomicWrite(path, text + block);
5918
+ await atomicWrite(path, text2 + block);
5613
5919
  }
5614
5920
  async function registerGoose(path, mcp, registration) {
5615
5921
  const existing = existsSync3(path) ? await readFile4(path, "utf8") : "";
@@ -5632,8 +5938,8 @@ async function unregisterGoose(path, name) {
5632
5938
  const existing = await readFile4(path, "utf8");
5633
5939
  await atomicWrite(path, removeGooseExtension(existing, name));
5634
5940
  }
5635
- function removeGooseExtension(text, name) {
5636
- const lines = text.split(/\r?\n/);
5941
+ function removeGooseExtension(text2, name) {
5942
+ const lines = text2.split(/\r?\n/);
5637
5943
  const kept = [];
5638
5944
  let inExtensions = false;
5639
5945
  let skipping = false;
@@ -5653,11 +5959,11 @@ function removeGooseExtension(text, name) {
5653
5959
  }
5654
5960
  return kept.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
5655
5961
  }
5656
- function insertGooseExtension(text, block) {
5657
- if (!text.trim()) return "extensions:\n" + block + "\n";
5658
- const lines = text.split(/\r?\n/);
5962
+ function insertGooseExtension(text2, block) {
5963
+ if (!text2.trim()) return "extensions:\n" + block + "\n";
5964
+ const lines = text2.split(/\r?\n/);
5659
5965
  const extensionIndex = lines.findIndex((line) => /^extensions:\s*$/.test(line));
5660
- if (extensionIndex < 0) return text.trimEnd() + "\n\nextensions:\n" + block + "\n";
5966
+ if (extensionIndex < 0) return text2.trimEnd() + "\n\nextensions:\n" + block + "\n";
5661
5967
  let end = lines.length;
5662
5968
  for (let index = extensionIndex + 1; index < lines.length; index += 1) {
5663
5969
  if (/^\S/.test(lines[index]) && lines[index].trim()) {
@@ -5685,11 +5991,11 @@ async function atomicWrite(path, contents) {
5685
5991
  await writeFile2(temporaryPath, contents, { encoding: "utf8", mode: 384 });
5686
5992
  await rename(temporaryPath, path);
5687
5993
  }
5688
- function withoutMcpServerSections(text, name) {
5994
+ function withoutMcpServerSections(text2, name) {
5689
5995
  const section = "[mcp_servers." + name + "]";
5690
5996
  const environmentSection = "[mcp_servers." + name + ".env]";
5691
5997
  let skipping = false;
5692
- return text.split(/\r?\n/).filter((line) => {
5998
+ return text2.split(/\r?\n/).filter((line) => {
5693
5999
  const trimmed = line.trim();
5694
6000
  if (trimmed === section || trimmed === environmentSection) {
5695
6001
  skipping = true;
@@ -5800,7 +6106,7 @@ function registerSkillsCommand(program2) {
5800
6106
  assertAgentSetupSucceeded(results);
5801
6107
  emit({ installed: results, mcpCommand: mcp }, mode, () => formatInstall(results, mcp));
5802
6108
  } catch (err) {
5803
- fail(err, mode);
6109
+ fail2(err, mode);
5804
6110
  }
5805
6111
  }
5806
6112
  );
@@ -5816,7 +6122,7 @@ function registerSkillsCommand(program2) {
5816
6122
  });
5817
6123
  emit({ agents: rows }, mode, () => formatStatus(rows));
5818
6124
  } catch (err) {
5819
- fail(err, mode);
6125
+ fail2(err, mode);
5820
6126
  }
5821
6127
  });
5822
6128
  skills.command("uninstall").description("Remove the skill file and MCP registration for each agent").option("--claude", "Target Claude Code only").option("--codex", "Target Codex only").option("--agent <host...>", "Target one or more hosts by id").option("--all", "Remove from every supported host").option("--project <dir>", "Remove project-scoped host files under <dir>").option("--agent-home <dir>", "Target an alternate agent home (managed installs and clean-room verification)").option("--name <name>", "Remove one registration (defaults to topolo)").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(async (opts) => {
@@ -5832,7 +6138,7 @@ function registerSkillsCommand(program2) {
5832
6138
  () => removed.map((r) => `${AGENT_LABELS2[r.agent]}: ${r.name} removed=${r.registrationRemoved}, skill removed=${r.skillRemoved}`).join("\n")
5833
6139
  );
5834
6140
  } catch (err) {
5835
- fail(err, mode);
6141
+ fail2(err, mode);
5836
6142
  }
5837
6143
  });
5838
6144
  }
@@ -5863,7 +6169,7 @@ function registerSetupCommand(program2) {
5863
6169
  assertAgentSetupSucceeded(results);
5864
6170
  emit({ installed: results, mcpCommand: mcp }, mode, () => formatInstall(results, mcp));
5865
6171
  } catch (error) {
5866
- fail(error, mode);
6172
+ fail2(error, mode);
5867
6173
  }
5868
6174
  });
5869
6175
  }
@@ -6051,7 +6357,7 @@ function registerDoctorCommand(program2) {
6051
6357
  if (!result.ok) throw new Error(formatDoctor(result));
6052
6358
  emit(result, mode, () => formatDoctor(result));
6053
6359
  } catch (error) {
6054
- fail(error, mode);
6360
+ fail2(error, mode);
6055
6361
  }
6056
6362
  });
6057
6363
  }
@@ -6087,8 +6393,15 @@ async function fetchMe(credential, serviceUrls) {
6087
6393
  const user = data.user ?? {};
6088
6394
  const rawMemberships = user.organizationMemberships || data.organizations || [];
6089
6395
  const memberships = rawMemberships.filter((m) => m && m.id);
6090
- const activeType = user.orgId ? "organization" : "personal";
6091
- return { memberships, activeType, activeOrgId: user.orgId || void 0 };
6396
+ const availableContexts = Array.isArray(user.availableContexts) ? user.availableContexts : [];
6397
+ const activeType = user.activeContextType === "personal" ? "personal" : "organization";
6398
+ const activeOrgId = activeType === "organization" ? user.activeContextId || void 0 : void 0;
6399
+ return {
6400
+ memberships,
6401
+ activeType,
6402
+ activeOrgId,
6403
+ personalAvailable: availableContexts.some((candidate) => candidate?.type === "personal")
6404
+ };
6092
6405
  }
6093
6406
  function credentialFromTokens(tokens) {
6094
6407
  const accessToken = tokens.accessToken || tokens.access_token;
@@ -6132,7 +6445,7 @@ function hasSameAccessToken(left, right) {
6132
6445
  return leftCredential?.kind === "access_token" && rightCredential?.kind === "access_token" && leftCredential.accessToken === rightCredential.accessToken;
6133
6446
  }
6134
6447
  function identityMatchesContext(me, context) {
6135
- if (context.contextType === "personal") return me.activeType === "personal";
6448
+ if (context.contextType === "personal") return me.personalAvailable && me.activeType === "personal";
6136
6449
  return me.activeType === "organization" && me.activeOrgId === context.organizationId;
6137
6450
  }
6138
6451
  function registerContextCommand(program2) {
@@ -6147,6 +6460,7 @@ function registerContextCommand(program2) {
6147
6460
  const activeKey = profile.activeContext;
6148
6461
  const rows = /* @__PURE__ */ new Map();
6149
6462
  for (const [key, ctx] of Object.entries(stored)) {
6463
+ if (ctx.contextType === "personal") continue;
6150
6464
  rows.set(key, {
6151
6465
  key,
6152
6466
  type: ctx.contextType,
@@ -6172,15 +6486,22 @@ function registerContextCommand(program2) {
6172
6486
  if (!contextCredential) return;
6173
6487
  const identity = key === activeKey && activeIdentity ? activeIdentity : await fetchMe(contextCredential, serviceUrls);
6174
6488
  if (!identityMatchesContext(identity, ctx)) return;
6175
- const row = rows.get(key);
6176
- if (row) rows.set(key, { ...row, signedIn: true, available: true });
6489
+ const row = rows.get(key) ?? {
6490
+ key,
6491
+ type: ctx.contextType,
6492
+ label: labelFor(ctx),
6493
+ signedIn: false,
6494
+ available: false,
6495
+ active: key === activeKey
6496
+ };
6497
+ rows.set(key, { ...row, signedIn: true, available: true });
6177
6498
  } catch {
6178
6499
  }
6179
6500
  }));
6180
6501
  if (activeIdentity) {
6181
6502
  try {
6182
6503
  const personalKey = contextKey("personal");
6183
- if (!rows.has(personalKey)) {
6504
+ if (activeIdentity.personalAvailable && !rows.has(personalKey)) {
6184
6505
  rows.set(personalKey, {
6185
6506
  key: personalKey,
6186
6507
  type: "personal",
@@ -6189,7 +6510,7 @@ function registerContextCommand(program2) {
6189
6510
  available: true,
6190
6511
  active: personalKey === activeKey
6191
6512
  });
6192
- } else {
6513
+ } else if (activeIdentity.personalAvailable) {
6193
6514
  const row = rows.get(personalKey);
6194
6515
  rows.set(personalKey, { ...row, available: true });
6195
6516
  }
@@ -6227,7 +6548,7 @@ function registerContextCommand(program2) {
6227
6548
  return lines.join("\n");
6228
6549
  });
6229
6550
  } catch (err) {
6230
- fail(err, mode);
6551
+ fail2(err, mode);
6231
6552
  }
6232
6553
  });
6233
6554
  context.command("use <target>").description('Switch the active context (an organization name/slug, or "personal")').option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(async (target, opts) => {
@@ -6265,8 +6586,9 @@ function registerContextCommand(program2) {
6265
6586
  );
6266
6587
  if (!match) {
6267
6588
  const names = me.memberships.map((m) => m.slug || m.name || m.id);
6589
+ const available = [me.personalAvailable ? "personal" : "", ...names].filter(Boolean);
6268
6590
  throw new Error(
6269
- `You are not a member of "${target}". Available: personal${names.length ? ", " + names.join(", ") : ""}.`
6591
+ `You are not a member of "${target}". Available: ${available.join(", ") || "none"}.`
6270
6592
  );
6271
6593
  }
6272
6594
  resolved = {
@@ -6292,6 +6614,12 @@ function registerContextCommand(program2) {
6292
6614
  }
6293
6615
  }
6294
6616
  if (!newCredential) {
6617
+ if (resolved.contextType === "personal") {
6618
+ const identity = await fetchMe(cred, serviceUrls);
6619
+ if (!identity.personalAvailable) {
6620
+ throw new Error("Personal context is not available for this account.");
6621
+ }
6622
+ }
6295
6623
  try {
6296
6624
  newCredential = await exchangeForContext(cred, serviceUrls, {
6297
6625
  type: resolved.contextType,
@@ -6323,7 +6651,7 @@ function registerContextCommand(program2) {
6323
6651
  \u2713 Now acting as ${labelFor(resolved)} (${resolved.contextType}).`
6324
6652
  );
6325
6653
  } catch (err) {
6326
- fail(err, mode);
6654
+ fail2(err, mode);
6327
6655
  }
6328
6656
  });
6329
6657
  }
@@ -6340,7 +6668,7 @@ function registerResourcesCommand(program2) {
6340
6668
  const result = await topolo.client.listBindableResources(selector);
6341
6669
  emit(result, mode, () => formatResources(result));
6342
6670
  } catch (err) {
6343
- fail(err, mode);
6671
+ fail2(err, mode);
6344
6672
  }
6345
6673
  });
6346
6674
  }
@@ -6368,7 +6696,7 @@ function registerWorkspacesCommand(program2) {
6368
6696
  const result = await topolo.workspaces.list(application);
6369
6697
  emit(result, mode, () => formatWorkspaceList(result));
6370
6698
  } catch (error) {
6371
- fail(error, mode);
6699
+ fail2(error, mode);
6372
6700
  }
6373
6701
  });
6374
6702
  workspaces.command("create <application>").description("Create a workspace when the target application policy permits it").requiredOption("--name <name>", "Workspace display name").option("--default", "Make the new workspace the application default").option("--confirm", "Acknowledge workspace creation").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (application, opts) => {
@@ -6382,7 +6710,7 @@ function registerWorkspacesCommand(program2) {
6382
6710
  });
6383
6711
  emit(result, mode, () => formatWorkspaceMutation(result));
6384
6712
  } catch (error) {
6385
- fail(error, mode);
6713
+ fail2(error, mode);
6386
6714
  }
6387
6715
  });
6388
6716
  workspaces.command("rename <application> <workspace-id>").description("Rename a caller-authorized workspace without changing its stable id").requiredOption("--name <name>", "New workspace display name").option("--confirm", "Acknowledge workspace rename").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (application, workspaceId, opts) => {
@@ -6392,7 +6720,7 @@ function registerWorkspacesCommand(program2) {
6392
6720
  const result = await topolo.workspaces.rename(application, workspaceId, opts.name.trim(), opts.confirm === true);
6393
6721
  emit(result, mode, () => formatWorkspaceMutation(result));
6394
6722
  } catch (error) {
6395
- fail(error, mode);
6723
+ fail2(error, mode);
6396
6724
  }
6397
6725
  });
6398
6726
  workspaces.command("default <application> <workspace-id>").description("Set the default workspace for one application").option("--confirm", "Acknowledge the default-workspace change").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (application, workspaceId, opts) => {
@@ -6402,7 +6730,7 @@ function registerWorkspacesCommand(program2) {
6402
6730
  const result = await topolo.workspaces.setDefault(application, workspaceId, opts.confirm === true);
6403
6731
  emit(result, mode, () => formatWorkspaceMutation(result));
6404
6732
  } catch (error) {
6405
- fail(error, mode);
6733
+ fail2(error, mode);
6406
6734
  }
6407
6735
  });
6408
6736
  }
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@topolo/cli",
3
- "version": "0.10.0",
4
- "topoloPublishedVersion": "0.10.0",
3
+ "packageManager": "pnpm@10.17.1",
4
+ "version": "0.11.0",
5
+ "topoloPublishedVersion": "0.11.0",
5
6
  "description": "Public command-line interface for Topolo customer organizations, developers, and agents.",
6
7
  "license": "UNLICENSED",
7
8
  "author": "Topolo",
@@ -21,14 +22,14 @@
21
22
  "dev": "tsup --watch",
22
23
  "typecheck": "tsc --noEmit",
23
24
  "start": "node dist/index.js",
24
- "test": "if test \"$TOPOLO_BUILD_READY\" = 1; then vitest run; else python3 ../../tooling/with-resource-lock.py ../topolo-sdk -- sh -c 'pnpm --dir ../topolo-sdk build && exec vitest run'; fi",
25
+ "test": "vitest run",
25
26
  "test:watch": "vitest",
26
- "coverage": "if test \"$TOPOLO_BUILD_READY\" = 1; then vitest run --coverage; else python3 ../../tooling/with-resource-lock.py ../topolo-sdk -- sh -c 'pnpm --dir ../topolo-sdk build && exec vitest run --coverage'; fi"
27
+ "coverage": "vitest run --coverage"
27
28
  },
28
29
  "dependencies": {
29
30
  "@modelcontextprotocol/sdk": "^1.0.0",
30
- "@topolo/mcp": "0.11.12",
31
- "@topolo/sdk": "0.14.4",
31
+ "@topolo/mcp": "0.12.0",
32
+ "@topolo/sdk": "0.17.1",
32
33
  "commander": "^12.1.0"
33
34
  },
34
35
  "devDependencies": {
@@ -40,5 +41,9 @@
40
41
  },
41
42
  "engines": {
42
43
  "node": ">=20"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/Topolo-io/TopoloCli.git"
43
48
  }
44
49
  }