@topolo/cli 0.5.0 → 0.5.1

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 +612 -203
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -61,13 +61,13 @@ var TopoloHttpError = class extends TopoloSdkError {
61
61
  body;
62
62
  service;
63
63
  path;
64
- constructor(service, path4, status, body, message) {
65
- super("http_error", message ?? `HTTP ${status} from ${service}${path4}`);
64
+ constructor(service, path5, status, body, message) {
65
+ super("http_error", message ?? `HTTP ${status} from ${service}${path5}`);
66
66
  this.name = "TopoloHttpError";
67
67
  this.status = status;
68
68
  this.body = body;
69
69
  this.service = service;
70
- this.path = path4;
70
+ this.path = path5;
71
71
  }
72
72
  };
73
73
  var APP_API_URLS = {
@@ -2171,24 +2171,24 @@ var SUPPORTED_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
2171
2171
  "exclusiveMaximum",
2172
2172
  "multipleOf"
2173
2173
  ]);
2174
- function validateTopoloActionInput(schema, input2) {
2174
+ function validateTopoloActionInput(schema, input3) {
2175
2175
  const schemaIssue = inspectSchema(schema, "$", schema, 0);
2176
2176
  if (schemaIssue) return { valid: false, errors: [schemaIssue] };
2177
2177
  const errors = [];
2178
- validateNode(schema, input2, "$", schema, errors, 0);
2178
+ validateNode(schema, input3, "$", schema, errors, 0);
2179
2179
  return { valid: errors.length === 0, errors };
2180
2180
  }
2181
- function inspectSchema(schema, path4, root, depth) {
2182
- if (depth > 128) return { path: path4, code: "schema_depth", message: "Schema recursion exceeded 128 levels." };
2181
+ function inspectSchema(schema, path5, root, depth) {
2182
+ if (depth > 128) return { path: path5, code: "schema_depth", message: "Schema recursion exceeded 128 levels." };
2183
2183
  const unsupportedKeyword = Object.keys(schema).find((keyword) => !SUPPORTED_SCHEMA_KEYWORDS.has(keyword));
2184
2184
  if (unsupportedKeyword) {
2185
- return { path: path4, code: "unsupported_schema_keyword", message: `Action schema uses unsupported keyword ${unsupportedKeyword}.` };
2185
+ return { path: path5, code: "unsupported_schema_keyword", message: `Action schema uses unsupported keyword ${unsupportedKeyword}.` };
2186
2186
  }
2187
2187
  if (typeof schema.format === "string" && !["uri", "email", "date-time"].includes(schema.format)) {
2188
- return { path: path4, code: "unsupported_schema_format", message: `Action schema uses unsupported format ${schema.format}.` };
2188
+ return { path: path5, code: "unsupported_schema_format", message: `Action schema uses unsupported format ${schema.format}.` };
2189
2189
  }
2190
2190
  if (typeof schema.$ref === "string" && !resolveLocalReference(root, schema.$ref)) {
2191
- return { path: path4, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
2191
+ return { path: path5, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
2192
2192
  }
2193
2193
  const children = [];
2194
2194
  if (isRecord(schema.properties)) children.push(...Object.entries(schema.properties));
@@ -2204,7 +2204,7 @@ function inspectSchema(schema, path4, root, depth) {
2204
2204
  );
2205
2205
  for (const [key, child] of children) {
2206
2206
  if (!isRecord(child)) continue;
2207
- const issue = inspectSchema(child, `${path4}.${key}`, root, depth + 1);
2207
+ const issue = inspectSchema(child, `${path5}.${key}`, root, depth + 1);
2208
2208
  if (issue) return issue;
2209
2209
  }
2210
2210
  return null;
@@ -2212,129 +2212,129 @@ function inspectSchema(schema, path4, root, depth) {
2212
2212
  function arraySchemaEntries(keyword, value) {
2213
2213
  return Array.isArray(value) ? value.map((candidate, index) => [`${keyword}[${index}]`, candidate]) : [];
2214
2214
  }
2215
- function validateNode(schema, value, path4, root, errors, depth) {
2215
+ function validateNode(schema, value, path5, root, errors, depth) {
2216
2216
  if (depth > 128) {
2217
- errors.push({ path: path4, code: "schema_depth", message: "Schema recursion exceeded 128 levels." });
2217
+ errors.push({ path: path5, code: "schema_depth", message: "Schema recursion exceeded 128 levels." });
2218
2218
  return;
2219
2219
  }
2220
2220
  if (typeof schema.$ref === "string") {
2221
2221
  const resolved = resolveLocalReference(root, schema.$ref);
2222
2222
  if (!resolved) {
2223
- errors.push({ path: path4, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` });
2223
+ errors.push({ path: path5, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` });
2224
2224
  return;
2225
2225
  }
2226
- validateNode(resolved, value, path4, root, errors, depth + 1);
2226
+ validateNode(resolved, value, path5, root, errors, depth + 1);
2227
2227
  }
2228
2228
  if (Array.isArray(schema.allOf)) {
2229
2229
  for (const candidate of schema.allOf) {
2230
- if (isRecord(candidate)) validateNode(candidate, value, path4, root, errors, depth + 1);
2230
+ if (isRecord(candidate)) validateNode(candidate, value, path5, root, errors, depth + 1);
2231
2231
  }
2232
2232
  }
2233
2233
  if (Array.isArray(schema.anyOf) && !schema.anyOf.some((candidate) => candidateMatches(candidate, value, root, depth))) {
2234
- errors.push({ path: path4, code: "any_of", message: "Value does not match any allowed schema." });
2234
+ errors.push({ path: path5, code: "any_of", message: "Value does not match any allowed schema." });
2235
2235
  return;
2236
2236
  }
2237
2237
  if (Array.isArray(schema.oneOf)) {
2238
2238
  const matches = schema.oneOf.filter((candidate) => candidateMatches(candidate, value, root, depth)).length;
2239
2239
  if (matches !== 1) {
2240
- errors.push({ path: path4, code: "one_of", message: "Value must match exactly one allowed schema." });
2240
+ errors.push({ path: path5, code: "one_of", message: "Value must match exactly one allowed schema." });
2241
2241
  return;
2242
2242
  }
2243
2243
  }
2244
2244
  if (value === null && schema.nullable === true) return;
2245
2245
  if (Object.prototype.hasOwnProperty.call(schema, "const") && !sameJsonValue(value, schema.const)) {
2246
- errors.push({ path: path4, code: "const", message: `Value must equal ${JSON.stringify(schema.const)}.` });
2246
+ errors.push({ path: path5, code: "const", message: `Value must equal ${JSON.stringify(schema.const)}.` });
2247
2247
  return;
2248
2248
  }
2249
2249
  if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => sameJsonValue(value, candidate))) {
2250
- errors.push({ path: path4, code: "enum", message: `Value must be one of ${schema.enum.map((item) => JSON.stringify(item)).join(", ")}.` });
2250
+ errors.push({ path: path5, code: "enum", message: `Value must be one of ${schema.enum.map((item) => JSON.stringify(item)).join(", ")}.` });
2251
2251
  return;
2252
2252
  }
2253
2253
  if (!matchesType(schema.type, value)) {
2254
- errors.push({ path: path4, code: "type", message: `Expected ${String(schema.type)}.` });
2254
+ errors.push({ path: path5, code: "type", message: `Expected ${String(schema.type)}.` });
2255
2255
  return;
2256
2256
  }
2257
- if (typeof value === "string") validateString(schema, value, path4, errors);
2258
- if (typeof value === "number") validateNumber(schema, value, path4, errors);
2259
- if (Array.isArray(value)) validateArray(schema, value, path4, root, errors, depth);
2260
- if (isRecord(value)) validateObject(schema, value, path4, root, errors, depth);
2257
+ if (typeof value === "string") validateString(schema, value, path5, errors);
2258
+ if (typeof value === "number") validateNumber(schema, value, path5, errors);
2259
+ if (Array.isArray(value)) validateArray(schema, value, path5, root, errors, depth);
2260
+ if (isRecord(value)) validateObject(schema, value, path5, root, errors, depth);
2261
2261
  }
2262
- function validateObject(schema, value, path4, root, errors, depth) {
2262
+ function validateObject(schema, value, path5, root, errors, depth) {
2263
2263
  if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
2264
- errors.push({ path: path4, code: "min_properties", message: `Expected at least ${schema.minProperties} properties.` });
2264
+ errors.push({ path: path5, code: "min_properties", message: `Expected at least ${schema.minProperties} properties.` });
2265
2265
  }
2266
2266
  if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
2267
- errors.push({ path: path4, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
2267
+ errors.push({ path: path5, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
2268
2268
  }
2269
2269
  const properties = isRecord(schema.properties) ? schema.properties : {};
2270
2270
  const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
2271
2271
  for (const key of required) {
2272
2272
  if (!Object.prototype.hasOwnProperty.call(value, key)) {
2273
- errors.push({ path: `${path4}.${key}`, code: "required", message: "Required property is missing." });
2273
+ errors.push({ path: `${path5}.${key}`, code: "required", message: "Required property is missing." });
2274
2274
  }
2275
2275
  }
2276
2276
  if (isRecord(schema.propertyNames)) {
2277
2277
  for (const key of Object.keys(value)) {
2278
- validateNode(schema.propertyNames, key, `${path4}.${key}`, root, errors, depth + 1);
2278
+ validateNode(schema.propertyNames, key, `${path5}.${key}`, root, errors, depth + 1);
2279
2279
  }
2280
2280
  }
2281
2281
  for (const [key, child] of Object.entries(value)) {
2282
2282
  const childSchema = properties[key];
2283
2283
  if (isRecord(childSchema)) {
2284
- validateNode(childSchema, child, `${path4}.${key}`, root, errors, depth + 1);
2284
+ validateNode(childSchema, child, `${path5}.${key}`, root, errors, depth + 1);
2285
2285
  } else if (schema.additionalProperties === false) {
2286
- errors.push({ path: `${path4}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
2286
+ errors.push({ path: `${path5}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
2287
2287
  } else if (isRecord(schema.additionalProperties)) {
2288
- validateNode(schema.additionalProperties, child, `${path4}.${key}`, root, errors, depth + 1);
2288
+ validateNode(schema.additionalProperties, child, `${path5}.${key}`, root, errors, depth + 1);
2289
2289
  }
2290
2290
  }
2291
2291
  }
2292
- function validateArray(schema, value, path4, root, errors, depth) {
2292
+ function validateArray(schema, value, path5, root, errors, depth) {
2293
2293
  if (typeof schema.minItems === "number" && value.length < schema.minItems) {
2294
- errors.push({ path: path4, code: "min_items", message: `Expected at least ${schema.minItems} items.` });
2294
+ errors.push({ path: path5, code: "min_items", message: `Expected at least ${schema.minItems} items.` });
2295
2295
  }
2296
2296
  if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
2297
- errors.push({ path: path4, code: "max_items", message: `Expected at most ${schema.maxItems} items.` });
2297
+ errors.push({ path: path5, code: "max_items", message: `Expected at most ${schema.maxItems} items.` });
2298
2298
  }
2299
2299
  if (schema.uniqueItems === true) {
2300
2300
  const unique2 = new Set(value.map((item) => JSON.stringify(item)));
2301
- if (unique2.size !== value.length) errors.push({ path: path4, code: "unique_items", message: "Expected every item to be unique." });
2301
+ if (unique2.size !== value.length) errors.push({ path: path5, code: "unique_items", message: "Expected every item to be unique." });
2302
2302
  }
2303
2303
  if (isRecord(schema.items)) {
2304
- value.forEach((item, index) => validateNode(schema.items, item, `${path4}[${index}]`, root, errors, depth + 1));
2304
+ value.forEach((item, index) => validateNode(schema.items, item, `${path5}[${index}]`, root, errors, depth + 1));
2305
2305
  }
2306
2306
  }
2307
- function validateString(schema, value, path4, errors) {
2307
+ function validateString(schema, value, path5, errors) {
2308
2308
  if (typeof schema.minLength === "number" && value.length < schema.minLength) {
2309
- errors.push({ path: path4, code: "min_length", message: `Expected at least ${schema.minLength} characters.` });
2309
+ errors.push({ path: path5, code: "min_length", message: `Expected at least ${schema.minLength} characters.` });
2310
2310
  }
2311
2311
  if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
2312
- errors.push({ path: path4, code: "max_length", message: `Expected at most ${schema.maxLength} characters.` });
2312
+ errors.push({ path: path5, code: "max_length", message: `Expected at most ${schema.maxLength} characters.` });
2313
2313
  }
2314
2314
  if (typeof schema.pattern === "string") {
2315
2315
  try {
2316
- if (!new RegExp(schema.pattern).test(value)) errors.push({ path: path4, code: "pattern", message: "Value does not match the required pattern." });
2316
+ if (!new RegExp(schema.pattern).test(value)) errors.push({ path: path5, code: "pattern", message: "Value does not match the required pattern." });
2317
2317
  } catch {
2318
- errors.push({ path: path4, code: "invalid_schema_pattern", message: "Action schema contains an invalid pattern." });
2318
+ errors.push({ path: path5, code: "invalid_schema_pattern", message: "Action schema contains an invalid pattern." });
2319
2319
  }
2320
2320
  }
2321
- if (schema.format === "uri" && !isUrl(value)) errors.push({ path: path4, code: "format_uri", message: "Expected an absolute URL." });
2322
- if (schema.format === "email" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) errors.push({ path: path4, code: "format_email", message: "Expected an email address." });
2323
- if (schema.format === "date-time" && !isDateTime(value)) errors.push({ path: path4, code: "format_date_time", message: "Expected an ISO-8601 date-time." });
2321
+ if (schema.format === "uri" && !isUrl(value)) errors.push({ path: path5, code: "format_uri", message: "Expected an absolute URL." });
2322
+ if (schema.format === "email" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) errors.push({ path: path5, code: "format_email", message: "Expected an email address." });
2323
+ if (schema.format === "date-time" && !isDateTime(value)) errors.push({ path: path5, code: "format_date_time", message: "Expected an ISO-8601 date-time." });
2324
2324
  }
2325
- function validateNumber(schema, value, path4, errors) {
2326
- if (typeof schema.minimum === "number" && value < schema.minimum) errors.push({ path: path4, code: "minimum", message: `Expected a value of at least ${schema.minimum}.` });
2327
- if (typeof schema.maximum === "number" && value > schema.maximum) errors.push({ path: path4, code: "maximum", message: `Expected a value of at most ${schema.maximum}.` });
2325
+ function validateNumber(schema, value, path5, errors) {
2326
+ if (typeof schema.minimum === "number" && value < schema.minimum) errors.push({ path: path5, code: "minimum", message: `Expected a value of at least ${schema.minimum}.` });
2327
+ if (typeof schema.maximum === "number" && value > schema.maximum) errors.push({ path: path5, code: "maximum", message: `Expected a value of at most ${schema.maximum}.` });
2328
2328
  const exclusiveMinimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.exclusiveMinimum === true && typeof schema.minimum === "number" ? schema.minimum : null;
2329
2329
  const exclusiveMaximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.exclusiveMaximum === true && typeof schema.maximum === "number" ? schema.maximum : null;
2330
2330
  if (exclusiveMinimum !== null && value <= exclusiveMinimum) {
2331
- errors.push({ path: path4, code: "exclusive_minimum", message: `Expected a value greater than ${exclusiveMinimum}.` });
2331
+ errors.push({ path: path5, code: "exclusive_minimum", message: `Expected a value greater than ${exclusiveMinimum}.` });
2332
2332
  }
2333
2333
  if (exclusiveMaximum !== null && value >= exclusiveMaximum) {
2334
- errors.push({ path: path4, code: "exclusive_maximum", message: `Expected a value less than ${exclusiveMaximum}.` });
2334
+ errors.push({ path: path5, code: "exclusive_maximum", message: `Expected a value less than ${exclusiveMaximum}.` });
2335
2335
  }
2336
2336
  if (typeof schema.multipleOf === "number" && !isMultipleOf(value, schema.multipleOf)) {
2337
- errors.push({ path: path4, code: "multiple_of", message: `Expected a multiple of ${schema.multipleOf}.` });
2337
+ errors.push({ path: path5, code: "multiple_of", message: `Expected a multiple of ${schema.multipleOf}.` });
2338
2338
  }
2339
2339
  }
2340
2340
  function candidateMatches(candidate, value, root, depth) {
@@ -2845,14 +2845,14 @@ var TopoloClient = class _TopoloClient {
2845
2845
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
2846
2846
  throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is unavailable.");
2847
2847
  }
2848
- const record = raw;
2849
- if (record.algorithm !== "sha256-v1" || typeof record.digest !== "string" || !/^[a-f0-9]{64}$/.test(record.digest) || !Number.isSafeInteger(record.actionCount) || Number(record.actionCount) < 0) {
2848
+ const record2 = raw;
2849
+ if (record2.algorithm !== "sha256-v1" || typeof record2.digest !== "string" || !/^[a-f0-9]{64}$/.test(record2.digest) || !Number.isSafeInteger(record2.actionCount) || Number(record2.actionCount) < 0) {
2850
2850
  throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is invalid.");
2851
2851
  }
2852
2852
  return {
2853
2853
  algorithm: "sha256-v1",
2854
- digest: record.digest,
2855
- actionCount: Number(record.actionCount)
2854
+ digest: record2.digest,
2855
+ actionCount: Number(record2.actionCount)
2856
2856
  };
2857
2857
  }
2858
2858
  /** @deprecated Prefer searchActions() so callers retain the continuation cursor. */
@@ -2889,22 +2889,22 @@ var TopoloClient = class _TopoloClient {
2889
2889
  if (!match) throw new TopoloSdkError("unknown_action", `Unknown or unavailable Topolo action "${action}".`);
2890
2890
  return match;
2891
2891
  }
2892
- async callAction(action, input2 = {}, options = {}) {
2892
+ async callAction(action, input3 = {}, options = {}) {
2893
2893
  const entry = await this.getAction(action);
2894
- return this.executeResolvedAction(entry, input2, options);
2894
+ return this.executeResolvedAction(entry, input3, options);
2895
2895
  }
2896
- async validateActionInput(action, input2 = {}) {
2896
+ async validateActionInput(action, input3 = {}) {
2897
2897
  const entry = await this.getAction(action);
2898
- return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input2) };
2898
+ return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input3) };
2899
2899
  }
2900
- async planAction(action, input2 = {}, options = {}) {
2900
+ async planAction(action, input3 = {}, options = {}) {
2901
2901
  const entry = await this.getAction(action);
2902
- const validation = validateTopoloActionInput(entry.inputSchema, input2);
2902
+ const validation = validateTopoloActionInput(entry.inputSchema, input3);
2903
2903
  const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
2904
2904
  const confirmationRequired = agentAccess === "confirm" || entry.requiresConfirmation || entry.destructive;
2905
2905
  let request = null;
2906
2906
  if (validation.valid) {
2907
- const mapped = mapActionInput(entry, input2);
2907
+ const mapped = mapActionInput(entry, input3);
2908
2908
  request = {
2909
2909
  method: entry.method,
2910
2910
  service: entry.appSlug || entry.appId,
@@ -2927,7 +2927,7 @@ var TopoloClient = class _TopoloClient {
2927
2927
  nextActionIds: stringArray(entry.agentContract?.nextActionIds)
2928
2928
  };
2929
2929
  }
2930
- async readAction(action, input2 = {}, options = {}) {
2930
+ async readAction(action, input3 = {}, options = {}) {
2931
2931
  const entry = await this.getAction(action);
2932
2932
  const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
2933
2933
  if (!entry.readOnly || entry.destructive || entry.requiresConfirmation || agentAccess !== "auto") {
@@ -2936,9 +2936,9 @@ var TopoloClient = class _TopoloClient {
2936
2936
  entry.requiredPermission ? [entry.requiredPermission] : []
2937
2937
  );
2938
2938
  }
2939
- return this.executeResolvedAction(entry, input2, options);
2939
+ return this.executeResolvedAction(entry, input3, options);
2940
2940
  }
2941
- async executeResolvedAction(entry, input2, options) {
2941
+ async executeResolvedAction(entry, input3, options) {
2942
2942
  const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
2943
2943
  if (agentAccess === "off") {
2944
2944
  throw new TopoloPermissionError(
@@ -2951,14 +2951,14 @@ var TopoloClient = class _TopoloClient {
2951
2951
  `Topolo action "${entry.actionId}" requires { confirm: true } before invocation.`
2952
2952
  );
2953
2953
  }
2954
- const validation = validateTopoloActionInput(entry.inputSchema, input2);
2954
+ const validation = validateTopoloActionInput(entry.inputSchema, input3);
2955
2955
  if (!validation.valid) {
2956
2956
  throw new TopoloSdkError(
2957
2957
  "invalid_action_input",
2958
2958
  `Topolo action "${entry.actionId}" input is invalid: ${validation.errors.map((issue) => `${issue.path} ${issue.message}`).join("; ")}`
2959
2959
  );
2960
2960
  }
2961
- const mapped = mapActionInput(entry, input2);
2961
+ const mapped = mapActionInput(entry, input3);
2962
2962
  const service = entry.appSlug || entry.appId;
2963
2963
  return this.request({
2964
2964
  service,
@@ -3050,15 +3050,15 @@ function resolveConfiguredAppId(slug, appIds) {
3050
3050
  }
3051
3051
  return null;
3052
3052
  }
3053
- function normalizeApiKeyResourceMetadata(input2) {
3054
- if (!isRecord2(input2)) return null;
3055
- const resourceType = readString(input2.resourceType ?? input2.resource_type);
3056
- const label = readString(input2.label);
3057
- const emptyLabel = readString(input2.emptyLabel ?? input2.empty_label);
3058
- const resourceConcept = readString(input2.resourceConcept ?? input2.resource_concept);
3059
- const canonicalResourceType = readString(input2.canonicalResourceType ?? input2.canonical_resource_type);
3060
- const defaultEntitlement = readString(input2.defaultEntitlement ?? input2.default_entitlement);
3061
- const exceptionPolicy = readString(input2.exceptionPolicy ?? input2.exception_policy);
3053
+ function normalizeApiKeyResourceMetadata(input3) {
3054
+ if (!isRecord2(input3)) return null;
3055
+ const resourceType = readString(input3.resourceType ?? input3.resource_type);
3056
+ const label = readString(input3.label);
3057
+ const emptyLabel = readString(input3.emptyLabel ?? input3.empty_label);
3058
+ const resourceConcept = readString(input3.resourceConcept ?? input3.resource_concept);
3059
+ const canonicalResourceType = readString(input3.canonicalResourceType ?? input3.canonical_resource_type);
3060
+ const defaultEntitlement = readString(input3.defaultEntitlement ?? input3.default_entitlement);
3061
+ const exceptionPolicy = readString(input3.exceptionPolicy ?? input3.exception_policy);
3062
3062
  if (!resourceType || !label || !emptyLabel || resourceConcept !== "workspace" || canonicalResourceType !== "workspace" || defaultEntitlement !== "enterprise" || exceptionPolicy !== "app_or_organization") {
3063
3063
  return null;
3064
3064
  }
@@ -3072,11 +3072,11 @@ function normalizeApiKeyResourceMetadata(input2) {
3072
3072
  exceptionPolicy
3073
3073
  };
3074
3074
  }
3075
- function normalizeApiKeyResourceMetadataList(input2) {
3076
- if (!Array.isArray(input2)) return [];
3075
+ function normalizeApiKeyResourceMetadataList(input3) {
3076
+ if (!Array.isArray(input3)) return [];
3077
3077
  const resources = [];
3078
3078
  const seenTypes = /* @__PURE__ */ new Set();
3079
- for (const item of input2) {
3079
+ for (const item of input3) {
3080
3080
  const resource = normalizeApiKeyResourceMetadata(item);
3081
3081
  if (!resource || seenTypes.has(resource.resourceType)) continue;
3082
3082
  seenTypes.add(resource.resourceType);
@@ -3084,24 +3084,24 @@ function normalizeApiKeyResourceMetadataList(input2) {
3084
3084
  }
3085
3085
  return resources;
3086
3086
  }
3087
- function normalizeBindableResource(input2) {
3088
- if (!isRecord2(input2)) return null;
3089
- const resourceType = readString(input2.resourceType ?? input2.resource_type);
3090
- const resourceId = readString(input2.resourceId ?? input2.resource_id);
3087
+ function normalizeBindableResource(input3) {
3088
+ if (!isRecord2(input3)) return null;
3089
+ const resourceType = readString(input3.resourceType ?? input3.resource_type);
3090
+ const resourceId = readString(input3.resourceId ?? input3.resource_id);
3091
3091
  if (!resourceType || !resourceId) return null;
3092
3092
  return {
3093
3093
  resourceType,
3094
3094
  resourceId,
3095
- label: readOptionalString(input2.label),
3096
- slug: readOptionalString(input2.slug),
3097
- status: readOptionalString(input2.status),
3098
- organizationId: readOptionalString(input2.organizationId ?? input2.organization_id),
3099
- metadata: isRecord2(input2.metadata) ? input2.metadata : {}
3095
+ label: readOptionalString(input3.label),
3096
+ slug: readOptionalString(input3.slug),
3097
+ status: readOptionalString(input3.status),
3098
+ organizationId: readOptionalString(input3.organizationId ?? input3.organization_id),
3099
+ metadata: isRecord2(input3.metadata) ? input3.metadata : {}
3100
3100
  };
3101
3101
  }
3102
- function normalizeResourceBindings(input2) {
3103
- if (!Array.isArray(input2)) return [];
3104
- return input2.flatMap((binding) => {
3102
+ function normalizeResourceBindings(input3) {
3103
+ if (!Array.isArray(input3)) return [];
3104
+ return input3.flatMap((binding) => {
3105
3105
  if (!isRecord2(binding)) return [];
3106
3106
  const resourceType = readString(binding.resourceType ?? binding.resource_type);
3107
3107
  const resourceId = readString(binding.resourceId ?? binding.resource_id);
@@ -3227,8 +3227,8 @@ function normalizeActionCatalogEntry(item) {
3227
3227
  const name = pickActionString(a.name);
3228
3228
  const appId = pickActionString(a.app_id) ?? pickActionString(a.appId);
3229
3229
  const method = pickActionString(a.method)?.toUpperCase();
3230
- const path4 = pickActionString(a.path);
3231
- if (!actionId || !name || !appId || !method || !path4 || !isHttpMethod(method)) {
3230
+ const path5 = pickActionString(a.path);
3231
+ if (!actionId || !name || !appId || !method || !path5 || !isHttpMethod(method)) {
3232
3232
  return null;
3233
3233
  }
3234
3234
  return {
@@ -3243,7 +3243,7 @@ function normalizeActionCatalogEntry(item) {
3243
3243
  appSlug: pickActionString(a.app_slug) ?? pickActionString(a.appSlug) ?? null,
3244
3244
  appName: pickActionString(a.app_name) ?? pickActionString(a.appName) ?? null,
3245
3245
  method,
3246
- path: path4,
3246
+ path: path5,
3247
3247
  permissionName: pickActionString(a.permission_name) ?? pickActionString(a.permissionName) ?? "",
3248
3248
  requiredPermission: pickActionString(a.required_permission) ?? pickActionString(a.requiredPermission) ?? "",
3249
3249
  authorizationMode: pickAuthorizationMode(a.authorization_mode ?? a.authorizationMode),
@@ -3277,9 +3277,9 @@ function plainObject(value) {
3277
3277
  function stringArray(value) {
3278
3278
  return Array.isArray(value) ? value.map((item) => String(item || "").trim()).filter(Boolean) : [];
3279
3279
  }
3280
- function mapActionInput(action, input2) {
3281
- const remaining = { ...input2 || {} };
3282
- const path4 = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
3280
+ function mapActionInput(action, input3) {
3281
+ const remaining = { ...input3 || {} };
3282
+ const path5 = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
3283
3283
  const value = remaining[key];
3284
3284
  if (value === void 0 || value === null || value === "") {
3285
3285
  throw new TopoloSdkError(
@@ -3296,14 +3296,14 @@ function mapActionInput(action, input2) {
3296
3296
  delete remaining.body;
3297
3297
  if (action.method === "GET") {
3298
3298
  return {
3299
- path: path4,
3299
+ path: path5,
3300
3300
  query: objectToQuery({ ...remaining, ...explicitQuery })
3301
3301
  };
3302
3302
  }
3303
3303
  const query = objectToQuery(explicitQuery);
3304
3304
  const body = explicitBody !== void 0 ? explicitBody : remaining;
3305
3305
  return {
3306
- path: path4,
3306
+ path: path5,
3307
3307
  ...query ? { query } : {},
3308
3308
  body
3309
3309
  };
@@ -3473,8 +3473,8 @@ var TopoloOAuth = class {
3473
3473
  if (params.clientSecret) body.set("client_secret", params.clientSecret);
3474
3474
  return this.#postForm("/api/developer-oauth/token", body);
3475
3475
  }
3476
- async #postForm(path4, body) {
3477
- const url = new URL(path4, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3476
+ async #postForm(path5, body) {
3477
+ const url = new URL(path5, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3478
3478
  const res = await this.fetchImpl(url.toString(), {
3479
3479
  method: "POST",
3480
3480
  headers: {
@@ -3490,7 +3490,7 @@ var TopoloOAuth = class {
3490
3490
  const err = parsed;
3491
3491
  throw new TopoloAuthError(err.error_description || err.error, err.error);
3492
3492
  }
3493
- throw new TopoloHttpError("auth", path4, res.status, parsed);
3493
+ throw new TopoloHttpError("auth", path5, res.status, parsed);
3494
3494
  }
3495
3495
  return parsed;
3496
3496
  }
@@ -3528,13 +3528,13 @@ var TopoloHttpError2 = class extends TopoloSdkError2 {
3528
3528
  body;
3529
3529
  service;
3530
3530
  path;
3531
- constructor(service, path4, status, body, message) {
3532
- super("http_error", message ?? `HTTP ${status} from ${service}${path4}`);
3531
+ constructor(service, path5, status, body, message) {
3532
+ super("http_error", message ?? `HTTP ${status} from ${service}${path5}`);
3533
3533
  this.name = "TopoloHttpError";
3534
3534
  this.status = status;
3535
3535
  this.body = body;
3536
3536
  this.service = service;
3537
- this.path = path4;
3537
+ this.path = path5;
3538
3538
  }
3539
3539
  };
3540
3540
  var APP_API_URLS2 = {
@@ -3665,8 +3665,8 @@ var TopoloOAuth2 = class {
3665
3665
  if (params.clientSecret) body.set("client_secret", params.clientSecret);
3666
3666
  return this.#postForm("/api/developer-oauth/token", body);
3667
3667
  }
3668
- async #postForm(path4, body) {
3669
- const url = new URL(path4, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3668
+ async #postForm(path5, body) {
3669
+ const url = new URL(path5, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3670
3670
  const res = await this.fetchImpl(url.toString(), {
3671
3671
  method: "POST",
3672
3672
  headers: {
@@ -3682,7 +3682,7 @@ var TopoloOAuth2 = class {
3682
3682
  const err = parsed;
3683
3683
  throw new TopoloAuthError2(err.error_description || err.error, err.error);
3684
3684
  }
3685
- throw new TopoloHttpError2("auth", path4, res.status, parsed);
3685
+ throw new TopoloHttpError2("auth", path5, res.status, parsed);
3686
3686
  }
3687
3687
  return parsed;
3688
3688
  }
@@ -3712,22 +3712,22 @@ function configPath() {
3712
3712
  return join(base, "topolo", "config.json");
3713
3713
  }
3714
3714
  async function readConfig() {
3715
- const path4 = configPath();
3716
- if (!existsSync(path4)) return {};
3715
+ const path5 = configPath();
3716
+ if (!existsSync(path5)) return {};
3717
3717
  try {
3718
- const raw = await readFile(path4, "utf8");
3718
+ const raw = await readFile(path5, "utf8");
3719
3719
  return JSON.parse(raw);
3720
3720
  } catch {
3721
3721
  return {};
3722
3722
  }
3723
3723
  }
3724
3724
  async function writeConfig(config) {
3725
- const path4 = configPath();
3726
- await mkdir(dirname(path4), { recursive: true });
3727
- await writeFile(path4, JSON.stringify(config, null, 2) + "\n", "utf8");
3725
+ const path5 = configPath();
3726
+ await mkdir(dirname(path5), { recursive: true });
3727
+ await writeFile(path5, JSON.stringify(config, null, 2) + "\n", "utf8");
3728
3728
  if (platform() !== "win32") {
3729
3729
  try {
3730
- await chmod(path4, 384);
3730
+ await chmod(path5, 384);
3731
3731
  } catch {
3732
3732
  }
3733
3733
  }
@@ -4023,13 +4023,14 @@ function normalizeError(err) {
4023
4023
  const hintMap = {
4024
4024
  auth_error: "Run `topolo auth login` or set TOPOLO_API_KEY.",
4025
4025
  confirmation_required: "Review the action and rerun with --confirm to allow it.",
4026
- permission_denied: "Check your API key scopes or role assignments in TopoloDevelopers."
4026
+ permission_denied: "Check your API key scopes or role assignments in TopoloDevelopers.",
4027
+ immutable_drift: "Restore the manifest identity fields to the registered values; immutable ids cannot be updated."
4027
4028
  };
4028
4029
  return {
4029
4030
  message: err.message,
4030
4031
  code,
4031
4032
  hint: code ? hintMap[code] : void 0,
4032
- exitCode: code === "auth_error" || code === "confirmation_required" ? 2 : code === "permission_denied" ? 3 : 1
4033
+ exitCode: code === "auth_error" || code === "confirmation_required" ? 2 : code === "permission_denied" ? 3 : code === "immutable_drift" ? 4 : 1
4033
4034
  };
4034
4035
  }
4035
4036
  return { message: String(err), exitCode: 1 };
@@ -4046,9 +4047,9 @@ function readCliVersion() {
4046
4047
  join2(here, "package.json"),
4047
4048
  join2(here, "..", "package.json")
4048
4049
  ];
4049
- for (const path4 of candidates) {
4050
+ for (const path5 of candidates) {
4050
4051
  try {
4051
- const pkg = JSON.parse(readFileSync(path4, "utf8"));
4052
+ const pkg = JSON.parse(readFileSync(path5, "utf8"));
4052
4053
  if (typeof pkg.version === "string" && pkg.version) return pkg.version;
4053
4054
  } catch {
4054
4055
  }
@@ -4407,7 +4408,7 @@ function registerApiCommand(program2) {
4407
4408
  "-q, --query <kv...>",
4408
4409
  "Query params as key=value (repeatable), e.g. --query source=cli page=2"
4409
4410
  ).option("--resource-type <type>", "Cataloged app-local workspace resource alias for resource-scoped API keys").option("--resource-id <id>", "Workspace resource id for resource-scoped API keys").option("--confirm", "Acknowledge a mutating request").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
4410
- async (service, method, path4, opts) => {
4411
+ async (service, method, path5, opts) => {
4411
4412
  const mode = resolveOutputMode(opts.json);
4412
4413
  try {
4413
4414
  const upperMethod = method.toUpperCase();
@@ -4446,7 +4447,7 @@ function registerApiCommand(program2) {
4446
4447
  const result = await topolo.client.request({
4447
4448
  service,
4448
4449
  method: httpMethod,
4449
- path: path4,
4450
+ path: path5,
4450
4451
  body,
4451
4452
  ...query ? { query } : {},
4452
4453
  ...resource ? { resource } : {},
@@ -4539,9 +4540,9 @@ function registerActionsCommand(program2) {
4539
4540
  if (!topolo) {
4540
4541
  throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
4541
4542
  }
4542
- const input2 = parseJsonObject(opts.data || "{}");
4543
+ const input3 = parseJsonObject(opts.data || "{}");
4543
4544
  const resource = parseResourceOption(opts.resourceType, opts.resourceId);
4544
- const result = await topolo.client.callAction(action, input2, {
4545
+ const result = await topolo.client.callAction(action, input3, {
4545
4546
  confirm: opts.confirm === true,
4546
4547
  ...resource ? { resource } : {}
4547
4548
  });
@@ -4610,13 +4611,13 @@ async function requiredClient() {
4610
4611
  function parseJsonObject(value) {
4611
4612
  let source = value;
4612
4613
  if (value.startsWith("@")) {
4613
- const path4 = value.slice(1);
4614
- if (!path4) throw new Error("--data @file path must not be empty");
4614
+ const path5 = value.slice(1);
4615
+ if (!path5) throw new Error("--data @file path must not be empty");
4615
4616
  try {
4616
- source = readFileSync2(path4, "utf8");
4617
+ source = readFileSync2(path5, "utf8");
4617
4618
  } catch (err) {
4618
4619
  const detail = err instanceof Error ? err.message : String(err);
4619
- throw new Error(`Could not read --data file ${path4}: ${detail}`);
4620
+ throw new Error(`Could not read --data file ${path5}: ${detail}`);
4620
4621
  }
4621
4622
  }
4622
4623
  let parsed;
@@ -4691,7 +4692,7 @@ function formatActionPlan(plan) {
4691
4692
  // src/commands/apps.ts
4692
4693
  import { execSync } from "child_process";
4693
4694
  import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
4694
- import path2 from "path";
4695
+ import path3 from "path";
4695
4696
 
4696
4697
  // src/commands/app-audit-local.ts
4697
4698
  import {
@@ -5282,8 +5283,8 @@ function enrichFinding(application, finding2, evidence) {
5282
5283
  evidence: verificationEvidence
5283
5284
  });
5284
5285
  }
5285
- const usesNodeCheck = evidence.packageJsons.some((record) => {
5286
- const scripts = record.packageJson.scripts ?? {};
5286
+ const usesNodeCheck = evidence.packageJsons.some((record2) => {
5287
+ const scripts = record2.packageJson.scripts ?? {};
5287
5288
  return Object.values(scripts).some((cmd) => typeof cmd === "string" && /node\s+--check/.test(cmd));
5288
5289
  });
5289
5290
  if (hasBuild && usesNodeCheck) {
@@ -5549,35 +5550,35 @@ function verificationEvidenceFor(evidence) {
5549
5550
  "test:auth-contract": "test",
5550
5551
  validate: "validate"
5551
5552
  };
5552
- for (const record of evidence.packageJsons) {
5553
- const scripts = record.packageJson.scripts ?? {};
5554
- const isRootPackage = record.path === evidence.packageJsonPath;
5553
+ for (const record2 of evidence.packageJsons) {
5554
+ const scripts = record2.packageJson.scripts ?? {};
5555
+ const isRootPackage = record2.path === evidence.packageJsonPath;
5555
5556
  const seenCanonical = /* @__PURE__ */ new Set();
5556
5557
  for (const [scriptName, canonical] of Object.entries(scriptAliases)) {
5557
5558
  if (!scripts[scriptName]) continue;
5558
5559
  if (seenCanonical.has(canonical)) continue;
5559
5560
  seenCanonical.add(canonical);
5560
- items.push(isRootPackage ? `script=${canonical}` : `script=${packageLabel(record)}:${canonical}`);
5561
+ items.push(isRootPackage ? `script=${canonical}` : `script=${packageLabel(record2)}:${canonical}`);
5561
5562
  }
5562
5563
  }
5563
5564
  return unique(items);
5564
5565
  }
5565
5566
  function dependencyNames(evidence) {
5566
5567
  return new Set(
5567
- evidence.packageJsons.flatMap((record) => [
5568
- ...Object.keys(record.packageJson.dependencies ?? {}),
5569
- ...Object.keys(record.packageJson.devDependencies ?? {}),
5570
- ...Object.keys(record.packageJson.peerDependencies ?? {})
5568
+ evidence.packageJsons.flatMap((record2) => [
5569
+ ...Object.keys(record2.packageJson.dependencies ?? {}),
5570
+ ...Object.keys(record2.packageJson.devDependencies ?? {}),
5571
+ ...Object.keys(record2.packageJson.peerDependencies ?? {})
5571
5572
  ])
5572
5573
  );
5573
5574
  }
5574
5575
  function packageDependencyVersions(evidence, packageName) {
5575
5576
  return unique(
5576
- evidence.packageJsons.flatMap((record) => {
5577
+ evidence.packageJsons.flatMap((record2) => {
5577
5578
  const specs = [
5578
- record.packageJson.dependencies?.[packageName],
5579
- record.packageJson.devDependencies?.[packageName],
5580
- record.packageJson.peerDependencies?.[packageName]
5579
+ record2.packageJson.dependencies?.[packageName],
5580
+ record2.packageJson.devDependencies?.[packageName],
5581
+ record2.packageJson.peerDependencies?.[packageName]
5581
5582
  ];
5582
5583
  return specs.filter((spec) => typeof spec === "string");
5583
5584
  })
@@ -5703,8 +5704,8 @@ function collectPackageJsons(root) {
5703
5704
  function readPackageJson(filePath) {
5704
5705
  return readJson(filePath);
5705
5706
  }
5706
- function packageLabel(record) {
5707
- return record.packageJson.name ?? relativeTopoloPath(path.dirname(record.path));
5707
+ function packageLabel(record2) {
5708
+ return record2.packageJson.name ?? relativeTopoloPath(path.dirname(record2.path));
5708
5709
  }
5709
5710
  function sortedDirEntries(dir) {
5710
5711
  return readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
@@ -5793,14 +5794,14 @@ import { constants } from "fs";
5793
5794
  import { access } from "fs/promises";
5794
5795
  import { relative, resolve } from "path";
5795
5796
  function normalizeHomePath(value) {
5796
- const path4 = String(value || "/dashboard").trim();
5797
- if (!path4.startsWith("/") || path4.startsWith("//") || /[?#]/.test(path4)) {
5797
+ const path5 = String(value || "/dashboard").trim();
5798
+ if (!path5.startsWith("/") || path5.startsWith("//") || /[?#]/.test(path5)) {
5798
5799
  throw new Error("Invalid home path. Use an absolute application path such as /dashboard.");
5799
5800
  }
5800
- if (["/", "/login", "/auth/callback", "/getting-started"].includes(path4)) {
5801
+ if (["/", "/login", "/auth/callback", "/getting-started"].includes(path5)) {
5801
5802
  throw new Error("Home path must be a protected product route, not a reserved entry route.");
5802
5803
  }
5803
- return path4.replace(/\/$/, "") || "/dashboard";
5804
+ return path5.replace(/\/$/, "") || "/dashboard";
5804
5805
  }
5805
5806
  function normalizeHomeLabel(value) {
5806
5807
  const label = String(value || "Dashboard").trim();
@@ -5941,9 +5942,9 @@ function toTitle(value) {
5941
5942
  function json(value) {
5942
5943
  return JSON.stringify(value, null, 2);
5943
5944
  }
5944
- async function pathExists(path4) {
5945
+ async function pathExists(path5) {
5945
5946
  try {
5946
- await access(path4, constants.F_OK);
5947
+ await access(path5, constants.F_OK);
5947
5948
  return true;
5948
5949
  } catch {
5949
5950
  return false;
@@ -8229,6 +8230,37 @@ function topoloAppManifestJson(application, options) {
8229
8230
  }
8230
8231
  },
8231
8232
  targets,
8233
+ registration: {
8234
+ ownerType: options.payload.ownerType,
8235
+ portfolio: options.payload.portfolio,
8236
+ applicationId: application.id,
8237
+ serviceSlug: application.serviceSlug,
8238
+ displayName: toTitle(application.name),
8239
+ description: options.payload.experience.tagline,
8240
+ category: "topolo-platform",
8241
+ audience: options.payload.audience,
8242
+ tenancy: options.payload.tenancy,
8243
+ surfaceModel: options.payload.surfaceModel,
8244
+ containerAppId: options.payload.containerAppId,
8245
+ distributionModel: options.payload.distributionModel,
8246
+ version: "1.0.0",
8247
+ pricing: { type: "free", monthlyPrice: null },
8248
+ supportEmail: "support@topolo.io",
8249
+ auth: {
8250
+ callbackPath: "/auth/callback",
8251
+ requestedScopes: options.api ? ["openid", "profile", "email", "organization:read", "entitlements:read", `${application.id}:read`] : ["openid", "profile", "email", "organization:read"]
8252
+ },
8253
+ capabilities: options.payload.capabilities,
8254
+ marketplace: {
8255
+ intent: options.payload.distributionModel === "marketplace" ? "submit_when_ready" : "not_requested",
8256
+ status: "preview",
8257
+ shortBlurb: options.payload.experience.tagline,
8258
+ tagline: options.payload.experience.tagline,
8259
+ description: options.payload.experience.tagline,
8260
+ docsUrl: `https://docs.topolo.app/applications/${slug}`
8261
+ }
8262
+ },
8263
+ notifications: { role: "consumer_only" },
8232
8264
  ...actions.length ? { actions } : {}
8233
8265
  })}
8234
8266
  `;
@@ -8913,7 +8945,7 @@ function developerManifest(application, options) {
8913
8945
  surface_model: options.surfaceModel,
8914
8946
  container_app_id: options.containerAppId,
8915
8947
  package_name: application.packageName,
8916
- app_id: options.api ? application.appId : null,
8948
+ app_id: application.appId,
8917
8949
  capabilities: {
8918
8950
  selected: options.capabilities,
8919
8951
  supported_v1: [
@@ -8957,7 +8989,38 @@ function developerManifest(application, options) {
8957
8989
  },
8958
8990
  marketplace: {
8959
8991
  intent: options.distributionModel === "marketplace" ? "submit_when_ready" : "not_requested"
8960
- }
8992
+ },
8993
+ registration: {
8994
+ ownerType: "third_party",
8995
+ applicationId: application.id,
8996
+ serviceSlug: application.serviceSlug,
8997
+ displayName: toTitle(application.name),
8998
+ description: `${toTitle(application.name)} application for Topolo.`,
8999
+ category: "productivity",
9000
+ audience: options.audience,
9001
+ tenancy: options.tenancy,
9002
+ surfaceModel: options.surfaceModel,
9003
+ containerAppId: options.containerAppId,
9004
+ distributionModel: options.distributionModel,
9005
+ version: "1.0.0",
9006
+ pricing: { type: "free", monthlyPrice: null },
9007
+ supportEmail: "",
9008
+ productionUrl: application.productionUrl,
9009
+ auth: {
9010
+ callbackPath,
9011
+ requestedScopes
9012
+ },
9013
+ capabilities: options.capabilities,
9014
+ marketplace: {
9015
+ intent: options.distributionModel === "marketplace" ? "submit_when_ready" : "not_requested",
9016
+ status: "preview",
9017
+ shortBlurb: `${toTitle(application.name)} application for Topolo.`,
9018
+ tagline: `${toTitle(application.name)} application for Topolo.`,
9019
+ description: `${toTitle(application.name)} application for Topolo.`,
9020
+ docsUrl: application.productionUrl || "https://developers.topolo.app"
9021
+ }
9022
+ },
9023
+ notifications: { role: "consumer_only" }
8961
9024
  })}
8962
9025
  `;
8963
9026
  }
@@ -9243,9 +9306,10 @@ function applicationScaffoldPayload(application, options = {}) {
9243
9306
  workspaceLabel: tenancy === "household" ? "Households" : "Workspaces",
9244
9307
  tagline: String(options.tagline || "").trim() || `Manage your work in ${toTitle(name)}.`
9245
9308
  };
9246
- const serviceSlug = api ? target === "platform" ? `topolo-${slug}` : id : null;
9247
- const defaultAppId = target === "platform" ? `app_topolo_${id}` : catalogApplication?.services[0] ?? id;
9248
- const appId = api ? options.appId ?? defaultAppId : null;
9309
+ const serviceSlug = target === "platform" ? `topolo-${slug}` : id;
9310
+ const catalogAppId = catalogApplication?.services.find((service) => service.startsWith("app_"));
9311
+ const defaultAppId = target === "platform" ? `app_topolo_${id}` : catalogAppId ?? `app_${id.replace(/-/g, "_")}`;
9312
+ const appId = options.appId ?? defaultAppId;
9249
9313
  const productionUrl = browser ? options.productionUrl ?? catalogApplication?.productionUrl ?? `https://${slug}.topolo.app` : null;
9250
9314
  const scopes = scaffoldScopes({ browser, api });
9251
9315
  const capabilities = scaffoldCapabilities({ browser, api, distributionModel });
@@ -9623,7 +9687,8 @@ function scaffoldFiles(options) {
9623
9687
  content: topoloAppManifestJson(application, {
9624
9688
  api: options.api,
9625
9689
  resources: options.payload.resources,
9626
- profile: options.payload.profile
9690
+ profile: options.payload.profile,
9691
+ payload: options.payload
9627
9692
  })
9628
9693
  },
9629
9694
  ...options.api ? [
@@ -10026,7 +10091,7 @@ async function provisionApplicationScaffold(payload) {
10026
10091
  `Could not resolve Developers app id from Auth slug ${DEVELOPERS_SERVICE_SLUG}.`
10027
10092
  );
10028
10093
  }
10029
- const endpoint = payload.target === "platform" ? "/api/developer-console/admin/first-party-app-scaffolds" : "/api/developer-console/apps/scaffold";
10094
+ const endpoint = payload.target === "platform" ? "/api/developer-console/admin/apps/reconcile" : "/api/developer-console/apps/reconcile";
10030
10095
  const headers = new Headers({
10031
10096
  "Content-Type": "application/json",
10032
10097
  "X-App-ID": developersAppId,
@@ -10044,7 +10109,7 @@ async function provisionApplicationScaffold(payload) {
10044
10109
  const response = await fetch(`${developersOrigin}${endpoint}`, {
10045
10110
  method: "POST",
10046
10111
  headers,
10047
- body: JSON.stringify(buildProvisioningRequest(payload))
10112
+ body: JSON.stringify(buildProvisioningRequest(payload, identity.user.email))
10048
10113
  });
10049
10114
  const envelope = await response.json().catch(() => null);
10050
10115
  if (!response.ok || !envelope?.success || !envelope.data) {
@@ -10053,8 +10118,10 @@ async function provisionApplicationScaffold(payload) {
10053
10118
  );
10054
10119
  }
10055
10120
  const app = pickRecord(envelope.data.app);
10056
- const service = pickRecord(envelope.data.service);
10057
- const authConfig = pickRecord(envelope.data.authConfig);
10121
+ const projections = pickRecord(envelope.data.projections);
10122
+ const authProjection = pickRecord(projections?.auth);
10123
+ const service = pickRecord(authProjection?.service);
10124
+ const authConfig = pickRecord(authProjection?.authConfig);
10058
10125
  if (!app) {
10059
10126
  throw new Error("Developers provisioning did not return an app record.");
10060
10127
  }
@@ -10091,9 +10158,9 @@ async function provisionApplicationScaffold(payload) {
10091
10158
  } : null
10092
10159
  };
10093
10160
  }
10094
- function buildProvisioningRequest(payload) {
10161
+ function buildProvisioningRequest(payload, supportEmail) {
10095
10162
  return {
10096
- target: payload.target,
10163
+ mode: "apply",
10097
10164
  ownerType: payload.ownerType,
10098
10165
  portfolio: payload.portfolio,
10099
10166
  audience: payload.audience,
@@ -10106,14 +10173,28 @@ function buildProvisioningRequest(payload) {
10106
10173
  "http://localhost:5173/auth/callback",
10107
10174
  payload.application.productionUrl ? `${payload.application.productionUrl.replace(/\/$/, "")}/auth/callback` : null
10108
10175
  ].filter(Boolean),
10109
- healthEndpoint: payload.application.productionUrl ? `${payload.application.productionUrl.replace(/\/$/, "")}/api/health` : null,
10110
- runtimeStrategy: "external",
10111
10176
  requestedScopes: payload.application.appId ? ["openid", "profile", "email", "organization:read", "entitlements:read", `${payload.application.id}:read`] : ["openid", "profile", "email", "organization:read"],
10112
- marketplaceIntent: payload.distributionModel === "marketplace" ? "submit_when_ready" : "not_requested",
10177
+ marketplace: {
10178
+ intent: payload.distributionModel === "marketplace" ? "submit_when_ready" : "not_requested",
10179
+ status: "preview",
10180
+ shortBlurb: payload.experience.tagline,
10181
+ tagline: payload.experience.tagline,
10182
+ description: payload.experience.tagline,
10183
+ docsUrl: payload.application.productionUrl || "https://developers.topolo.app"
10184
+ },
10185
+ notifications: { role: "consumer_only" },
10113
10186
  applicationId: payload.application.id,
10114
- appDir: payload.application.appDir,
10115
- packageName: payload.application.packageName,
10116
10187
  name: payload.application.name,
10188
+ description: payload.experience.tagline,
10189
+ category: payload.target === "platform" ? "topolo-platform" : "productivity",
10190
+ version: "1.0.0",
10191
+ pricingType: "free",
10192
+ monthlyPrice: null,
10193
+ demoUrl: payload.application.productionUrl,
10194
+ repoUrl: null,
10195
+ supportEmail,
10196
+ tags: [],
10197
+ screenshots: [],
10117
10198
  productionUrl: payload.application.productionUrl,
10118
10199
  serviceSlug: payload.application.serviceSlug,
10119
10200
  appId: payload.application.appId
@@ -10187,6 +10268,332 @@ async function applyProvisionedAppId(manifestPath, appId) {
10187
10268
 
10188
10269
  // src/commands/apps.ts
10189
10270
  import { join as join3 } from "path";
10271
+
10272
+ // src/commands/app-reconcile.ts
10273
+ import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
10274
+ import path2 from "path";
10275
+ import { createInterface as createInterface2 } from "readline/promises";
10276
+ import { stdin as input2, stdout as output } from "process";
10277
+ function record(value) {
10278
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
10279
+ }
10280
+ function environmentValue(value, environment) {
10281
+ if (typeof value === "string") return value;
10282
+ if (!value) return null;
10283
+ return typeof value[environment] === "string" ? value[environment] : null;
10284
+ }
10285
+ function resolveManifestApplicationUrl(manifest, environment) {
10286
+ const targets = manifest.targets || {};
10287
+ const targetName = manifest.primaryTarget || (targets.web ? "web" : Object.keys(targets)[0]);
10288
+ const target = targetName ? targets[targetName] : null;
10289
+ if (!target) {
10290
+ if (manifest.registration?.productionUrl) return manifest.registration.productionUrl.replace(/\/+$/, "");
10291
+ throw new Error("topolo.app.json has no application target or registration.productionUrl.");
10292
+ }
10293
+ const publicUrl = environmentValue(target.publicUrl, environment);
10294
+ if (publicUrl) return publicUrl.replace(/\/+$/, "");
10295
+ const hostOverride = target.hostOverride?.[environment];
10296
+ if (hostOverride) return `https://${hostOverride.replace(/^https?:\/\//, "").replace(/\/+$/, "")}`;
10297
+ const domain = manifest.environments?.[environment]?.domain;
10298
+ if (!target.subdomain || !domain) {
10299
+ throw new Error(`Cannot derive the ${environment} application URL from target ${targetName}.`);
10300
+ }
10301
+ return `https://${target.subdomain}.${domain}`;
10302
+ }
10303
+ function assertRegistration(value) {
10304
+ const registration = record(value);
10305
+ const required = [
10306
+ "ownerType",
10307
+ "applicationId",
10308
+ "serviceSlug",
10309
+ "displayName",
10310
+ "description",
10311
+ "audience",
10312
+ "tenancy",
10313
+ "surfaceModel",
10314
+ "distributionModel",
10315
+ "version",
10316
+ "pricing",
10317
+ "supportEmail",
10318
+ "auth",
10319
+ "capabilities",
10320
+ "marketplace"
10321
+ ];
10322
+ const missing = required.filter((field) => registration?.[field] == null || registration[field] === "");
10323
+ if (missing.length) {
10324
+ throw new Error(
10325
+ `topolo.app.json registration is incomplete: ${missing.join(", ")}. Run in an interactive terminal to complete the manifest, or edit the declarative registration block.`
10326
+ );
10327
+ }
10328
+ }
10329
+ function titleFromSlug(value) {
10330
+ return value.replace(/^topolo[-_]/, "").split(/[-_]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
10331
+ }
10332
+ async function completeApplicationRegistrationInteractively(options) {
10333
+ const manifestPath = path2.resolve(options.manifestPath || "topolo.app.json");
10334
+ const manifest = JSON.parse(await readFile3(manifestPath, "utf8"));
10335
+ const existing = record(manifest.registration) || {};
10336
+ const appId = String(manifest.appId || manifest.app_id || "").trim();
10337
+ const slug = String(existing.serviceSlug || manifest.name || "").trim();
10338
+ const defaultName = titleFromSlug(slug);
10339
+ const readline = options.prompt ? null : createInterface2({ input: input2, output });
10340
+ const ask = options.prompt || ((question) => readline.question(question));
10341
+ let changed = !manifest.registration;
10342
+ const requiredText = async (field, label, fallback = "") => {
10343
+ const current = typeof existing[field] === "string" ? String(existing[field]).trim() : "";
10344
+ if (current) return current;
10345
+ const answer = (await ask(`${label}${fallback ? ` [${fallback}]` : ""}: `)).trim() || fallback;
10346
+ if (!answer) throw new Error(`${field} is required.`);
10347
+ changed = true;
10348
+ return answer;
10349
+ };
10350
+ try {
10351
+ const productionUrl = String(existing.productionUrl || resolveManifestApplicationUrl(manifest, options.environment || selectedEnvironment()));
10352
+ const displayName = await requiredText("displayName", "Application display name", defaultName);
10353
+ const description = await requiredText("description", "Application description", `${displayName} application for Topolo.`);
10354
+ const supportEmail = await requiredText("supportEmail", "Support email");
10355
+ manifest.registration = {
10356
+ ownerType: options.expectedOwnerType,
10357
+ ...options.expectedOwnerType === "topolo_first_party" ? { portfolio: existing.portfolio || "platform" } : {},
10358
+ applicationId: String(existing.applicationId || slug).replace(/^topolo-/, "topolo-"),
10359
+ serviceSlug: slug,
10360
+ displayName,
10361
+ description,
10362
+ category: String(existing.category || (options.expectedOwnerType === "topolo_first_party" ? "topolo-platform" : "productivity")),
10363
+ audience: existing.audience || "business",
10364
+ tenancy: existing.tenancy || "organization",
10365
+ surfaceModel: existing.surfaceModel || "web_app",
10366
+ containerAppId: existing.containerAppId,
10367
+ distributionModel: existing.distributionModel || (options.expectedOwnerType === "topolo_first_party" ? "marketplace" : "organization_internal"),
10368
+ version: String(existing.version || "1.0.0"),
10369
+ pricing: record(existing.pricing) || { type: "free", monthlyPrice: null },
10370
+ supportEmail,
10371
+ productionUrl,
10372
+ demoUrl: typeof existing.demoUrl === "string" ? existing.demoUrl : void 0,
10373
+ repoUrl: typeof existing.repoUrl === "string" ? existing.repoUrl : void 0,
10374
+ tags: Array.isArray(existing.tags) ? existing.tags : [],
10375
+ screenshots: Array.isArray(existing.screenshots) ? existing.screenshots : [],
10376
+ auth: record(existing.auth) || {
10377
+ callbackPath: "/auth/callback",
10378
+ requestedScopes: ["openid", "profile", "email", "organization:read"]
10379
+ },
10380
+ capabilities: Array.isArray(existing.capabilities) ? existing.capabilities : ["login", "launcher"],
10381
+ marketplace: record(existing.marketplace) || {
10382
+ intent: options.expectedOwnerType === "topolo_first_party" ? "submit_when_ready" : "not_requested",
10383
+ status: "preview",
10384
+ shortBlurb: description,
10385
+ tagline: description,
10386
+ description,
10387
+ docsUrl: productionUrl
10388
+ }
10389
+ };
10390
+ if (!manifest.notifications) {
10391
+ const role = (await ask("Notification role [consumer_only]: ")).trim() || "consumer_only";
10392
+ if (!["producer", "consumer_only", "internal_non_producer"].includes(role)) {
10393
+ throw new Error("Notification role must be producer, consumer_only, or internal_non_producer.");
10394
+ }
10395
+ if (role === "producer") {
10396
+ const contracts = (await ask("Notification contracts path: ")).trim();
10397
+ if (!contracts) throw new Error("Producer applications must declare a notification contracts path.");
10398
+ manifest.notifications = { role, contracts };
10399
+ } else {
10400
+ manifest.notifications = { role };
10401
+ }
10402
+ changed = true;
10403
+ }
10404
+ if (!appId) throw new Error("topolo.app.json must declare the immutable appId before reconciliation.");
10405
+ if (changed) await writeFile4(manifestPath, `${JSON.stringify(manifest, null, 2)}
10406
+ `, "utf8");
10407
+ return { changed, manifestPath };
10408
+ } finally {
10409
+ if (readline) readline.close();
10410
+ }
10411
+ }
10412
+ async function loadApplicationReconcileRequest(options) {
10413
+ const manifestPath = path2.resolve(options.manifestPath || "topolo.app.json");
10414
+ const raw = await readFile3(manifestPath, "utf8");
10415
+ const manifest = JSON.parse(raw);
10416
+ assertRegistration(manifest.registration);
10417
+ const registration = manifest.registration;
10418
+ if (registration.ownerType !== options.expectedOwnerType) {
10419
+ throw new Error(
10420
+ `${registration.ownerType} registration cannot be reconciled through the ${options.expectedOwnerType} command surface.`
10421
+ );
10422
+ }
10423
+ const environment = options.environment || selectedEnvironment();
10424
+ const productionUrl = resolveManifestApplicationUrl(manifest, environment);
10425
+ const callbackPath = registration.auth.callbackPath.startsWith("/") ? registration.auth.callbackPath : `/${registration.auth.callbackPath}`;
10426
+ const appId = String(manifest.appId || manifest.app_id || "").trim();
10427
+ if (!appId) throw new Error("topolo.app.json must declare the immutable appId.");
10428
+ return {
10429
+ manifestPath,
10430
+ request: {
10431
+ mode: options.mode,
10432
+ appId,
10433
+ ownerType: registration.ownerType,
10434
+ portfolio: registration.portfolio ?? null,
10435
+ applicationId: registration.applicationId,
10436
+ serviceSlug: registration.serviceSlug,
10437
+ audience: registration.audience,
10438
+ tenancy: registration.tenancy,
10439
+ surfaceModel: registration.surfaceModel,
10440
+ containerAppId: registration.containerAppId ?? null,
10441
+ distributionModel: registration.distributionModel,
10442
+ name: registration.displayName,
10443
+ description: registration.description,
10444
+ category: registration.category ?? null,
10445
+ version: registration.version,
10446
+ pricingType: registration.pricing.type,
10447
+ monthlyPrice: registration.pricing.monthlyPrice ?? null,
10448
+ productionUrl,
10449
+ demoUrl: registration.ownerType === "topolo_first_party" ? productionUrl : registration.demoUrl ?? productionUrl,
10450
+ repoUrl: registration.repoUrl ?? null,
10451
+ supportEmail: registration.supportEmail,
10452
+ tags: registration.tags ?? [],
10453
+ screenshots: registration.screenshots ?? [],
10454
+ callbackUrls: [`${productionUrl}${callbackPath}`],
10455
+ requestedScopes: registration.auth.requestedScopes,
10456
+ capabilities: registration.capabilities,
10457
+ marketplace: { ...registration.marketplace, appUrl: productionUrl },
10458
+ notifications: manifest.notifications ?? null
10459
+ }
10460
+ };
10461
+ }
10462
+ function defaultAuthOrigin2(environment) {
10463
+ if (environment === "development") return "https://auth.topolo.dev";
10464
+ if (environment === "staging") return "https://auth.stg.topolo.us";
10465
+ return "https://auth.topolo.app";
10466
+ }
10467
+ async function resolveDevelopersAppId(authOrigin, fetchImpl) {
10468
+ const response = await fetchImpl(`${authOrigin}/api/services/by-slug/topolo-developers`, {
10469
+ headers: { Accept: "application/json" }
10470
+ });
10471
+ const payload = await response.json().catch(() => null);
10472
+ const appId = typeof payload?.data?.app_id === "string" ? payload.data.app_id.trim() : "";
10473
+ if (!response.ok || !appId) throw new Error("Could not resolve the Developers app id from Auth.");
10474
+ return appId;
10475
+ }
10476
+ async function reconcileApplicationRequest(request, dependencies = {}) {
10477
+ const selectEnvironment = dependencies.selectedEnvironment || selectedEnvironment;
10478
+ const refreshCredential = dependencies.refreshCredentialIfNeeded || refreshCredentialIfNeeded;
10479
+ const makeClient2 = dependencies.buildClient || buildClient;
10480
+ const loadConfig = dependencies.readConfig || readConfig;
10481
+ const resolveProfile = dependencies.resolvedProfileConfig || resolvedProfileConfig;
10482
+ const assertServiceUrl = dependencies.assertServiceUrlForEnvironment || assertServiceUrlForEnvironment;
10483
+ const fetchImpl = dependencies.fetch || fetch;
10484
+ const environment = selectEnvironment();
10485
+ const credential = await refreshCredential(environment);
10486
+ if (!credential) throw new Error("Application reconciliation requires an authenticated Topolo session. Run `topolo auth login`.");
10487
+ const client = await makeClient2();
10488
+ if (!client) throw new Error("Could not build the authenticated Topolo client.");
10489
+ await client.client.introspect();
10490
+ const config = await loadConfig();
10491
+ const profile = resolveProfile(config, environment);
10492
+ const authOrigin = (profile.serviceUrls?.auth || defaultAuthOrigin2(environment)).replace(/\/+$/, "");
10493
+ const developersOrigin = (profile.serviceUrls?.developers || "https://developers.topolo.app").replace(/\/+$/, "");
10494
+ assertServiceUrl("auth", authOrigin, environment);
10495
+ assertServiceUrl("developers", developersOrigin, environment);
10496
+ const headers = new Headers({
10497
+ "Content-Type": "application/json",
10498
+ "X-App-ID": await resolveDevelopersAppId(authOrigin, fetchImpl),
10499
+ "X-Topolo-Client": `topolo-cli/${CLI_VERSION}`
10500
+ });
10501
+ const agentName = resolveAgentName(profile.agentName);
10502
+ if (agentName) headers.set("X-Topolo-Agent", agentName);
10503
+ if (credential.kind === "api_key") headers.set("X-Api-Key", credential.apiKey);
10504
+ else headers.set("Authorization", `Bearer ${credential.accessToken}`);
10505
+ const endpoint = request.ownerType === "topolo_first_party" ? "/api/developer-console/admin/apps/reconcile" : "/api/developer-console/apps/reconcile";
10506
+ const response = await fetchImpl(`${developersOrigin}${endpoint}`, {
10507
+ method: "POST",
10508
+ headers,
10509
+ body: JSON.stringify(request)
10510
+ });
10511
+ const envelope = await response.json().catch(() => null);
10512
+ if (!response.ok || !envelope?.success || !envelope.data) {
10513
+ const message = typeof envelope?.error === "string" ? envelope.error : envelope?.error?.message || envelope?.message || `Application reconciliation failed with status ${response.status}.`;
10514
+ const error = new Error(message);
10515
+ error.code = response.status === 409 ? "immutable_drift" : response.status === 403 ? "permission_denied" : "reconcile_failed";
10516
+ error.details = typeof envelope?.error === "object" ? envelope.error.details : void 0;
10517
+ throw error;
10518
+ }
10519
+ return envelope.data;
10520
+ }
10521
+ function resolveReconcileMode(options) {
10522
+ const explicit = [options.check, options.dryRun, options.apply].filter(Boolean).length;
10523
+ if (explicit > 1) throw new Error("Choose exactly one of --check, --dry-run, or --apply.");
10524
+ if (options.apply) return { mode: "apply", interactiveApply: false };
10525
+ if (options.check || options.dryRun) return { mode: "check", interactiveApply: false };
10526
+ return {
10527
+ mode: "check",
10528
+ interactiveApply: !options.nonInteractive && Boolean(options.interactive || process.stdin.isTTY && process.stdout.isTTY)
10529
+ };
10530
+ }
10531
+ async function confirmApplicationReconcile(result) {
10532
+ if (result.plan.status === "in_sync") return false;
10533
+ const readline = createInterface2({ input: input2, output });
10534
+ try {
10535
+ const answer = await readline.question(`Apply ${result.plan.changes.length} application registration change(s)? [y/N] `);
10536
+ return /^(y|yes)$/i.test(answer.trim());
10537
+ } finally {
10538
+ readline.close();
10539
+ }
10540
+ }
10541
+ function formatApplicationReconcile(result, manifestPath) {
10542
+ const lines = [
10543
+ `Application registration: ${result.plan.status}`,
10544
+ `Manifest: ${manifestPath}`,
10545
+ `Mode: ${result.mode}`
10546
+ ];
10547
+ for (const change of result.changes || result.plan.changes) {
10548
+ lines.push(`- ${change.path}: ${JSON.stringify(change.before)} -> ${JSON.stringify(change.after)}`);
10549
+ }
10550
+ if (result.projections) lines.push("Catalog/Auth projections: reconciled");
10551
+ return lines.join("\n");
10552
+ }
10553
+ function registerApplicationReconcileCommand(parent, ownerType, dependencies = {}) {
10554
+ const completeRegistration = dependencies.completeRegistration || completeApplicationRegistrationInteractively;
10555
+ const loadRequest = dependencies.loadRequest || loadApplicationReconcileRequest;
10556
+ const reconcileRequest = dependencies.reconcileRequest || reconcileApplicationRequest;
10557
+ const confirm = dependencies.confirm || confirmApplicationReconcile;
10558
+ parent.command("reconcile").description("Check or apply the declarative application registration in topolo.app.json.").option("--manifest <path>", "Path to topolo.app.json", "topolo.app.json").option("--check", "Report drift without mutating remote data").option("--dry-run", "Alias for --check").option("--apply", "Apply mutable drift and required projections").option("--interactive", "Use the human confirmation flow").option("--non-interactive", "Disable all prompts").option("--yes", "Accept the interactive apply confirmation").option("--json", "Output JSON").option("--no-json", "Output human-readable details").action(async (options) => {
10559
+ const outputMode = resolveOutputMode(options.json);
10560
+ try {
10561
+ const selection = resolveReconcileMode(options);
10562
+ if (selection.interactiveApply) {
10563
+ await completeRegistration({
10564
+ manifestPath: options.manifest,
10565
+ expectedOwnerType: ownerType
10566
+ });
10567
+ }
10568
+ const loaded = await loadRequest({
10569
+ manifestPath: options.manifest,
10570
+ expectedOwnerType: ownerType,
10571
+ mode: selection.mode
10572
+ });
10573
+ let result = await reconcileRequest(loaded.request);
10574
+ if (selection.interactiveApply && result.plan.status !== "in_sync") {
10575
+ if (outputMode === "pretty") {
10576
+ process.stdout.write(`${formatApplicationReconcile(result, loaded.manifestPath)}
10577
+ `);
10578
+ }
10579
+ const confirmed = options.yes || await confirm(result);
10580
+ if (confirmed) {
10581
+ result = await reconcileRequest({ ...loaded.request, mode: "apply" });
10582
+ }
10583
+ }
10584
+ emit(
10585
+ { manifest: loaded.manifestPath, reconciliation: result },
10586
+ outputMode,
10587
+ () => formatApplicationReconcile(result, loaded.manifestPath)
10588
+ );
10589
+ if (result.mode === "check" && result.plan.status !== "in_sync") process.exitCode = 2;
10590
+ } catch (error) {
10591
+ fail(error, outputMode);
10592
+ }
10593
+ });
10594
+ }
10595
+
10596
+ // src/commands/apps.ts
10190
10597
  var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
10191
10598
  var CLOUDCONTROL_LOCAL_APPLICATION = {
10192
10599
  id: "cloudcontrol",
@@ -10216,6 +10623,7 @@ function registerAppsCommand(program2) {
10216
10623
  fail(err, mode);
10217
10624
  }
10218
10625
  });
10626
+ registerApplicationReconcileCommand(apps, "third_party");
10219
10627
  apps.command("get <application>").description("Show one Topolo application available to the current credential.").option("--json", "Output JSON", true).option("--no-json", "Output human-readable details").action(async (application, opts) => {
10220
10628
  const mode = resolveOutputMode(opts.json);
10221
10629
  try {
@@ -10254,8 +10662,8 @@ function registerAppsCommand(program2) {
10254
10662
  repoRoot: opts.repoRoot
10255
10663
  });
10256
10664
  const gate = opts.failOn ? evaluateApplicationAuditGate(payload, opts.failOn) : null;
10257
- const output = gate ? { ...payload, conformanceGate: gate } : payload;
10258
- emit(output, mode, () => formatAudit(output));
10665
+ const output2 = gate ? { ...payload, conformanceGate: gate } : payload;
10666
+ emit(output2, mode, () => formatAudit(output2));
10259
10667
  if (gate && !gate.passed) {
10260
10668
  if (mode === "json") {
10261
10669
  process.stderr.write(JSON.stringify({ error: gate.message, conformanceGate: gate }, null, 2) + "\n");
@@ -10317,14 +10725,14 @@ function registerAppsCommand(program2) {
10317
10725
  appIdWriteBack = { ...result, path: manifestFile.path };
10318
10726
  }
10319
10727
  }
10320
- const output = {
10728
+ const output2 = {
10321
10729
  ...payload,
10322
10730
  ...writeResult ? { write: writeResult } : {},
10323
10731
  ...provisionResult ? { provision: provisionResult } : {},
10324
10732
  ...appIdWriteBack ? { appIdWriteBack } : {}
10325
10733
  };
10326
10734
  emit(
10327
- output,
10735
+ output2,
10328
10736
  mode,
10329
10737
  () => formatScaffold(payload, writeResult, provisionResult, appIdWriteBack)
10330
10738
  );
@@ -10388,7 +10796,7 @@ function applicationAuditPayload(application, options = {}) {
10388
10796
  }
10389
10797
  function recordApplicationEvidence(application, options = {}) {
10390
10798
  const appId = resolveApplication(application).id;
10391
- const root = path2.resolve(options.appRoot ?? process.cwd());
10799
+ const root = path3.resolve(options.appRoot ?? process.cwd());
10392
10800
  if (!existsSync3(root)) {
10393
10801
  throw new Error(`Application root does not exist: ${root}`);
10394
10802
  }
@@ -10400,8 +10808,8 @@ function recordApplicationEvidence(application, options = {}) {
10400
10808
  commit = void 0;
10401
10809
  }
10402
10810
  }
10403
- const evidenceDir = path2.join(root, ".topolo");
10404
- const evidencePath = path2.join(evidenceDir, "evidence.json");
10811
+ const evidenceDir = path3.join(root, ".topolo");
10812
+ const evidencePath = path3.join(evidenceDir, "evidence.json");
10405
10813
  let before = null;
10406
10814
  if (existsSync3(evidencePath)) {
10407
10815
  try {
@@ -10701,10 +11109,11 @@ function statusesAtOrWorse(threshold) {
10701
11109
 
10702
11110
  // src/commands/platform.ts
10703
11111
  import { readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
10704
- import path3 from "path";
11112
+ import path4 from "path";
10705
11113
  function registerPlatformCommand(program2) {
10706
11114
  const platform3 = program2.command("platform").description("Internal Topolo first-party platform and personal app commands");
10707
11115
  const platformApps = platform3.command("apps").description("Scaffold and operate first-party Topolo platform and personal applications");
11116
+ registerApplicationReconcileCommand(platformApps, "topolo_first_party");
10708
11117
  platformApps.command("scaffold <application>").description(
10709
11118
  "Generate a first-party Topolo scaffold for the platform or personal portfolio."
10710
11119
  ).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application repo name or apps/<repo> path").option("--name <name>", "Application display/package source name").option("--package-name <name>", "package.json name").option("--production-url <url>", "Production browser URL").option("--service <id>", "Stable SDK/Auth app id for API-backed apps").option("--portfolio <portfolio>", "First-party portfolio: platform or personal").option(
@@ -10761,13 +11170,13 @@ function registerPlatformCommand(program2) {
10761
11170
  }
10762
11171
  const writeResult = opts.write ? await writeApplicationScaffold(payload, { repoRoot: opts.repoRoot, force: opts.force }) : null;
10763
11172
  const provisionResult = shouldProvision ? await provisionApplicationScaffold(payload) : null;
10764
- const output = opts.workflowOnly ? { application: payload.application, ciWorkflowPath: payload.ciWorkflowPath, ciWorkflow: payload.ciWorkflow } : {
11173
+ const output2 = opts.workflowOnly ? { application: payload.application, ciWorkflowPath: payload.ciWorkflowPath, ciWorkflow: payload.ciWorkflow } : {
10765
11174
  ...payload,
10766
11175
  ...writeResult ? { write: writeResult } : {},
10767
11176
  ...provisionResult ? { provision: provisionResult } : {}
10768
11177
  };
10769
11178
  emit(
10770
- output,
11179
+ output2,
10771
11180
  mode,
10772
11181
  () => opts.workflowOnly ? payload.ciWorkflow ?? "" : formatScaffold(payload, writeResult, provisionResult)
10773
11182
  );
@@ -10818,12 +11227,12 @@ var SKIPPED_SCAN_DIRS = /* @__PURE__ */ new Set([
10818
11227
  "output"
10819
11228
  ]);
10820
11229
  function scanStagingProductionUrls(rootInput, allowPatterns) {
10821
- const root = path3.resolve(rootInput);
11230
+ const root = path4.resolve(rootInput);
10822
11231
  const allowlist = allowPatterns.map((pattern) => new RegExp(pattern));
10823
11232
  const violations = [];
10824
11233
  function shouldSkipFile(filePath) {
10825
- const basename = path3.basename(filePath);
10826
- const relativePath = path3.relative(root, filePath);
11234
+ const basename = path4.basename(filePath);
11235
+ const relativePath = path4.relative(root, filePath);
10827
11236
  if (basename === "package-lock.json" || basename === "pnpm-lock.yaml" || basename === "yarn.lock") return true;
10828
11237
  if (basename === "topolo.cloudcontrol.json") return true;
10829
11238
  if (basename === "wrangler.toml" || basename === "wrangler.jsonc") return true;
@@ -10832,14 +11241,14 @@ function scanStagingProductionUrls(rootInput, allowPatterns) {
10832
11241
  if (basename.endsWith(".generated.ts")) return true;
10833
11242
  if (basename.endsWith(".test.ts") || basename.endsWith(".test.tsx") || basename.endsWith(".spec.ts")) return true;
10834
11243
  if (basename === ".env.production") return true;
10835
- if (relativePath === path3.join("src", "environment.ts") || relativePath === path3.join("src", "environmentUrls.ts") || relativePath.endsWith(path3.join("src", "environment.ts")) || relativePath.endsWith(path3.join("src", "lib", "environment.ts")) || relativePath === path3.join("packages", "topolo-ui-kit", "src", "environmentUrls.ts")) return true;
10836
- if (relativePath.includes(`${path3.sep}test${path3.sep}`) || relativePath.includes(`${path3.sep}tests${path3.sep}`) || relativePath.includes(`${path3.sep}e2e${path3.sep}`)) return true;
10837
- if (relativePath.startsWith(`scripts${path3.sep}`) || relativePath.includes(`${path3.sep}scripts${path3.sep}`)) return true;
10838
- return !RUNTIME_EXTENSIONS.has(path3.extname(filePath));
11244
+ if (relativePath === path4.join("src", "environment.ts") || relativePath === path4.join("src", "environmentUrls.ts") || relativePath.endsWith(path4.join("src", "environment.ts")) || relativePath.endsWith(path4.join("src", "lib", "environment.ts")) || relativePath === path4.join("packages", "topolo-ui-kit", "src", "environmentUrls.ts")) return true;
11245
+ if (relativePath.includes(`${path4.sep}test${path4.sep}`) || relativePath.includes(`${path4.sep}tests${path4.sep}`) || relativePath.includes(`${path4.sep}e2e${path4.sep}`)) return true;
11246
+ if (relativePath.startsWith(`scripts${path4.sep}`) || relativePath.includes(`${path4.sep}scripts${path4.sep}`)) return true;
11247
+ return !RUNTIME_EXTENSIONS.has(path4.extname(filePath));
10839
11248
  }
10840
11249
  function scanFile(filePath) {
10841
11250
  if (shouldSkipFile(filePath)) return;
10842
- const relativePath = path3.relative(root, filePath);
11251
+ const relativePath = path4.relative(root, filePath);
10843
11252
  const content = readFileSync5(filePath, "utf8");
10844
11253
  const lines = content.split(/\r?\n/);
10845
11254
  lines.forEach((line, index) => {
@@ -10854,7 +11263,7 @@ function scanStagingProductionUrls(rootInput, allowPatterns) {
10854
11263
  }
10855
11264
  function walk(dir) {
10856
11265
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
10857
- const fullPath = path3.join(dir, entry.name);
11266
+ const fullPath = path4.join(dir, entry.name);
10858
11267
  if (entry.isDirectory()) {
10859
11268
  if (!SKIPPED_SCAN_DIRS.has(entry.name)) walk(fullPath);
10860
11269
  continue;
@@ -11022,7 +11431,7 @@ var DEFAULT_SEED_BASE_URL = "https://seed.stg.topolo.us";
11022
11431
  function seedBaseUrl() {
11023
11432
  return process.env.TOPOLO_SEED_URL || DEFAULT_SEED_BASE_URL;
11024
11433
  }
11025
- async function authedFetch(path4, init = {}) {
11434
+ async function authedFetch(path5, init = {}) {
11026
11435
  const credential = await refreshCredentialIfNeeded("staging");
11027
11436
  if (!credential) {
11028
11437
  throw new Error("No staging credential configured. Run `topolo auth login --env staging` or set TOPOLO_STAGING_API_KEY.");
@@ -11032,7 +11441,7 @@ async function authedFetch(path4, init = {}) {
11032
11441
  else headers.set("Authorization", `Bearer ${credential.accessToken}`);
11033
11442
  headers.set("X-Topolo-Client", `topolo-cli/${CLI_VERSION}`);
11034
11443
  if (init.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
11035
- const response = await fetch(`${seedBaseUrl()}${path4}`, { ...init, headers });
11444
+ const response = await fetch(`${seedBaseUrl()}${path5}`, { ...init, headers });
11036
11445
  const text = await response.text();
11037
11446
  let body;
11038
11447
  try {
@@ -11309,7 +11718,7 @@ function createOAuthClientPayload(options) {
11309
11718
  logo_url: options.logoUrl
11310
11719
  };
11311
11720
  }
11312
- async function authFetchJson(path4, init = {}) {
11721
+ async function authFetchJson(path5, init = {}) {
11313
11722
  const environment = selectedEnvironment();
11314
11723
  const credential = await refreshCredentialIfNeeded(environment);
11315
11724
  if (!credential) {
@@ -11331,7 +11740,7 @@ async function authFetchJson(path4, init = {}) {
11331
11740
  throw err;
11332
11741
  }
11333
11742
  headers.set("Authorization", `Bearer ${credential.accessToken}`);
11334
- const response = await fetch(`${authBaseUrl}${path4}`, { ...init, headers });
11743
+ const response = await fetch(`${authBaseUrl}${path5}`, { ...init, headers });
11335
11744
  const text = await response.text();
11336
11745
  const payload = text ? JSON.parse(text) : {};
11337
11746
  if (!response.ok || payload.success === false) {
@@ -11347,7 +11756,7 @@ function normalizeList(values, fallback = []) {
11347
11756
  }
11348
11757
 
11349
11758
  // src/commands/integrations.ts
11350
- import { readdir, readFile as readFile3, stat } from "fs/promises";
11759
+ import { readdir, readFile as readFile4, stat } from "fs/promises";
11351
11760
  import { join as join4, relative as relative3 } from "path";
11352
11761
  function registerIntegrationsCommand(program2) {
11353
11762
  const integrations = program2.command("integrations").description("Generate and validate Topolo integration handoffs.");
@@ -11491,7 +11900,7 @@ function detectFramework(packageJson2, haystack) {
11491
11900
  }
11492
11901
  async function readPackageJson2(repoRoot) {
11493
11902
  try {
11494
- return JSON.parse(await readFile3(join4(repoRoot, "package.json"), "utf8"));
11903
+ return JSON.parse(await readFile4(join4(repoRoot, "package.json"), "utf8"));
11495
11904
  } catch {
11496
11905
  return null;
11497
11906
  }
@@ -11514,7 +11923,7 @@ async function collectFiles(repoRoot) {
11514
11923
  if (info.size > 25e4) continue;
11515
11924
  result.push({
11516
11925
  path: relative3(root, fullPath),
11517
- content: await readFile3(fullPath, "utf8")
11926
+ content: await readFile4(fullPath, "utf8")
11518
11927
  });
11519
11928
  }
11520
11929
  }
@@ -11532,7 +11941,7 @@ function formatCheckResult(result) {
11532
11941
  // src/commands/skills.ts
11533
11942
  import { spawnSync } from "child_process";
11534
11943
  import { existsSync as existsSync4 } from "fs";
11535
- import { mkdir as mkdir3, copyFile, readFile as readFile4, writeFile as writeFile4, rm } from "fs/promises";
11944
+ import { mkdir as mkdir3, copyFile, readFile as readFile5, writeFile as writeFile5, rm } from "fs/promises";
11536
11945
  import { homedir as homedir2 } from "os";
11537
11946
  import { dirname as dirname4, join as join5, resolve as resolve3 } from "path";
11538
11947
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -11586,11 +11995,11 @@ function registerViaOfficialCli(cli, mcp, agentName) {
11586
11995
  return result.status === 0;
11587
11996
  }
11588
11997
  async function registerClaudeViaConfig(mcp) {
11589
- const path4 = join5(homedir2(), ".claude.json");
11998
+ const path5 = join5(homedir2(), ".claude.json");
11590
11999
  let config = {};
11591
- if (existsSync4(path4)) {
12000
+ if (existsSync4(path5)) {
11592
12001
  try {
11593
- config = JSON.parse(await readFile4(path4, "utf8"));
12002
+ config = JSON.parse(await readFile5(path5, "utf8"));
11594
12003
  } catch {
11595
12004
  config = {};
11596
12005
  }
@@ -11603,13 +12012,13 @@ async function registerClaudeViaConfig(mcp) {
11603
12012
  env: { TOPOLO_AGENT_NAME: AGENT_NAME_FOR.claude }
11604
12013
  };
11605
12014
  config.mcpServers = servers;
11606
- await writeFile4(path4, `${JSON.stringify(config, null, 2)}
12015
+ await writeFile5(path5, `${JSON.stringify(config, null, 2)}
11607
12016
  `, "utf8");
11608
12017
  }
11609
12018
  async function registerCodexViaConfig(mcp) {
11610
- const path4 = join5(homedir2(), ".codex", "config.toml");
12019
+ const path5 = join5(homedir2(), ".codex", "config.toml");
11611
12020
  let text = "";
11612
- if (existsSync4(path4)) text = await readFile4(path4, "utf8");
12021
+ if (existsSync4(path5)) text = await readFile5(path5, "utf8");
11613
12022
  text = text.replace(
11614
12023
  /(^|\n)\[mcp_servers\.topolo(?:\.[a-z]+)?\][^\[]*/g,
11615
12024
  (match) => match.startsWith("\n") ? "\n" : ""
@@ -11625,8 +12034,8 @@ command = "${mcp.command}"${argsToml}
11625
12034
  [mcp_servers.topolo.env]
11626
12035
  TOPOLO_AGENT_NAME = "${AGENT_NAME_FOR.codex}"
11627
12036
  `;
11628
- await mkdir3(dirname4(path4), { recursive: true });
11629
- await writeFile4(path4, `${text}${block}`, "utf8");
12037
+ await mkdir3(dirname4(path5), { recursive: true });
12038
+ await writeFile5(path5, `${text}${block}`, "utf8");
11630
12039
  }
11631
12040
  async function installForAgent(agent, sourceDir, mcp, opts) {
11632
12041
  const skillDir = agent === "claude" ? claudeSkillDir(opts.projectDir) : codexSkillDir();