@siglume/api-sdk 0.7.6 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1351,6 +1351,9 @@ function parseListing(data) {
1351
1351
  short_description: stringOrNull(data.short_description),
1352
1352
  docs_url: stringOrNull(data.docs_url),
1353
1353
  support_contact: stringOrNull(data.support_contact),
1354
+ seller_display_name: stringOrNull(data.seller_display_name),
1355
+ seller_homepage_url: stringOrNull(data.seller_homepage_url),
1356
+ seller_social_url: stringOrNull(data.seller_social_url),
1354
1357
  review_status: stringOrNull(data.review_status),
1355
1358
  review_note: stringOrNull(data.review_note),
1356
1359
  submission_blockers: Array.isArray(data.submission_blockers) ? data.submission_blockers.filter((item) => typeof item === "string") : [],
@@ -2567,6 +2570,13 @@ var init_client = __esm({
2567
2570
  if (options.runtime_validation) {
2568
2571
  payload.runtime_validation = coerceMapping(options.runtime_validation, "runtime_validation");
2569
2572
  }
2573
+ if (options.oauth_credentials) {
2574
+ payload.oauth_credentials = Array.isArray(options.oauth_credentials) ? {
2575
+ items: options.oauth_credentials.map(
2576
+ (item, index) => coerceMapping(item, `oauth_credentials[${index}]`)
2577
+ )
2578
+ } : coerceMapping(options.oauth_credentials, "oauth_credentials");
2579
+ }
2570
2580
  if (options.metadata) {
2571
2581
  payload.metadata = coerceMapping(options.metadata, "metadata");
2572
2582
  }
@@ -2585,6 +2595,8 @@ var init_client = __esm({
2585
2595
  "docs_url",
2586
2596
  "documentation_url",
2587
2597
  "support_contact",
2598
+ "seller_homepage_url",
2599
+ "seller_social_url",
2588
2600
  "jurisdiction",
2589
2601
  "price_model",
2590
2602
  "price_value_minor",
@@ -2600,10 +2612,14 @@ var init_client = __esm({
2600
2612
  }
2601
2613
  const docsUrl = String(manifestPayload.docs_url ?? manifestPayload.documentation_url ?? "").trim();
2602
2614
  const supportContact = String(manifestPayload.support_contact ?? "").trim();
2603
- if (docsUrl || supportContact) {
2615
+ const sellerHomepageUrl = String(manifestPayload.seller_homepage_url ?? "").trim();
2616
+ const sellerSocialUrl = String(manifestPayload.seller_social_url ?? "").trim();
2617
+ if (docsUrl || supportContact || sellerHomepageUrl || sellerSocialUrl) {
2604
2618
  const publisherIdentity = {
2605
2619
  documentation_url: docsUrl || null,
2606
- support_contact: supportContact || null
2620
+ support_contact: supportContact || null,
2621
+ seller_homepage_url: sellerHomepageUrl || null,
2622
+ seller_social_url: sellerSocialUrl || null
2607
2623
  };
2608
2624
  payload.publisher_identity = publisherIdentity;
2609
2625
  payload.legal = { publisher_identity: publisherIdentity };
@@ -2617,36 +2633,30 @@ var init_client = __esm({
2617
2633
  return {
2618
2634
  listing_id,
2619
2635
  status: String(data.status ?? "draft"),
2636
+ registration_mode: stringOrNull(data.registration_mode),
2637
+ listing_status: stringOrNull(data.listing_status),
2620
2638
  auto_manifest: toRecord(data.auto_manifest),
2621
2639
  confidence: toRecord(data.confidence),
2622
2640
  validation_report: toRecord(data.validation_report),
2641
+ oauth_status: toRecord(data.oauth_status),
2623
2642
  review_url: stringOrNull(data.review_url),
2624
2643
  trace_id: meta.trace_id,
2625
2644
  request_id: meta.request_id
2626
2645
  };
2627
2646
  }
2628
2647
  async confirm_registration(listing_id, options = {}) {
2629
- const pending = this.pendingConfirmations.get(listing_id);
2630
- const manifestPayload = options.manifest ? coerceMapping(options.manifest, "manifest") : pending?.manifest ?? {};
2631
- const toolManualPayload = options.tool_manual ? coerceMapping(options.tool_manual, "tool_manual") : pending?.tool_manual ?? {};
2632
- const overrides = {};
2633
- for (const fieldName of ["name", "job_to_be_done"]) {
2634
- if (manifestPayload[fieldName]) {
2635
- overrides[fieldName] = manifestPayload[fieldName];
2636
- }
2637
- }
2638
- if (Object.keys(toolManualPayload).length > 0) {
2639
- overrides.tool_manual = toolManualPayload;
2640
- }
2648
+ void options;
2641
2649
  const payload = { approved: true };
2642
- if (Object.keys(overrides).length > 0) {
2643
- payload.overrides = overrides;
2644
- }
2645
2650
  const [data, meta] = await this.request("POST", `/market/capabilities/${listing_id}/confirm-auto-register`, { json_body: payload });
2646
2651
  this.pendingConfirmations.delete(listing_id);
2652
+ const checklist = isRecord(data.checklist) ? Object.fromEntries(
2653
+ Object.entries(data.checklist).map(([key, value]) => [key, Boolean(value)])
2654
+ ) : {};
2647
2655
  return {
2648
2656
  listing_id: String(data.listing_id ?? listing_id),
2649
2657
  status: String(data.status ?? ""),
2658
+ message: stringOrNull(data.message),
2659
+ checklist,
2650
2660
  release: toRecord(data.release),
2651
2661
  quality: parseRegistrationQuality(toRecord(data.quality)),
2652
2662
  trace_id: meta.trace_id,
@@ -7166,6 +7176,15 @@ async function loadProject(path = ".") {
7166
7176
  const tool_manual = tool_manual_path ? JSON.parse(await (0, import_promises.readFile)(tool_manual_path, "utf8")) : buildToolManualTemplate(manifest);
7167
7177
  const runtime_validation_path = await findRuntimeValidationPath(root_dir);
7168
7178
  const runtime_validation = runtime_validation_path ? await loadJsonObject(runtime_validation_path, "runtime_validation") : void 0;
7179
+ const oauth_credentials_path = await findOauthCredentialsPath(root_dir);
7180
+ let oauth_credentials;
7181
+ if (oauth_credentials_path) {
7182
+ const parsed = JSON.parse(await (0, import_promises.readFile)(oauth_credentials_path, "utf8"));
7183
+ if (!isRecord(parsed) && !Array.isArray(parsed)) {
7184
+ throw new SiglumeProjectError("oauth_credentials must be a JSON object or array");
7185
+ }
7186
+ oauth_credentials = parsed;
7187
+ }
7169
7188
  return {
7170
7189
  root_dir,
7171
7190
  adapter_path,
@@ -7174,9 +7193,110 @@ async function loadProject(path = ".") {
7174
7193
  tool_manual_path: tool_manual_path ?? void 0,
7175
7194
  tool_manual,
7176
7195
  runtime_validation_path: runtime_validation_path ?? void 0,
7177
- runtime_validation
7196
+ runtime_validation,
7197
+ oauth_credentials_path: oauth_credentials_path ?? void 0,
7198
+ oauth_credentials
7178
7199
  };
7179
7200
  }
7201
+ var OAUTH_PROVIDER_ALIASES = {
7202
+ x: "twitter",
7203
+ "x-twitter": "twitter",
7204
+ twitter: "twitter",
7205
+ slack: "slack",
7206
+ google: "google",
7207
+ gmail: "google",
7208
+ "google-drive": "google",
7209
+ "google-calendar": "google",
7210
+ github: "github",
7211
+ linear: "linear",
7212
+ notion: "notion"
7213
+ };
7214
+ function oauthProviderKeyFromRequirement(value) {
7215
+ const raw = String(value ?? "").trim().toLowerCase().replaceAll("_", "-");
7216
+ if (!raw) return null;
7217
+ if (OAUTH_PROVIDER_ALIASES[raw]) {
7218
+ return OAUTH_PROVIDER_ALIASES[raw];
7219
+ }
7220
+ for (const token of raw.replaceAll("/", "-").replaceAll(":", "-").split("-")) {
7221
+ const next = token.trim();
7222
+ if (OAUTH_PROVIDER_ALIASES[next]) {
7223
+ return OAUTH_PROVIDER_ALIASES[next];
7224
+ }
7225
+ }
7226
+ return null;
7227
+ }
7228
+ function requiredOauthProviders(requirements) {
7229
+ const providers = [];
7230
+ for (const item of requirements ?? []) {
7231
+ const providerKey = oauthProviderKeyFromRequirement(item);
7232
+ if (providerKey && !providers.includes(providerKey)) {
7233
+ providers.push(providerKey);
7234
+ }
7235
+ }
7236
+ return providers;
7237
+ }
7238
+ function oauthProviderRecordsMap(payload) {
7239
+ if (!payload) {
7240
+ return {};
7241
+ }
7242
+ const items = Array.isArray(payload) ? payload : Array.isArray(payload.items) ? payload.items : [payload];
7243
+ const resolved = {};
7244
+ for (const [index, item] of items.entries()) {
7245
+ if (!isRecord(item)) {
7246
+ throw new SiglumeProjectError(`oauth_credentials[${index}] must be a JSON object.`);
7247
+ }
7248
+ const providerKey = oauthProviderKeyFromRequirement(item.provider_key ?? item.provider);
7249
+ if (!providerKey) {
7250
+ throw new SiglumeProjectError(`oauth_credentials[${index}].provider_key is unsupported.`);
7251
+ }
7252
+ const clientId = String(item.client_id ?? "").trim();
7253
+ const clientSecret = String(item.client_secret ?? "").trim();
7254
+ if (!clientId || !clientSecret) {
7255
+ throw new SiglumeProjectError(`oauth_credentials[${index}] must include client_id and client_secret.`);
7256
+ }
7257
+ const rawScopes = item.required_scopes ?? item.scopes;
7258
+ let scopes = [];
7259
+ if (rawScopes == null) {
7260
+ scopes = [];
7261
+ } else if (!Array.isArray(rawScopes)) {
7262
+ throw new SiglumeProjectError(`oauth_credentials[${index}].required_scopes must be a JSON array.`);
7263
+ } else {
7264
+ scopes = rawScopes.map((scope) => String(scope ?? "").trim()).filter(Boolean);
7265
+ }
7266
+ resolved[providerKey] = {
7267
+ provider_key: providerKey,
7268
+ client_id: clientId,
7269
+ client_secret: clientSecret,
7270
+ required_scopes: scopes
7271
+ };
7272
+ }
7273
+ return resolved;
7274
+ }
7275
+ function canonicalOauthCredentialsPayload(payload) {
7276
+ const records = oauthProviderRecordsMap(payload);
7277
+ const providerKeys = Object.keys(records).sort();
7278
+ if (providerKeys.length === 0) {
7279
+ return void 0;
7280
+ }
7281
+ return {
7282
+ items: providerKeys.map((providerKey) => records[providerKey])
7283
+ };
7284
+ }
7285
+ function ensureRequiredOauthCredentials(project) {
7286
+ const requiredProviders = requiredOauthProviders(project.manifest.required_connected_accounts ?? []);
7287
+ if (requiredProviders.length === 0) {
7288
+ return;
7289
+ }
7290
+ const provided = new Set(Object.keys(oauthProviderRecordsMap(project.oauth_credentials)));
7291
+ const missing = requiredProviders.filter((provider) => !provided.has(provider));
7292
+ if (missing.length === 0) {
7293
+ return;
7294
+ }
7295
+ const path = project.oauth_credentials_path ?? (0, import_node_path.join)(project.root_dir, "oauth_credentials.json");
7296
+ throw new SiglumeProjectError(
7297
+ `${path} is required for OAuth-backed APIs. Missing provider seeds: ${missing.join(", ")}`
7298
+ );
7299
+ }
7180
7300
  async function validateProject(path = ".", deps = {}) {
7181
7301
  const project = await loadProject(path);
7182
7302
  const manifest_issues = await projectValidationIssues(project);
@@ -7213,17 +7333,31 @@ function ensureManifestPublisherIdentity(project) {
7213
7333
  const manifestPayload = project.manifest;
7214
7334
  const docsUrl = String(manifestPayload.docs_url ?? manifestPayload.documentation_url ?? "").trim();
7215
7335
  const supportContact = String(manifestPayload.support_contact ?? "").trim();
7336
+ const sellerHomepageUrl = String(manifestPayload.seller_homepage_url ?? "").trim();
7337
+ const sellerSocialUrl = String(manifestPayload.seller_social_url ?? "").trim();
7216
7338
  const jurisdiction = String(manifestPayload.jurisdiction ?? "").trim();
7217
7339
  const issues = [];
7218
7340
  if (!docsUrl) {
7219
7341
  issues.push("manifest.docs_url is required");
7220
7342
  } else if (looksLikePlaceholder(docsUrl)) {
7221
7343
  issues.push("manifest.docs_url must be replaced with your public documentation URL");
7344
+ } else if (!looksLikeHttpUrl(docsUrl)) {
7345
+ issues.push("manifest.docs_url must be an http(s) URL");
7346
+ } else if (looksLikeRootUrl(docsUrl)) {
7347
+ issues.push("manifest.docs_url must be a dedicated API usage page, not a root homepage URL");
7222
7348
  }
7223
7349
  if (!supportContact) {
7224
7350
  issues.push("manifest.support_contact is required");
7225
7351
  } else if (looksLikePlaceholder(supportContact)) {
7226
7352
  issues.push("manifest.support_contact must be replaced with your real support email or support URL");
7353
+ } else if (!looksLikeEmail(supportContact) && !looksLikeHttpUrl(supportContact)) {
7354
+ issues.push("manifest.support_contact must be a real email address or http(s) support URL");
7355
+ }
7356
+ if (sellerHomepageUrl && (looksLikePlaceholder(sellerHomepageUrl) || !looksLikeHttpUrl(sellerHomepageUrl))) {
7357
+ issues.push("manifest.seller_homepage_url must be a real http(s) official homepage URL when provided");
7358
+ }
7359
+ if (sellerSocialUrl && (looksLikePlaceholder(sellerSocialUrl) || !looksLikeHttpUrl(sellerSocialUrl))) {
7360
+ issues.push("manifest.seller_social_url must be a real http(s) official social/profile URL when provided");
7227
7361
  }
7228
7362
  if (!jurisdiction) issues.push("manifest.jurisdiction is required");
7229
7363
  if (issues.length > 0) {
@@ -7235,7 +7369,25 @@ ${issues.map((issue2) => `- ${issue2}`).join("\n")}`
7235
7369
  }
7236
7370
  function looksLikePlaceholder(value) {
7237
7371
  const normalized = value.trim().toLowerCase();
7238
- return !normalized || normalized.includes("example.com") || normalized.startsWith("replace-with-") || normalized.startsWith("your-") || normalized.includes("your-domain") || normalized.includes("localhost") || normalized.includes("127.0.0.1") || normalized.includes("0.0.0.0");
7372
+ return !normalized || normalized.includes("example.com") || normalized.includes("example.net") || normalized.includes("example.org") || normalized.startsWith("replace-with-") || normalized.startsWith("your-") || normalized.includes("your-domain") || normalized.includes("localhost") || normalized.includes("127.0.0.1") || normalized.includes("0.0.0.0");
7373
+ }
7374
+ function looksLikeHttpUrl(value) {
7375
+ try {
7376
+ const parsed = new URL(value.trim());
7377
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
7378
+ } catch {
7379
+ return false;
7380
+ }
7381
+ }
7382
+ function looksLikeRootUrl(value) {
7383
+ if (!looksLikeHttpUrl(value)) return false;
7384
+ const parsed = new URL(value.trim());
7385
+ return parsed.pathname.replace(/^\/+|\/+$/g, "") === "";
7386
+ }
7387
+ function looksLikeEmail(value) {
7388
+ const normalized = value.trim();
7389
+ const domain = normalized.split("@").at(-1) ?? "";
7390
+ return normalized.includes("@") && !normalized.includes(" ") && domain.includes(".") && !normalized.startsWith("@");
7239
7391
  }
7240
7392
  function runtimePlaceholderIssues(runtimeValidation) {
7241
7393
  const issues = [];
@@ -7295,6 +7447,9 @@ async function registrationPreflight(project, client) {
7295
7447
  const manifestIssues = await projectValidationIssues(project);
7296
7448
  const [toolManualValid, toolManualIssues] = validate_tool_manual(project.tool_manual);
7297
7449
  const remoteQuality = await client.preview_quality_score(project.tool_manual);
7450
+ const requiredOauthProvidersList = requiredOauthProviders(project.manifest.required_connected_accounts ?? []);
7451
+ const oauthProviderRecords = oauthProviderRecordsMap(project.oauth_credentials);
7452
+ const missingOauthProviders = requiredOauthProvidersList.filter((provider) => !oauthProviderRecords[provider]);
7298
7453
  const blockingToolManualIssues = toolManualIssues.filter((issue2) => issue2.severity === "error");
7299
7454
  const errors = [
7300
7455
  ...manifestIssues.map((issue2) => String(issue2)),
@@ -7306,11 +7461,17 @@ async function registrationPreflight(project, client) {
7306
7461
  if (!remoteQualityOk(remoteQuality)) {
7307
7462
  errors.push(`remote Tool Manual quality is not publishable: ${remoteQuality.grade} (${remoteQuality.overall_score}/100)`);
7308
7463
  }
7464
+ if (missingOauthProviders.length > 0) {
7465
+ errors.push(`oauth_credentials.json is required for OAuth-backed APIs: ${missingOauthProviders.join(", ")}`);
7466
+ }
7309
7467
  const preflight = {
7310
7468
  manifest_issues: manifestIssues,
7311
7469
  tool_manual_valid: toolManualValid,
7312
7470
  tool_manual_issues: toolManualIssues.map((issue2) => toJsonable(issue2)),
7313
7471
  remote_quality: toJsonable(remoteQuality),
7472
+ required_oauth_providers: requiredOauthProvidersList,
7473
+ oauth_credentials_path: project.oauth_credentials_path ?? null,
7474
+ oauth_missing_providers: missingOauthProviders,
7314
7475
  ok: errors.length === 0
7315
7476
  };
7316
7477
  if (errors.length > 0) {
@@ -7326,6 +7487,7 @@ async function runRegistration(path = ".", options = {}, deps = {}) {
7326
7487
  ensureExplicitToolManual(project);
7327
7488
  ensureManifestPublisherIdentity(project);
7328
7489
  ensureRuntimeValidationReady(project);
7490
+ ensureRequiredOauthCredentials(project);
7329
7491
  const client = await createClient(deps);
7330
7492
  const preflight = await registrationPreflight(project, client);
7331
7493
  let developerPortalPreflight = null;
@@ -7334,18 +7496,20 @@ async function runRegistration(path = ".", options = {}, deps = {}) {
7334
7496
  const verifiedDestination = portal.payout_readiness?.verified_destination;
7335
7497
  if (verifiedDestination !== true) {
7336
7498
  throw new SiglumeProjectError(
7337
- "Paid API registration requires a verified Polygon payout destination. Open https://siglume.com/owner/publish or call GET /v1/market/developer/portal until payout_readiness.verified_destination is true."
7499
+ "Paid API registration requires a verified Polygon payout destination. Open https://siglume.com/owner/credits/payout and confirm the embedded-wallet payout token, or call GET /v1/market/developer/portal until payout_readiness.verified_destination is true."
7338
7500
  );
7339
7501
  }
7340
7502
  developerPortalPreflight = toJsonable(portal);
7341
7503
  }
7342
7504
  const receipt = await client.auto_register(project.manifest, project.tool_manual, {
7343
- runtime_validation: project.runtime_validation
7505
+ runtime_validation: project.runtime_validation,
7506
+ oauth_credentials: canonicalOauthCredentialsPayload(project.oauth_credentials)
7344
7507
  });
7345
7508
  const result = {
7346
7509
  receipt: toJsonable(receipt),
7347
7510
  registration_preflight: preflight,
7348
- runtime_validation_path: project.runtime_validation_path ?? null
7511
+ runtime_validation_path: project.runtime_validation_path ?? null,
7512
+ oauth_credentials_path: project.oauth_credentials_path ?? null
7349
7513
  };
7350
7514
  if (developerPortalPreflight) {
7351
7515
  result.developer_portal_preflight = developerPortalPreflight;
@@ -7360,6 +7524,37 @@ async function runRegistration(path = ".", options = {}, deps = {}) {
7360
7524
  }
7361
7525
  return result;
7362
7526
  }
7527
+ async function runPreflight(path = ".", deps = {}) {
7528
+ const project = await loadProject(path);
7529
+ ensureExplicitToolManual(project);
7530
+ ensureManifestPublisherIdentity(project);
7531
+ ensureRuntimeValidationReady(project);
7532
+ ensureRequiredOauthCredentials(project);
7533
+ const client = await createClient(deps);
7534
+ const preflight = await registrationPreflight(project, client);
7535
+ let developerPortalPreflight = null;
7536
+ if (String(project.manifest.price_model ?? "free").toLowerCase() !== "free") {
7537
+ const portal = await client.get_developer_portal();
7538
+ const verifiedDestination = portal.payout_readiness?.verified_destination;
7539
+ if (verifiedDestination !== true) {
7540
+ throw new SiglumeProjectError(
7541
+ "Paid API registration requires a verified Polygon payout destination. Open https://siglume.com/owner/credits/payout and confirm the embedded-wallet payout token, or call GET /v1/market/developer/portal until payout_readiness.verified_destination is true."
7542
+ );
7543
+ }
7544
+ developerPortalPreflight = toJsonable(portal);
7545
+ }
7546
+ const result = {
7547
+ ok: true,
7548
+ adapter_path: project.adapter_path,
7549
+ registration_preflight: preflight,
7550
+ runtime_validation_path: project.runtime_validation_path ?? null,
7551
+ oauth_credentials_path: project.oauth_credentials_path ?? null
7552
+ };
7553
+ if (developerPortalPreflight) {
7554
+ result.developer_portal_preflight = developerPortalPreflight;
7555
+ }
7556
+ return result;
7557
+ }
7363
7558
  async function createSupportCaseReport(options, deps = {}) {
7364
7559
  const client = await createClient(deps);
7365
7560
  const supportCase = await client.create_support_case(options.subject, options.body, { trace_id: options.trace_id });
@@ -7406,8 +7601,12 @@ async function writeInitTemplate(template, destination) {
7406
7601
  const manifest_path = (0, import_node_path.join)(root, "manifest.json");
7407
7602
  const tool_manual_path = (0, import_node_path.join)(root, "tool_manual.json");
7408
7603
  const runtime_validation_path = (0, import_node_path.join)(root, "runtime_validation.json");
7604
+ const gitignore_path = (0, import_node_path.join)(root, ".gitignore");
7409
7605
  const readme_path = (0, import_node_path.join)(root, "README.md");
7410
- for (const filePath of [adapter_path, manifest_path, tool_manual_path, runtime_validation_path, readme_path]) {
7606
+ const docs_dir = (0, import_node_path.join)(root, "docs");
7607
+ await (0, import_promises.mkdir)(docs_dir, { recursive: true });
7608
+ const docs_usage_path = (0, import_node_path.join)(docs_dir, "api-usage.md");
7609
+ for (const filePath of [adapter_path, manifest_path, tool_manual_path, runtime_validation_path, docs_usage_path, readme_path]) {
7411
7610
  if ((0, import_node_fs.existsSync)(filePath)) {
7412
7611
  throw new SiglumeProjectError(`${(0, import_node_path.basename)(filePath)} already exists in ${root}`);
7413
7612
  }
@@ -7418,8 +7617,10 @@ async function writeInitTemplate(template, destination) {
7418
7617
  await (0, import_promises.writeFile)(manifest_path, renderJson(manifest), "utf8");
7419
7618
  await (0, import_promises.writeFile)(tool_manual_path, renderJson(toolManual), "utf8");
7420
7619
  await (0, import_promises.writeFile)(runtime_validation_path, renderJson(buildRuntimeValidationTemplate(toolManual)), "utf8");
7620
+ await (0, import_promises.writeFile)(docs_usage_path, apiUsageDocsTemplate(manifest), "utf8");
7621
+ await writeOrMergeGitignore(gitignore_path);
7421
7622
  await (0, import_promises.writeFile)(readme_path, readmeTemplate(template), "utf8");
7422
- return [adapter_path, manifest_path, tool_manual_path, runtime_validation_path, readme_path];
7623
+ return [adapter_path, manifest_path, tool_manual_path, runtime_validation_path, docs_usage_path, gitignore_path, readme_path];
7423
7624
  }
7424
7625
  async function listOperationCatalog(options = {}, deps = {}) {
7425
7626
  const resolvedAgentId = String(options.agent_id ?? "").trim();
@@ -7813,12 +8014,19 @@ function operationReadmeTemplate(operation, manifest, warning) {
7813
8014
  "- `stubs.ts`: mock fallback used when `SIGLUME_API_KEY` is not set",
7814
8015
  "- `manifest.json`: reviewable manifest snapshot",
7815
8016
  "- `tool_manual.json`: machine-generated ToolManual scaffold",
7816
- "- `runtime_validation.json`: public endpoint and review-key checks used by auto-register",
8017
+ "- `runtime_validation.json`: local public endpoint and review-key checks used by auto-register",
8018
+ "- `docs/api-usage.md`: publishable API usage guide template for `docs_url`",
8019
+ "- `.gitignore`: keeps runtime review keys and OAuth client secrets out of Git",
7817
8020
  "- `tests/test_adapter.ts`: smoke test for `AppTestHarness`",
7818
8021
  "",
7819
8022
  "Before registering, replace all generated placeholders:",
7820
- "- In `adapter.ts` and `manifest.json`, replace `docs_url` and `support_contact` with your public documentation and support contact.",
7821
- "- In `runtime_validation.json`, replace the public URL and review-key placeholders.",
8023
+ "- In `adapter.ts` and `manifest.json`, replace `docs_url` with a dedicated public API usage guide, not a homepage.",
8024
+ "- Replace `support_contact` with a real support email address or public support URL.",
8025
+ "- Optional `seller_homepage_url` is the seller's official site and can stay blank.",
8026
+ "- In the local `runtime_validation.json`, replace the public URL and review-key placeholders.",
8027
+ "- If the API uses seller-side OAuth, create a local `oauth_credentials.json` next to the adapter.",
8028
+ "- Do not commit real review keys or OAuth client secrets; the generated `.gitignore` excludes those files.",
8029
+ "- Because `runtime_validation.json` is ignored, GitHub samples do not commit review-key values.",
7822
8030
  "",
7823
8031
  "## Commands",
7824
8032
  "",
@@ -7830,16 +8038,108 @@ function operationReadmeTemplate(operation, manifest, warning) {
7830
8038
  "siglume score . --offline",
7831
8039
  "```",
7832
8040
  "",
7833
- "After placeholders are replaced and `SIGLUME_API_KEY` is set, run the server-aligned checks and register:",
8041
+ "After placeholders are replaced and `SIGLUME_API_KEY` is issued from Developer Portal -> CLI / API keys, run the server-aligned checks:",
7834
8042
  "",
7835
8043
  "```bash",
7836
8044
  "siglume validate .",
7837
8045
  "siglume score . --remote",
8046
+ "siglume preflight .",
8047
+ "siglume register .",
8048
+ "# inspect the draft, then explicitly approve publish:",
7838
8049
  "siglume register . --confirm",
7839
8050
  "```",
7840
8051
  ""
7841
8052
  ].join("\n");
7842
8053
  }
8054
+ function apiUsageDocsTemplate(manifest) {
8055
+ const name = String(manifest.name ?? manifest.capability_key ?? "Siglume API");
8056
+ const capabilityKey = String(manifest.capability_key ?? "replace-with-capability-key");
8057
+ const jobToBeDone = String(manifest.job_to_be_done ?? "Describe what this API lets an agent do.");
8058
+ const permissionClass = String(manifest.permission_class ?? "read-only");
8059
+ const priceModel = String(manifest.price_model ?? "free");
8060
+ const requiredAccounts = (manifest.required_connected_accounts ?? []).join(", ") || "none";
8061
+ const supportContact = String(manifest.support_contact ?? "replace-with-support-contact");
8062
+ return [
8063
+ `# ${name} API Usage Guide`,
8064
+ "",
8065
+ `This page is the dedicated public usage guide for the Siglume API listing \`${capabilityKey}\`.`,
8066
+ "Publish this page at an anonymous HTTP 200 URL and use that URL as `docs_url`.",
8067
+ "",
8068
+ "Do not use your company homepage as `docs_url`; keep seller/company homepages in `seller_homepage_url`.",
8069
+ "",
8070
+ "## What This API Does",
8071
+ "",
8072
+ jobToBeDone,
8073
+ "",
8074
+ "## Permission Model",
8075
+ "",
8076
+ `- Permission class: \`${permissionClass}\``,
8077
+ `- Price model: \`${priceModel}\``,
8078
+ `- Required connected accounts: \`${requiredAccounts}\``,
8079
+ "",
8080
+ "## Inputs",
8081
+ "",
8082
+ "Describe the request fields your API accepts. Keep this aligned with `tool_manual.json` and `runtime_validation.json`.",
8083
+ "",
8084
+ "## Outputs",
8085
+ "",
8086
+ "Describe the response fields your API returns. Include the fields in `runtime_validation.expected_response_fields`.",
8087
+ "",
8088
+ "## Errors And Support",
8089
+ "",
8090
+ "Explain common error messages and how an owner should recover.",
8091
+ "",
8092
+ `Support contact: ${supportContact}`,
8093
+ ""
8094
+ ].join("\n");
8095
+ }
8096
+ function generatedGitignore() {
8097
+ return [
8098
+ "# Local secrets and registration-only runtime checks.",
8099
+ ".env",
8100
+ ".env.*",
8101
+ "!.env.example",
8102
+ "runtime_validation.json",
8103
+ "runtime-validation.json",
8104
+ "oauth_credentials.json",
8105
+ "oauth-credentials.json",
8106
+ "",
8107
+ "# Python / test artifacts.",
8108
+ "__pycache__/",
8109
+ "*.py[cod]",
8110
+ ".pytest_cache/",
8111
+ ".mypy_cache/",
8112
+ ".coverage",
8113
+ "htmlcov/",
8114
+ "dist/",
8115
+ "build/",
8116
+ "*.egg-info/",
8117
+ "",
8118
+ "# JavaScript tooling if this project also uses TypeScript helpers.",
8119
+ "node_modules/",
8120
+ "coverage/",
8121
+ ""
8122
+ ].join("\n");
8123
+ }
8124
+ async function writeOrMergeGitignore(filePath) {
8125
+ const generated = generatedGitignore();
8126
+ if (!(0, import_node_fs.existsSync)(filePath)) {
8127
+ await (0, import_promises.writeFile)(filePath, generated, "utf8");
8128
+ return;
8129
+ }
8130
+ const existing = await (0, import_promises.readFile)(filePath, "utf8");
8131
+ const existingEntries = new Set(existing.split(/\r?\n/).map((line) => line.trim()));
8132
+ const additions = generated.split(/\r?\n/).filter((line) => line.trim() && !existingEntries.has(line.trim()));
8133
+ if (additions.length === 0) {
8134
+ return;
8135
+ }
8136
+ const prefix = existing.endsWith("\n") ? existing : `${existing}
8137
+ `;
8138
+ await (0, import_promises.writeFile)(filePath, `${prefix}
8139
+ # Siglume generated ignores.
8140
+ ${additions.join("\n")}
8141
+ `, "utf8");
8142
+ }
7843
8143
  async function writeOperationTemplate(operation_key, destination, options = {}, deps = {}) {
7844
8144
  const root = (0, import_node_path.resolve)(destination);
7845
8145
  await (0, import_promises.mkdir)(root, { recursive: true });
@@ -7850,9 +8150,22 @@ async function writeOperationTemplate(operation_key, destination, options = {},
7850
8150
  const manifest_path = (0, import_node_path.join)(root, "manifest.json");
7851
8151
  const tool_manual_path = (0, import_node_path.join)(root, "tool_manual.json");
7852
8152
  const runtime_validation_path = (0, import_node_path.join)(root, "runtime_validation.json");
8153
+ const gitignore_path = (0, import_node_path.join)(root, ".gitignore");
7853
8154
  const readme_path = (0, import_node_path.join)(root, "README.md");
7854
8155
  const test_path = (0, import_node_path.join)(testsDir, "test_adapter.ts");
7855
- for (const filePath of [adapter_path, stubs_path, manifest_path, tool_manual_path, runtime_validation_path, readme_path, test_path]) {
8156
+ const docs_dir = (0, import_node_path.join)(root, "docs");
8157
+ await (0, import_promises.mkdir)(docs_dir, { recursive: true });
8158
+ const docs_usage_path = (0, import_node_path.join)(docs_dir, "api-usage.md");
8159
+ for (const filePath of [
8160
+ adapter_path,
8161
+ stubs_path,
8162
+ manifest_path,
8163
+ tool_manual_path,
8164
+ runtime_validation_path,
8165
+ docs_usage_path,
8166
+ readme_path,
8167
+ test_path
8168
+ ]) {
7856
8169
  if ((0, import_node_fs.existsSync)(filePath)) {
7857
8170
  throw new SiglumeProjectError(`${(0, import_node_path.basename)(filePath)} already exists in ${root}`);
7858
8171
  }
@@ -7880,10 +8193,22 @@ async function writeOperationTemplate(operation_key, destination, options = {},
7880
8193
  await (0, import_promises.writeFile)(manifest_path, renderJson(manifest), "utf8");
7881
8194
  await (0, import_promises.writeFile)(tool_manual_path, renderJson(tool_manual), "utf8");
7882
8195
  await (0, import_promises.writeFile)(runtime_validation_path, renderJson(buildRuntimeValidationTemplate(tool_manual)), "utf8");
8196
+ await (0, import_promises.writeFile)(docs_usage_path, apiUsageDocsTemplate(manifest), "utf8");
8197
+ await writeOrMergeGitignore(gitignore_path);
7883
8198
  await (0, import_promises.writeFile)(readme_path, operationReadmeTemplate(operation, manifest, warning), "utf8");
7884
8199
  await (0, import_promises.writeFile)(test_path, operationTestSource(operation), "utf8");
7885
8200
  return {
7886
- files: [adapter_path, stubs_path, manifest_path, tool_manual_path, runtime_validation_path, readme_path, test_path],
8201
+ files: [
8202
+ adapter_path,
8203
+ stubs_path,
8204
+ manifest_path,
8205
+ tool_manual_path,
8206
+ runtime_validation_path,
8207
+ docs_usage_path,
8208
+ gitignore_path,
8209
+ readme_path,
8210
+ test_path
8211
+ ],
7887
8212
  operation,
7888
8213
  report: {
7889
8214
  tool_manual_valid,
@@ -8032,6 +8357,15 @@ async function findRuntimeValidationPath(root_dir) {
8032
8357
  }
8033
8358
  return null;
8034
8359
  }
8360
+ async function findOauthCredentialsPath(root_dir) {
8361
+ for (const name of ["oauth_credentials.json", "oauth-credentials.json"]) {
8362
+ const candidate = (0, import_node_path.join)(root_dir, name);
8363
+ if ((0, import_node_fs.existsSync)(candidate)) {
8364
+ return candidate;
8365
+ }
8366
+ }
8367
+ return null;
8368
+ }
8035
8369
  async function loadJsonObject(path, label) {
8036
8370
  let payload;
8037
8371
  try {
@@ -8255,11 +8589,18 @@ function readmeTemplate(template) {
8255
8589
  "- `adapter.ts`: your AppAdapter implementation",
8256
8590
  "- `manifest.json`: serialized AppManifest snapshot",
8257
8591
  "- `tool_manual.json`: editable ToolManual draft for validation and registration",
8258
- "- `runtime_validation.json`: live API smoke-test contract used during registration",
8592
+ "- `runtime_validation.json`: local live API smoke-test contract used during registration",
8593
+ "- `docs/api-usage.md`: publish this page and use its public URL as `docs_url`",
8594
+ "- `.gitignore`: keeps runtime review keys and OAuth client secrets out of Git",
8259
8595
  "",
8260
8596
  "Before registering, replace all generated placeholders:",
8261
- "- In `adapter.ts` and `manifest.json`, replace `docs_url` and `support_contact` with your public documentation and support contact.",
8262
- "- In `runtime_validation.json`, replace the public URL and review-key placeholders.",
8597
+ "- In `adapter.ts` and `manifest.json`, replace `docs_url` with a dedicated public API usage guide, not a homepage.",
8598
+ "- Replace `support_contact` with a real support email address or public support URL.",
8599
+ "- Optional `seller_homepage_url` is the seller's official site and can stay blank.",
8600
+ "- In the local `runtime_validation.json`, replace the public URL and review-key placeholders.",
8601
+ "- If the API uses seller-side OAuth, create a local `oauth_credentials.json` next to the adapter.",
8602
+ "- Do not commit real review keys or OAuth client secrets; the generated `.gitignore` excludes those files.",
8603
+ "- Because `runtime_validation.json` is ignored, GitHub samples do not commit review-key values.",
8263
8604
  "",
8264
8605
  "Suggested workflow:",
8265
8606
  "",
@@ -8270,11 +8611,14 @@ function readmeTemplate(template) {
8270
8611
  "siglume score . --offline",
8271
8612
  "```",
8272
8613
  "",
8273
- "After placeholders are replaced and `SIGLUME_API_KEY` is set, run the server-aligned checks and register:",
8614
+ "After placeholders are replaced and `SIGLUME_API_KEY` is issued from Developer Portal -> CLI / API keys, run the server-aligned checks:",
8274
8615
  "",
8275
8616
  "```bash",
8276
8617
  "siglume validate .",
8277
8618
  "siglume score . --remote",
8619
+ "siglume preflight .",
8620
+ "siglume register .",
8621
+ "# inspect the draft, then explicitly approve publish:",
8278
8622
  "siglume register . --confirm",
8279
8623
  "```",
8280
8624
  ""
@@ -8443,18 +8787,52 @@ async function runCli(argv, deps = {}) {
8443
8787
  throw new SiglumeProjectError("Score failed.");
8444
8788
  }
8445
8789
  });
8446
- program.command("register").option("--confirm", "confirm the draft registration immediately and submit it for review", false).option("--submit-review", "submit the draft for review if --confirm is not used", false).option("--json", "emit machine-readable JSON", false).argument("[path]", ".", "project path").action(async (path, options) => {
8790
+ program.command("preflight").option("--json", "emit machine-readable JSON", false).argument("[path]", ".", "project path").action(async (path, options) => {
8791
+ const report = await runPreflight(path, deps);
8792
+ if (options.json) {
8793
+ emit(stdout, renderJson(report));
8794
+ return;
8795
+ }
8796
+ emit(stdout, "Preflight passed.");
8797
+ const preflight = report.registration_preflight;
8798
+ if (preflight?.remote_quality) {
8799
+ emit(stdout, `preflight_quality: ${preflight.remote_quality.grade} (${preflight.remote_quality.overall_score}/100)`);
8800
+ }
8801
+ if (report.runtime_validation_path) emit(stdout, `runtime_validation_path: ${String(report.runtime_validation_path)}`);
8802
+ if (report.oauth_credentials_path) emit(stdout, `oauth_credentials_path: ${String(report.oauth_credentials_path)}`);
8803
+ });
8804
+ program.command("register").option("--confirm", "confirm the draft registration immediately and publish it when the self-serve checks pass", false).option("--submit-review", "legacy alias: publish immediately if your environment still routes through submit-review", false).option("--json", "emit machine-readable JSON", false).argument("[path]", ".", "project path").action(async (path, options) => {
8447
8805
  const report = await runRegistration(path, { confirm: options.confirm, submit_review: options.submitReview }, deps);
8448
8806
  if (options.json) {
8449
8807
  emit(stdout, renderJson(report));
8450
8808
  } else {
8451
8809
  const receipt = report.receipt;
8452
- emit(stdout, "Draft listing created.");
8810
+ if (report.confirmation) {
8811
+ emit(stdout, "Listing published.");
8812
+ } else if (report.review) {
8813
+ emit(stdout, "Listing published via legacy submit-review alias.");
8814
+ } else if (receipt.registration_mode === "upgrade") {
8815
+ emit(stdout, "Upgrade staged.");
8816
+ } else if (receipt.registration_mode === "refresh") {
8817
+ emit(stdout, "Draft refreshed.");
8818
+ } else {
8819
+ emit(stdout, "Draft listing created.");
8820
+ }
8453
8821
  emit(stdout, `listing_id: ${receipt.listing_id}`);
8454
- emit(stdout, `status: ${receipt.status}`);
8822
+ emit(stdout, `receipt_status: ${receipt.status}`);
8823
+ if (receipt.listing_status) emit(stdout, `listing_status: ${receipt.listing_status}`);
8824
+ if (receipt.oauth_status) emit(stdout, `oauth_configured: ${Boolean(receipt.oauth_status.configured)}`);
8455
8825
  if (receipt.review_url) emit(stdout, `review_url: ${receipt.review_url}`);
8456
8826
  if (receipt.trace_id) emit(stdout, `trace_id: ${receipt.trace_id}`);
8457
8827
  if (receipt.request_id) emit(stdout, `request_id: ${receipt.request_id}`);
8828
+ if (report.confirmation) {
8829
+ const confirmation = report.confirmation;
8830
+ if (confirmation.status) emit(stdout, `confirmation_status: ${confirmation.status}`);
8831
+ if (confirmation.release?.release_status) emit(stdout, `release_status: ${confirmation.release.release_status}`);
8832
+ } else if (report.review) {
8833
+ const review = report.review;
8834
+ if (review.status) emit(stdout, `publish_status: ${review.status}`);
8835
+ }
8458
8836
  const preflight = report.registration_preflight;
8459
8837
  if (preflight?.remote_quality) {
8460
8838
  emit(stdout, `preflight_quality: ${preflight.remote_quality.grade} (${preflight.remote_quality.overall_score}/100)`);