@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.
- package/dist/index.js +612 -203
- 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,
|
|
65
|
-
super("http_error", message ?? `HTTP ${status} from ${service}${
|
|
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 =
|
|
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,
|
|
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,
|
|
2178
|
+
validateNode(schema, input3, "$", schema, errors, 0);
|
|
2179
2179
|
return { valid: errors.length === 0, errors };
|
|
2180
2180
|
}
|
|
2181
|
-
function inspectSchema(schema,
|
|
2182
|
-
if (depth > 128) return { path:
|
|
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:
|
|
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:
|
|
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:
|
|
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, `${
|
|
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,
|
|
2215
|
+
function validateNode(schema, value, path5, root, errors, depth) {
|
|
2216
2216
|
if (depth > 128) {
|
|
2217
|
-
errors.push({ path:
|
|
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:
|
|
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,
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
2258
|
-
if (typeof value === "number") validateNumber(schema, value,
|
|
2259
|
-
if (Array.isArray(value)) validateArray(schema, value,
|
|
2260
|
-
if (isRecord(value)) validateObject(schema, value,
|
|
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,
|
|
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:
|
|
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:
|
|
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: `${
|
|
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, `${
|
|
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, `${
|
|
2284
|
+
validateNode(childSchema, child, `${path5}.${key}`, root, errors, depth + 1);
|
|
2285
2285
|
} else if (schema.additionalProperties === false) {
|
|
2286
|
-
errors.push({ path: `${
|
|
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, `${
|
|
2288
|
+
validateNode(schema.additionalProperties, child, `${path5}.${key}`, root, errors, depth + 1);
|
|
2289
2289
|
}
|
|
2290
2290
|
}
|
|
2291
2291
|
}
|
|
2292
|
-
function validateArray(schema, value,
|
|
2292
|
+
function validateArray(schema, value, path5, root, errors, depth) {
|
|
2293
2293
|
if (typeof schema.minItems === "number" && value.length < schema.minItems) {
|
|
2294
|
-
errors.push({ path:
|
|
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:
|
|
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:
|
|
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, `${
|
|
2304
|
+
value.forEach((item, index) => validateNode(schema.items, item, `${path5}[${index}]`, root, errors, depth + 1));
|
|
2305
2305
|
}
|
|
2306
2306
|
}
|
|
2307
|
-
function validateString(schema, value,
|
|
2307
|
+
function validateString(schema, value, path5, errors) {
|
|
2308
2308
|
if (typeof schema.minLength === "number" && value.length < schema.minLength) {
|
|
2309
|
-
errors.push({ path:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
2322
|
-
if (schema.format === "email" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) errors.push({ path:
|
|
2323
|
-
if (schema.format === "date-time" && !isDateTime(value)) errors.push({ path:
|
|
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,
|
|
2326
|
-
if (typeof schema.minimum === "number" && value < schema.minimum) errors.push({ path:
|
|
2327
|
-
if (typeof schema.maximum === "number" && value > schema.maximum) errors.push({ path:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
2849
|
-
if (
|
|
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:
|
|
2855
|
-
actionCount: Number(
|
|
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,
|
|
2892
|
+
async callAction(action, input3 = {}, options = {}) {
|
|
2893
2893
|
const entry = await this.getAction(action);
|
|
2894
|
-
return this.executeResolvedAction(entry,
|
|
2894
|
+
return this.executeResolvedAction(entry, input3, options);
|
|
2895
2895
|
}
|
|
2896
|
-
async validateActionInput(action,
|
|
2896
|
+
async validateActionInput(action, input3 = {}) {
|
|
2897
2897
|
const entry = await this.getAction(action);
|
|
2898
|
-
return { action: entry, validation: validateTopoloActionInput(entry.inputSchema,
|
|
2898
|
+
return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input3) };
|
|
2899
2899
|
}
|
|
2900
|
-
async planAction(action,
|
|
2900
|
+
async planAction(action, input3 = {}, options = {}) {
|
|
2901
2901
|
const entry = await this.getAction(action);
|
|
2902
|
-
const validation = validateTopoloActionInput(entry.inputSchema,
|
|
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,
|
|
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,
|
|
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,
|
|
2939
|
+
return this.executeResolvedAction(entry, input3, options);
|
|
2940
2940
|
}
|
|
2941
|
-
async executeResolvedAction(entry,
|
|
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,
|
|
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,
|
|
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(
|
|
3054
|
-
if (!isRecord2(
|
|
3055
|
-
const resourceType = readString(
|
|
3056
|
-
const label = readString(
|
|
3057
|
-
const emptyLabel = readString(
|
|
3058
|
-
const resourceConcept = readString(
|
|
3059
|
-
const canonicalResourceType = readString(
|
|
3060
|
-
const defaultEntitlement = readString(
|
|
3061
|
-
const exceptionPolicy = readString(
|
|
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(
|
|
3076
|
-
if (!Array.isArray(
|
|
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
|
|
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(
|
|
3088
|
-
if (!isRecord2(
|
|
3089
|
-
const resourceType = readString(
|
|
3090
|
-
const resourceId = readString(
|
|
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(
|
|
3096
|
-
slug: readOptionalString(
|
|
3097
|
-
status: readOptionalString(
|
|
3098
|
-
organizationId: readOptionalString(
|
|
3099
|
-
metadata: isRecord2(
|
|
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(
|
|
3103
|
-
if (!Array.isArray(
|
|
3104
|
-
return
|
|
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
|
|
3231
|
-
if (!actionId || !name || !appId || !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:
|
|
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,
|
|
3281
|
-
const remaining = { ...
|
|
3282
|
-
const
|
|
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:
|
|
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:
|
|
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(
|
|
3477
|
-
const url = new URL(
|
|
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",
|
|
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,
|
|
3532
|
-
super("http_error", message ?? `HTTP ${status} from ${service}${
|
|
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 =
|
|
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(
|
|
3669
|
-
const url = new URL(
|
|
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",
|
|
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
|
|
3716
|
-
if (!existsSync(
|
|
3715
|
+
const path5 = configPath();
|
|
3716
|
+
if (!existsSync(path5)) return {};
|
|
3717
3717
|
try {
|
|
3718
|
-
const raw = await readFile(
|
|
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
|
|
3726
|
-
await mkdir(dirname(
|
|
3727
|
-
await writeFile(
|
|
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(
|
|
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
|
|
4050
|
+
for (const path5 of candidates) {
|
|
4050
4051
|
try {
|
|
4051
|
-
const pkg = JSON.parse(readFileSync(
|
|
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,
|
|
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:
|
|
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
|
|
4543
|
+
const input3 = parseJsonObject(opts.data || "{}");
|
|
4543
4544
|
const resource = parseResourceOption(opts.resourceType, opts.resourceId);
|
|
4544
|
-
const result = await topolo.client.callAction(action,
|
|
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
|
|
4614
|
-
if (!
|
|
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(
|
|
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 ${
|
|
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
|
|
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((
|
|
5286
|
-
const 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
|
|
5553
|
-
const scripts =
|
|
5554
|
-
const isRootPackage =
|
|
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(
|
|
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((
|
|
5568
|
-
...Object.keys(
|
|
5569
|
-
...Object.keys(
|
|
5570
|
-
...Object.keys(
|
|
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((
|
|
5577
|
+
evidence.packageJsons.flatMap((record2) => {
|
|
5577
5578
|
const specs = [
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
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(
|
|
5707
|
-
return
|
|
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
|
|
5797
|
-
if (!
|
|
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(
|
|
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
|
|
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(
|
|
5945
|
+
async function pathExists(path5) {
|
|
5945
5946
|
try {
|
|
5946
|
-
await access(
|
|
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:
|
|
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 =
|
|
9247
|
-
const
|
|
9248
|
-
const
|
|
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/
|
|
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
|
|
10057
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
10258
|
-
emit(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
10404
|
-
const evidencePath =
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
10826
|
-
const relativePath =
|
|
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 ===
|
|
10836
|
-
if (relativePath.includes(`${
|
|
10837
|
-
if (relativePath.startsWith(`scripts${
|
|
10838
|
-
return !RUNTIME_EXTENSIONS.has(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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()}${
|
|
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(
|
|
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}${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
11998
|
+
const path5 = join5(homedir2(), ".claude.json");
|
|
11590
11999
|
let config = {};
|
|
11591
|
-
if (existsSync4(
|
|
12000
|
+
if (existsSync4(path5)) {
|
|
11592
12001
|
try {
|
|
11593
|
-
config = JSON.parse(await
|
|
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
|
|
12015
|
+
await writeFile5(path5, `${JSON.stringify(config, null, 2)}
|
|
11607
12016
|
`, "utf8");
|
|
11608
12017
|
}
|
|
11609
12018
|
async function registerCodexViaConfig(mcp) {
|
|
11610
|
-
const
|
|
12019
|
+
const path5 = join5(homedir2(), ".codex", "config.toml");
|
|
11611
12020
|
let text = "";
|
|
11612
|
-
if (existsSync4(
|
|
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(
|
|
11629
|
-
await
|
|
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();
|