@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.
@@ -1352,6 +1352,9 @@ function parseListing(data) {
1352
1352
  short_description: stringOrNull(data.short_description),
1353
1353
  docs_url: stringOrNull(data.docs_url),
1354
1354
  support_contact: stringOrNull(data.support_contact),
1355
+ seller_display_name: stringOrNull(data.seller_display_name),
1356
+ seller_homepage_url: stringOrNull(data.seller_homepage_url),
1357
+ seller_social_url: stringOrNull(data.seller_social_url),
1355
1358
  review_status: stringOrNull(data.review_status),
1356
1359
  review_note: stringOrNull(data.review_note),
1357
1360
  submission_blockers: Array.isArray(data.submission_blockers) ? data.submission_blockers.filter((item) => typeof item === "string") : [],
@@ -2568,6 +2571,13 @@ var init_client = __esm({
2568
2571
  if (options.runtime_validation) {
2569
2572
  payload.runtime_validation = coerceMapping(options.runtime_validation, "runtime_validation");
2570
2573
  }
2574
+ if (options.oauth_credentials) {
2575
+ payload.oauth_credentials = Array.isArray(options.oauth_credentials) ? {
2576
+ items: options.oauth_credentials.map(
2577
+ (item, index) => coerceMapping(item, `oauth_credentials[${index}]`)
2578
+ )
2579
+ } : coerceMapping(options.oauth_credentials, "oauth_credentials");
2580
+ }
2571
2581
  if (options.metadata) {
2572
2582
  payload.metadata = coerceMapping(options.metadata, "metadata");
2573
2583
  }
@@ -2586,6 +2596,8 @@ var init_client = __esm({
2586
2596
  "docs_url",
2587
2597
  "documentation_url",
2588
2598
  "support_contact",
2599
+ "seller_homepage_url",
2600
+ "seller_social_url",
2589
2601
  "jurisdiction",
2590
2602
  "price_model",
2591
2603
  "price_value_minor",
@@ -2601,10 +2613,14 @@ var init_client = __esm({
2601
2613
  }
2602
2614
  const docsUrl = String(manifestPayload.docs_url ?? manifestPayload.documentation_url ?? "").trim();
2603
2615
  const supportContact = String(manifestPayload.support_contact ?? "").trim();
2604
- if (docsUrl || supportContact) {
2616
+ const sellerHomepageUrl = String(manifestPayload.seller_homepage_url ?? "").trim();
2617
+ const sellerSocialUrl = String(manifestPayload.seller_social_url ?? "").trim();
2618
+ if (docsUrl || supportContact || sellerHomepageUrl || sellerSocialUrl) {
2605
2619
  const publisherIdentity = {
2606
2620
  documentation_url: docsUrl || null,
2607
- support_contact: supportContact || null
2621
+ support_contact: supportContact || null,
2622
+ seller_homepage_url: sellerHomepageUrl || null,
2623
+ seller_social_url: sellerSocialUrl || null
2608
2624
  };
2609
2625
  payload.publisher_identity = publisherIdentity;
2610
2626
  payload.legal = { publisher_identity: publisherIdentity };
@@ -2618,36 +2634,30 @@ var init_client = __esm({
2618
2634
  return {
2619
2635
  listing_id,
2620
2636
  status: String(data.status ?? "draft"),
2637
+ registration_mode: stringOrNull(data.registration_mode),
2638
+ listing_status: stringOrNull(data.listing_status),
2621
2639
  auto_manifest: toRecord(data.auto_manifest),
2622
2640
  confidence: toRecord(data.confidence),
2623
2641
  validation_report: toRecord(data.validation_report),
2642
+ oauth_status: toRecord(data.oauth_status),
2624
2643
  review_url: stringOrNull(data.review_url),
2625
2644
  trace_id: meta.trace_id,
2626
2645
  request_id: meta.request_id
2627
2646
  };
2628
2647
  }
2629
2648
  async confirm_registration(listing_id, options = {}) {
2630
- const pending = this.pendingConfirmations.get(listing_id);
2631
- const manifestPayload = options.manifest ? coerceMapping(options.manifest, "manifest") : pending?.manifest ?? {};
2632
- const toolManualPayload = options.tool_manual ? coerceMapping(options.tool_manual, "tool_manual") : pending?.tool_manual ?? {};
2633
- const overrides = {};
2634
- for (const fieldName of ["name", "job_to_be_done"]) {
2635
- if (manifestPayload[fieldName]) {
2636
- overrides[fieldName] = manifestPayload[fieldName];
2637
- }
2638
- }
2639
- if (Object.keys(toolManualPayload).length > 0) {
2640
- overrides.tool_manual = toolManualPayload;
2641
- }
2649
+ void options;
2642
2650
  const payload = { approved: true };
2643
- if (Object.keys(overrides).length > 0) {
2644
- payload.overrides = overrides;
2645
- }
2646
2651
  const [data, meta] = await this.request("POST", `/market/capabilities/${listing_id}/confirm-auto-register`, { json_body: payload });
2647
2652
  this.pendingConfirmations.delete(listing_id);
2653
+ const checklist = isRecord(data.checklist) ? Object.fromEntries(
2654
+ Object.entries(data.checklist).map(([key, value]) => [key, Boolean(value)])
2655
+ ) : {};
2648
2656
  return {
2649
2657
  listing_id: String(data.listing_id ?? listing_id),
2650
2658
  status: String(data.status ?? ""),
2659
+ message: stringOrNull(data.message),
2660
+ checklist,
2651
2661
  release: toRecord(data.release),
2652
2662
  quality: parseRegistrationQuality(toRecord(data.quality)),
2653
2663
  trace_id: meta.trace_id,
@@ -7172,6 +7182,15 @@ async function loadProject(path = ".") {
7172
7182
  const tool_manual = tool_manual_path ? JSON.parse(await (0, import_promises.readFile)(tool_manual_path, "utf8")) : buildToolManualTemplate(manifest);
7173
7183
  const runtime_validation_path = await findRuntimeValidationPath(root_dir);
7174
7184
  const runtime_validation = runtime_validation_path ? await loadJsonObject(runtime_validation_path, "runtime_validation") : void 0;
7185
+ const oauth_credentials_path = await findOauthCredentialsPath(root_dir);
7186
+ let oauth_credentials;
7187
+ if (oauth_credentials_path) {
7188
+ const parsed = JSON.parse(await (0, import_promises.readFile)(oauth_credentials_path, "utf8"));
7189
+ if (!isRecord(parsed) && !Array.isArray(parsed)) {
7190
+ throw new SiglumeProjectError("oauth_credentials must be a JSON object or array");
7191
+ }
7192
+ oauth_credentials = parsed;
7193
+ }
7175
7194
  return {
7176
7195
  root_dir,
7177
7196
  adapter_path,
@@ -7180,9 +7199,110 @@ async function loadProject(path = ".") {
7180
7199
  tool_manual_path: tool_manual_path ?? void 0,
7181
7200
  tool_manual,
7182
7201
  runtime_validation_path: runtime_validation_path ?? void 0,
7183
- runtime_validation
7202
+ runtime_validation,
7203
+ oauth_credentials_path: oauth_credentials_path ?? void 0,
7204
+ oauth_credentials
7184
7205
  };
7185
7206
  }
7207
+ var OAUTH_PROVIDER_ALIASES = {
7208
+ x: "twitter",
7209
+ "x-twitter": "twitter",
7210
+ twitter: "twitter",
7211
+ slack: "slack",
7212
+ google: "google",
7213
+ gmail: "google",
7214
+ "google-drive": "google",
7215
+ "google-calendar": "google",
7216
+ github: "github",
7217
+ linear: "linear",
7218
+ notion: "notion"
7219
+ };
7220
+ function oauthProviderKeyFromRequirement(value) {
7221
+ const raw = String(value ?? "").trim().toLowerCase().replaceAll("_", "-");
7222
+ if (!raw) return null;
7223
+ if (OAUTH_PROVIDER_ALIASES[raw]) {
7224
+ return OAUTH_PROVIDER_ALIASES[raw];
7225
+ }
7226
+ for (const token of raw.replaceAll("/", "-").replaceAll(":", "-").split("-")) {
7227
+ const next = token.trim();
7228
+ if (OAUTH_PROVIDER_ALIASES[next]) {
7229
+ return OAUTH_PROVIDER_ALIASES[next];
7230
+ }
7231
+ }
7232
+ return null;
7233
+ }
7234
+ function requiredOauthProviders(requirements) {
7235
+ const providers = [];
7236
+ for (const item of requirements ?? []) {
7237
+ const providerKey = oauthProviderKeyFromRequirement(item);
7238
+ if (providerKey && !providers.includes(providerKey)) {
7239
+ providers.push(providerKey);
7240
+ }
7241
+ }
7242
+ return providers;
7243
+ }
7244
+ function oauthProviderRecordsMap(payload) {
7245
+ if (!payload) {
7246
+ return {};
7247
+ }
7248
+ const items = Array.isArray(payload) ? payload : Array.isArray(payload.items) ? payload.items : [payload];
7249
+ const resolved = {};
7250
+ for (const [index, item] of items.entries()) {
7251
+ if (!isRecord(item)) {
7252
+ throw new SiglumeProjectError(`oauth_credentials[${index}] must be a JSON object.`);
7253
+ }
7254
+ const providerKey = oauthProviderKeyFromRequirement(item.provider_key ?? item.provider);
7255
+ if (!providerKey) {
7256
+ throw new SiglumeProjectError(`oauth_credentials[${index}].provider_key is unsupported.`);
7257
+ }
7258
+ const clientId = String(item.client_id ?? "").trim();
7259
+ const clientSecret = String(item.client_secret ?? "").trim();
7260
+ if (!clientId || !clientSecret) {
7261
+ throw new SiglumeProjectError(`oauth_credentials[${index}] must include client_id and client_secret.`);
7262
+ }
7263
+ const rawScopes = item.required_scopes ?? item.scopes;
7264
+ let scopes = [];
7265
+ if (rawScopes == null) {
7266
+ scopes = [];
7267
+ } else if (!Array.isArray(rawScopes)) {
7268
+ throw new SiglumeProjectError(`oauth_credentials[${index}].required_scopes must be a JSON array.`);
7269
+ } else {
7270
+ scopes = rawScopes.map((scope) => String(scope ?? "").trim()).filter(Boolean);
7271
+ }
7272
+ resolved[providerKey] = {
7273
+ provider_key: providerKey,
7274
+ client_id: clientId,
7275
+ client_secret: clientSecret,
7276
+ required_scopes: scopes
7277
+ };
7278
+ }
7279
+ return resolved;
7280
+ }
7281
+ function canonicalOauthCredentialsPayload(payload) {
7282
+ const records = oauthProviderRecordsMap(payload);
7283
+ const providerKeys = Object.keys(records).sort();
7284
+ if (providerKeys.length === 0) {
7285
+ return void 0;
7286
+ }
7287
+ return {
7288
+ items: providerKeys.map((providerKey) => records[providerKey])
7289
+ };
7290
+ }
7291
+ function ensureRequiredOauthCredentials(project) {
7292
+ const requiredProviders = requiredOauthProviders(project.manifest.required_connected_accounts ?? []);
7293
+ if (requiredProviders.length === 0) {
7294
+ return;
7295
+ }
7296
+ const provided = new Set(Object.keys(oauthProviderRecordsMap(project.oauth_credentials)));
7297
+ const missing = requiredProviders.filter((provider) => !provided.has(provider));
7298
+ if (missing.length === 0) {
7299
+ return;
7300
+ }
7301
+ const path = project.oauth_credentials_path ?? (0, import_node_path.join)(project.root_dir, "oauth_credentials.json");
7302
+ throw new SiglumeProjectError(
7303
+ `${path} is required for OAuth-backed APIs. Missing provider seeds: ${missing.join(", ")}`
7304
+ );
7305
+ }
7186
7306
  async function validateProject(path = ".", deps = {}) {
7187
7307
  const project = await loadProject(path);
7188
7308
  const manifest_issues = await projectValidationIssues(project);
@@ -7219,17 +7339,31 @@ function ensureManifestPublisherIdentity(project) {
7219
7339
  const manifestPayload = project.manifest;
7220
7340
  const docsUrl = String(manifestPayload.docs_url ?? manifestPayload.documentation_url ?? "").trim();
7221
7341
  const supportContact = String(manifestPayload.support_contact ?? "").trim();
7342
+ const sellerHomepageUrl = String(manifestPayload.seller_homepage_url ?? "").trim();
7343
+ const sellerSocialUrl = String(manifestPayload.seller_social_url ?? "").trim();
7222
7344
  const jurisdiction = String(manifestPayload.jurisdiction ?? "").trim();
7223
7345
  const issues = [];
7224
7346
  if (!docsUrl) {
7225
7347
  issues.push("manifest.docs_url is required");
7226
7348
  } else if (looksLikePlaceholder(docsUrl)) {
7227
7349
  issues.push("manifest.docs_url must be replaced with your public documentation URL");
7350
+ } else if (!looksLikeHttpUrl(docsUrl)) {
7351
+ issues.push("manifest.docs_url must be an http(s) URL");
7352
+ } else if (looksLikeRootUrl(docsUrl)) {
7353
+ issues.push("manifest.docs_url must be a dedicated API usage page, not a root homepage URL");
7228
7354
  }
7229
7355
  if (!supportContact) {
7230
7356
  issues.push("manifest.support_contact is required");
7231
7357
  } else if (looksLikePlaceholder(supportContact)) {
7232
7358
  issues.push("manifest.support_contact must be replaced with your real support email or support URL");
7359
+ } else if (!looksLikeEmail(supportContact) && !looksLikeHttpUrl(supportContact)) {
7360
+ issues.push("manifest.support_contact must be a real email address or http(s) support URL");
7361
+ }
7362
+ if (sellerHomepageUrl && (looksLikePlaceholder(sellerHomepageUrl) || !looksLikeHttpUrl(sellerHomepageUrl))) {
7363
+ issues.push("manifest.seller_homepage_url must be a real http(s) official homepage URL when provided");
7364
+ }
7365
+ if (sellerSocialUrl && (looksLikePlaceholder(sellerSocialUrl) || !looksLikeHttpUrl(sellerSocialUrl))) {
7366
+ issues.push("manifest.seller_social_url must be a real http(s) official social/profile URL when provided");
7233
7367
  }
7234
7368
  if (!jurisdiction) issues.push("manifest.jurisdiction is required");
7235
7369
  if (issues.length > 0) {
@@ -7241,7 +7375,25 @@ ${issues.map((issue2) => `- ${issue2}`).join("\n")}`
7241
7375
  }
7242
7376
  function looksLikePlaceholder(value) {
7243
7377
  const normalized = value.trim().toLowerCase();
7244
- 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");
7378
+ 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");
7379
+ }
7380
+ function looksLikeHttpUrl(value) {
7381
+ try {
7382
+ const parsed = new URL(value.trim());
7383
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
7384
+ } catch {
7385
+ return false;
7386
+ }
7387
+ }
7388
+ function looksLikeRootUrl(value) {
7389
+ if (!looksLikeHttpUrl(value)) return false;
7390
+ const parsed = new URL(value.trim());
7391
+ return parsed.pathname.replace(/^\/+|\/+$/g, "") === "";
7392
+ }
7393
+ function looksLikeEmail(value) {
7394
+ const normalized = value.trim();
7395
+ const domain = normalized.split("@").at(-1) ?? "";
7396
+ return normalized.includes("@") && !normalized.includes(" ") && domain.includes(".") && !normalized.startsWith("@");
7245
7397
  }
7246
7398
  function runtimePlaceholderIssues(runtimeValidation) {
7247
7399
  const issues = [];
@@ -7301,6 +7453,9 @@ async function registrationPreflight(project, client) {
7301
7453
  const manifestIssues = await projectValidationIssues(project);
7302
7454
  const [toolManualValid, toolManualIssues] = validate_tool_manual(project.tool_manual);
7303
7455
  const remoteQuality = await client.preview_quality_score(project.tool_manual);
7456
+ const requiredOauthProvidersList = requiredOauthProviders(project.manifest.required_connected_accounts ?? []);
7457
+ const oauthProviderRecords = oauthProviderRecordsMap(project.oauth_credentials);
7458
+ const missingOauthProviders = requiredOauthProvidersList.filter((provider) => !oauthProviderRecords[provider]);
7304
7459
  const blockingToolManualIssues = toolManualIssues.filter((issue2) => issue2.severity === "error");
7305
7460
  const errors = [
7306
7461
  ...manifestIssues.map((issue2) => String(issue2)),
@@ -7312,11 +7467,17 @@ async function registrationPreflight(project, client) {
7312
7467
  if (!remoteQualityOk(remoteQuality)) {
7313
7468
  errors.push(`remote Tool Manual quality is not publishable: ${remoteQuality.grade} (${remoteQuality.overall_score}/100)`);
7314
7469
  }
7470
+ if (missingOauthProviders.length > 0) {
7471
+ errors.push(`oauth_credentials.json is required for OAuth-backed APIs: ${missingOauthProviders.join(", ")}`);
7472
+ }
7315
7473
  const preflight = {
7316
7474
  manifest_issues: manifestIssues,
7317
7475
  tool_manual_valid: toolManualValid,
7318
7476
  tool_manual_issues: toolManualIssues.map((issue2) => toJsonable(issue2)),
7319
7477
  remote_quality: toJsonable(remoteQuality),
7478
+ required_oauth_providers: requiredOauthProvidersList,
7479
+ oauth_credentials_path: project.oauth_credentials_path ?? null,
7480
+ oauth_missing_providers: missingOauthProviders,
7320
7481
  ok: errors.length === 0
7321
7482
  };
7322
7483
  if (errors.length > 0) {
@@ -7332,6 +7493,7 @@ async function runRegistration(path = ".", options = {}, deps = {}) {
7332
7493
  ensureExplicitToolManual(project);
7333
7494
  ensureManifestPublisherIdentity(project);
7334
7495
  ensureRuntimeValidationReady(project);
7496
+ ensureRequiredOauthCredentials(project);
7335
7497
  const client = await createClient(deps);
7336
7498
  const preflight = await registrationPreflight(project, client);
7337
7499
  let developerPortalPreflight = null;
@@ -7340,18 +7502,20 @@ async function runRegistration(path = ".", options = {}, deps = {}) {
7340
7502
  const verifiedDestination = portal.payout_readiness?.verified_destination;
7341
7503
  if (verifiedDestination !== true) {
7342
7504
  throw new SiglumeProjectError(
7343
- "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."
7505
+ "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."
7344
7506
  );
7345
7507
  }
7346
7508
  developerPortalPreflight = toJsonable(portal);
7347
7509
  }
7348
7510
  const receipt = await client.auto_register(project.manifest, project.tool_manual, {
7349
- runtime_validation: project.runtime_validation
7511
+ runtime_validation: project.runtime_validation,
7512
+ oauth_credentials: canonicalOauthCredentialsPayload(project.oauth_credentials)
7350
7513
  });
7351
7514
  const result = {
7352
7515
  receipt: toJsonable(receipt),
7353
7516
  registration_preflight: preflight,
7354
- runtime_validation_path: project.runtime_validation_path ?? null
7517
+ runtime_validation_path: project.runtime_validation_path ?? null,
7518
+ oauth_credentials_path: project.oauth_credentials_path ?? null
7355
7519
  };
7356
7520
  if (developerPortalPreflight) {
7357
7521
  result.developer_portal_preflight = developerPortalPreflight;
@@ -7366,6 +7530,37 @@ async function runRegistration(path = ".", options = {}, deps = {}) {
7366
7530
  }
7367
7531
  return result;
7368
7532
  }
7533
+ async function runPreflight(path = ".", deps = {}) {
7534
+ const project = await loadProject(path);
7535
+ ensureExplicitToolManual(project);
7536
+ ensureManifestPublisherIdentity(project);
7537
+ ensureRuntimeValidationReady(project);
7538
+ ensureRequiredOauthCredentials(project);
7539
+ const client = await createClient(deps);
7540
+ const preflight = await registrationPreflight(project, client);
7541
+ let developerPortalPreflight = null;
7542
+ if (String(project.manifest.price_model ?? "free").toLowerCase() !== "free") {
7543
+ const portal = await client.get_developer_portal();
7544
+ const verifiedDestination = portal.payout_readiness?.verified_destination;
7545
+ if (verifiedDestination !== true) {
7546
+ throw new SiglumeProjectError(
7547
+ "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."
7548
+ );
7549
+ }
7550
+ developerPortalPreflight = toJsonable(portal);
7551
+ }
7552
+ const result = {
7553
+ ok: true,
7554
+ adapter_path: project.adapter_path,
7555
+ registration_preflight: preflight,
7556
+ runtime_validation_path: project.runtime_validation_path ?? null,
7557
+ oauth_credentials_path: project.oauth_credentials_path ?? null
7558
+ };
7559
+ if (developerPortalPreflight) {
7560
+ result.developer_portal_preflight = developerPortalPreflight;
7561
+ }
7562
+ return result;
7563
+ }
7369
7564
  async function createSupportCaseReport(options, deps = {}) {
7370
7565
  const client = await createClient(deps);
7371
7566
  const supportCase = await client.create_support_case(options.subject, options.body, { trace_id: options.trace_id });
@@ -7412,8 +7607,12 @@ async function writeInitTemplate(template, destination) {
7412
7607
  const manifest_path = (0, import_node_path.join)(root, "manifest.json");
7413
7608
  const tool_manual_path = (0, import_node_path.join)(root, "tool_manual.json");
7414
7609
  const runtime_validation_path = (0, import_node_path.join)(root, "runtime_validation.json");
7610
+ const gitignore_path = (0, import_node_path.join)(root, ".gitignore");
7415
7611
  const readme_path = (0, import_node_path.join)(root, "README.md");
7416
- for (const filePath of [adapter_path, manifest_path, tool_manual_path, runtime_validation_path, readme_path]) {
7612
+ const docs_dir = (0, import_node_path.join)(root, "docs");
7613
+ await (0, import_promises.mkdir)(docs_dir, { recursive: true });
7614
+ const docs_usage_path = (0, import_node_path.join)(docs_dir, "api-usage.md");
7615
+ for (const filePath of [adapter_path, manifest_path, tool_manual_path, runtime_validation_path, docs_usage_path, readme_path]) {
7417
7616
  if ((0, import_node_fs.existsSync)(filePath)) {
7418
7617
  throw new SiglumeProjectError(`${(0, import_node_path.basename)(filePath)} already exists in ${root}`);
7419
7618
  }
@@ -7424,8 +7623,10 @@ async function writeInitTemplate(template, destination) {
7424
7623
  await (0, import_promises.writeFile)(manifest_path, renderJson(manifest), "utf8");
7425
7624
  await (0, import_promises.writeFile)(tool_manual_path, renderJson(toolManual), "utf8");
7426
7625
  await (0, import_promises.writeFile)(runtime_validation_path, renderJson(buildRuntimeValidationTemplate(toolManual)), "utf8");
7626
+ await (0, import_promises.writeFile)(docs_usage_path, apiUsageDocsTemplate(manifest), "utf8");
7627
+ await writeOrMergeGitignore(gitignore_path);
7427
7628
  await (0, import_promises.writeFile)(readme_path, readmeTemplate(template), "utf8");
7428
- return [adapter_path, manifest_path, tool_manual_path, runtime_validation_path, readme_path];
7629
+ return [adapter_path, manifest_path, tool_manual_path, runtime_validation_path, docs_usage_path, gitignore_path, readme_path];
7429
7630
  }
7430
7631
  async function listOperationCatalog(options = {}, deps = {}) {
7431
7632
  const resolvedAgentId = String(options.agent_id ?? "").trim();
@@ -7819,12 +8020,19 @@ function operationReadmeTemplate(operation, manifest, warning) {
7819
8020
  "- `stubs.ts`: mock fallback used when `SIGLUME_API_KEY` is not set",
7820
8021
  "- `manifest.json`: reviewable manifest snapshot",
7821
8022
  "- `tool_manual.json`: machine-generated ToolManual scaffold",
7822
- "- `runtime_validation.json`: public endpoint and review-key checks used by auto-register",
8023
+ "- `runtime_validation.json`: local public endpoint and review-key checks used by auto-register",
8024
+ "- `docs/api-usage.md`: publishable API usage guide template for `docs_url`",
8025
+ "- `.gitignore`: keeps runtime review keys and OAuth client secrets out of Git",
7823
8026
  "- `tests/test_adapter.ts`: smoke test for `AppTestHarness`",
7824
8027
  "",
7825
8028
  "Before registering, replace all generated placeholders:",
7826
- "- In `adapter.ts` and `manifest.json`, replace `docs_url` and `support_contact` with your public documentation and support contact.",
7827
- "- In `runtime_validation.json`, replace the public URL and review-key placeholders.",
8029
+ "- In `adapter.ts` and `manifest.json`, replace `docs_url` with a dedicated public API usage guide, not a homepage.",
8030
+ "- Replace `support_contact` with a real support email address or public support URL.",
8031
+ "- Optional `seller_homepage_url` is the seller's official site and can stay blank.",
8032
+ "- In the local `runtime_validation.json`, replace the public URL and review-key placeholders.",
8033
+ "- If the API uses seller-side OAuth, create a local `oauth_credentials.json` next to the adapter.",
8034
+ "- Do not commit real review keys or OAuth client secrets; the generated `.gitignore` excludes those files.",
8035
+ "- Because `runtime_validation.json` is ignored, GitHub samples do not commit review-key values.",
7828
8036
  "",
7829
8037
  "## Commands",
7830
8038
  "",
@@ -7836,16 +8044,108 @@ function operationReadmeTemplate(operation, manifest, warning) {
7836
8044
  "siglume score . --offline",
7837
8045
  "```",
7838
8046
  "",
7839
- "After placeholders are replaced and `SIGLUME_API_KEY` is set, run the server-aligned checks and register:",
8047
+ "After placeholders are replaced and `SIGLUME_API_KEY` is issued from Developer Portal -> CLI / API keys, run the server-aligned checks:",
7840
8048
  "",
7841
8049
  "```bash",
7842
8050
  "siglume validate .",
7843
8051
  "siglume score . --remote",
8052
+ "siglume preflight .",
8053
+ "siglume register .",
8054
+ "# inspect the draft, then explicitly approve publish:",
7844
8055
  "siglume register . --confirm",
7845
8056
  "```",
7846
8057
  ""
7847
8058
  ].join("\n");
7848
8059
  }
8060
+ function apiUsageDocsTemplate(manifest) {
8061
+ const name = String(manifest.name ?? manifest.capability_key ?? "Siglume API");
8062
+ const capabilityKey = String(manifest.capability_key ?? "replace-with-capability-key");
8063
+ const jobToBeDone = String(manifest.job_to_be_done ?? "Describe what this API lets an agent do.");
8064
+ const permissionClass = String(manifest.permission_class ?? "read-only");
8065
+ const priceModel = String(manifest.price_model ?? "free");
8066
+ const requiredAccounts = (manifest.required_connected_accounts ?? []).join(", ") || "none";
8067
+ const supportContact = String(manifest.support_contact ?? "replace-with-support-contact");
8068
+ return [
8069
+ `# ${name} API Usage Guide`,
8070
+ "",
8071
+ `This page is the dedicated public usage guide for the Siglume API listing \`${capabilityKey}\`.`,
8072
+ "Publish this page at an anonymous HTTP 200 URL and use that URL as `docs_url`.",
8073
+ "",
8074
+ "Do not use your company homepage as `docs_url`; keep seller/company homepages in `seller_homepage_url`.",
8075
+ "",
8076
+ "## What This API Does",
8077
+ "",
8078
+ jobToBeDone,
8079
+ "",
8080
+ "## Permission Model",
8081
+ "",
8082
+ `- Permission class: \`${permissionClass}\``,
8083
+ `- Price model: \`${priceModel}\``,
8084
+ `- Required connected accounts: \`${requiredAccounts}\``,
8085
+ "",
8086
+ "## Inputs",
8087
+ "",
8088
+ "Describe the request fields your API accepts. Keep this aligned with `tool_manual.json` and `runtime_validation.json`.",
8089
+ "",
8090
+ "## Outputs",
8091
+ "",
8092
+ "Describe the response fields your API returns. Include the fields in `runtime_validation.expected_response_fields`.",
8093
+ "",
8094
+ "## Errors And Support",
8095
+ "",
8096
+ "Explain common error messages and how an owner should recover.",
8097
+ "",
8098
+ `Support contact: ${supportContact}`,
8099
+ ""
8100
+ ].join("\n");
8101
+ }
8102
+ function generatedGitignore() {
8103
+ return [
8104
+ "# Local secrets and registration-only runtime checks.",
8105
+ ".env",
8106
+ ".env.*",
8107
+ "!.env.example",
8108
+ "runtime_validation.json",
8109
+ "runtime-validation.json",
8110
+ "oauth_credentials.json",
8111
+ "oauth-credentials.json",
8112
+ "",
8113
+ "# Python / test artifacts.",
8114
+ "__pycache__/",
8115
+ "*.py[cod]",
8116
+ ".pytest_cache/",
8117
+ ".mypy_cache/",
8118
+ ".coverage",
8119
+ "htmlcov/",
8120
+ "dist/",
8121
+ "build/",
8122
+ "*.egg-info/",
8123
+ "",
8124
+ "# JavaScript tooling if this project also uses TypeScript helpers.",
8125
+ "node_modules/",
8126
+ "coverage/",
8127
+ ""
8128
+ ].join("\n");
8129
+ }
8130
+ async function writeOrMergeGitignore(filePath) {
8131
+ const generated = generatedGitignore();
8132
+ if (!(0, import_node_fs.existsSync)(filePath)) {
8133
+ await (0, import_promises.writeFile)(filePath, generated, "utf8");
8134
+ return;
8135
+ }
8136
+ const existing = await (0, import_promises.readFile)(filePath, "utf8");
8137
+ const existingEntries = new Set(existing.split(/\r?\n/).map((line) => line.trim()));
8138
+ const additions = generated.split(/\r?\n/).filter((line) => line.trim() && !existingEntries.has(line.trim()));
8139
+ if (additions.length === 0) {
8140
+ return;
8141
+ }
8142
+ const prefix = existing.endsWith("\n") ? existing : `${existing}
8143
+ `;
8144
+ await (0, import_promises.writeFile)(filePath, `${prefix}
8145
+ # Siglume generated ignores.
8146
+ ${additions.join("\n")}
8147
+ `, "utf8");
8148
+ }
7849
8149
  async function writeOperationTemplate(operation_key, destination, options = {}, deps = {}) {
7850
8150
  const root = (0, import_node_path.resolve)(destination);
7851
8151
  await (0, import_promises.mkdir)(root, { recursive: true });
@@ -7856,9 +8156,22 @@ async function writeOperationTemplate(operation_key, destination, options = {},
7856
8156
  const manifest_path = (0, import_node_path.join)(root, "manifest.json");
7857
8157
  const tool_manual_path = (0, import_node_path.join)(root, "tool_manual.json");
7858
8158
  const runtime_validation_path = (0, import_node_path.join)(root, "runtime_validation.json");
8159
+ const gitignore_path = (0, import_node_path.join)(root, ".gitignore");
7859
8160
  const readme_path = (0, import_node_path.join)(root, "README.md");
7860
8161
  const test_path = (0, import_node_path.join)(testsDir, "test_adapter.ts");
7861
- for (const filePath of [adapter_path, stubs_path, manifest_path, tool_manual_path, runtime_validation_path, readme_path, test_path]) {
8162
+ const docs_dir = (0, import_node_path.join)(root, "docs");
8163
+ await (0, import_promises.mkdir)(docs_dir, { recursive: true });
8164
+ const docs_usage_path = (0, import_node_path.join)(docs_dir, "api-usage.md");
8165
+ for (const filePath of [
8166
+ adapter_path,
8167
+ stubs_path,
8168
+ manifest_path,
8169
+ tool_manual_path,
8170
+ runtime_validation_path,
8171
+ docs_usage_path,
8172
+ readme_path,
8173
+ test_path
8174
+ ]) {
7862
8175
  if ((0, import_node_fs.existsSync)(filePath)) {
7863
8176
  throw new SiglumeProjectError(`${(0, import_node_path.basename)(filePath)} already exists in ${root}`);
7864
8177
  }
@@ -7886,10 +8199,22 @@ async function writeOperationTemplate(operation_key, destination, options = {},
7886
8199
  await (0, import_promises.writeFile)(manifest_path, renderJson(manifest), "utf8");
7887
8200
  await (0, import_promises.writeFile)(tool_manual_path, renderJson(tool_manual), "utf8");
7888
8201
  await (0, import_promises.writeFile)(runtime_validation_path, renderJson(buildRuntimeValidationTemplate(tool_manual)), "utf8");
8202
+ await (0, import_promises.writeFile)(docs_usage_path, apiUsageDocsTemplate(manifest), "utf8");
8203
+ await writeOrMergeGitignore(gitignore_path);
7889
8204
  await (0, import_promises.writeFile)(readme_path, operationReadmeTemplate(operation, manifest, warning), "utf8");
7890
8205
  await (0, import_promises.writeFile)(test_path, operationTestSource(operation), "utf8");
7891
8206
  return {
7892
- files: [adapter_path, stubs_path, manifest_path, tool_manual_path, runtime_validation_path, readme_path, test_path],
8207
+ files: [
8208
+ adapter_path,
8209
+ stubs_path,
8210
+ manifest_path,
8211
+ tool_manual_path,
8212
+ runtime_validation_path,
8213
+ docs_usage_path,
8214
+ gitignore_path,
8215
+ readme_path,
8216
+ test_path
8217
+ ],
7893
8218
  operation,
7894
8219
  report: {
7895
8220
  tool_manual_valid,
@@ -8038,6 +8363,15 @@ async function findRuntimeValidationPath(root_dir) {
8038
8363
  }
8039
8364
  return null;
8040
8365
  }
8366
+ async function findOauthCredentialsPath(root_dir) {
8367
+ for (const name of ["oauth_credentials.json", "oauth-credentials.json"]) {
8368
+ const candidate = (0, import_node_path.join)(root_dir, name);
8369
+ if ((0, import_node_fs.existsSync)(candidate)) {
8370
+ return candidate;
8371
+ }
8372
+ }
8373
+ return null;
8374
+ }
8041
8375
  async function loadJsonObject(path, label) {
8042
8376
  let payload;
8043
8377
  try {
@@ -8261,11 +8595,18 @@ function readmeTemplate(template) {
8261
8595
  "- `adapter.ts`: your AppAdapter implementation",
8262
8596
  "- `manifest.json`: serialized AppManifest snapshot",
8263
8597
  "- `tool_manual.json`: editable ToolManual draft for validation and registration",
8264
- "- `runtime_validation.json`: live API smoke-test contract used during registration",
8598
+ "- `runtime_validation.json`: local live API smoke-test contract used during registration",
8599
+ "- `docs/api-usage.md`: publish this page and use its public URL as `docs_url`",
8600
+ "- `.gitignore`: keeps runtime review keys and OAuth client secrets out of Git",
8265
8601
  "",
8266
8602
  "Before registering, replace all generated placeholders:",
8267
- "- In `adapter.ts` and `manifest.json`, replace `docs_url` and `support_contact` with your public documentation and support contact.",
8268
- "- In `runtime_validation.json`, replace the public URL and review-key placeholders.",
8603
+ "- In `adapter.ts` and `manifest.json`, replace `docs_url` with a dedicated public API usage guide, not a homepage.",
8604
+ "- Replace `support_contact` with a real support email address or public support URL.",
8605
+ "- Optional `seller_homepage_url` is the seller's official site and can stay blank.",
8606
+ "- In the local `runtime_validation.json`, replace the public URL and review-key placeholders.",
8607
+ "- If the API uses seller-side OAuth, create a local `oauth_credentials.json` next to the adapter.",
8608
+ "- Do not commit real review keys or OAuth client secrets; the generated `.gitignore` excludes those files.",
8609
+ "- Because `runtime_validation.json` is ignored, GitHub samples do not commit review-key values.",
8269
8610
  "",
8270
8611
  "Suggested workflow:",
8271
8612
  "",
@@ -8276,11 +8617,14 @@ function readmeTemplate(template) {
8276
8617
  "siglume score . --offline",
8277
8618
  "```",
8278
8619
  "",
8279
- "After placeholders are replaced and `SIGLUME_API_KEY` is set, run the server-aligned checks and register:",
8620
+ "After placeholders are replaced and `SIGLUME_API_KEY` is issued from Developer Portal -> CLI / API keys, run the server-aligned checks:",
8280
8621
  "",
8281
8622
  "```bash",
8282
8623
  "siglume validate .",
8283
8624
  "siglume score . --remote",
8625
+ "siglume preflight .",
8626
+ "siglume register .",
8627
+ "# inspect the draft, then explicitly approve publish:",
8284
8628
  "siglume register . --confirm",
8285
8629
  "```",
8286
8630
  ""
@@ -8449,18 +8793,52 @@ async function runCli(argv, deps = {}) {
8449
8793
  throw new SiglumeProjectError("Score failed.");
8450
8794
  }
8451
8795
  });
8452
- 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) => {
8796
+ program.command("preflight").option("--json", "emit machine-readable JSON", false).argument("[path]", ".", "project path").action(async (path, options) => {
8797
+ const report = await runPreflight(path, deps);
8798
+ if (options.json) {
8799
+ emit(stdout, renderJson(report));
8800
+ return;
8801
+ }
8802
+ emit(stdout, "Preflight passed.");
8803
+ const preflight = report.registration_preflight;
8804
+ if (preflight?.remote_quality) {
8805
+ emit(stdout, `preflight_quality: ${preflight.remote_quality.grade} (${preflight.remote_quality.overall_score}/100)`);
8806
+ }
8807
+ if (report.runtime_validation_path) emit(stdout, `runtime_validation_path: ${String(report.runtime_validation_path)}`);
8808
+ if (report.oauth_credentials_path) emit(stdout, `oauth_credentials_path: ${String(report.oauth_credentials_path)}`);
8809
+ });
8810
+ 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) => {
8453
8811
  const report = await runRegistration(path, { confirm: options.confirm, submit_review: options.submitReview }, deps);
8454
8812
  if (options.json) {
8455
8813
  emit(stdout, renderJson(report));
8456
8814
  } else {
8457
8815
  const receipt = report.receipt;
8458
- emit(stdout, "Draft listing created.");
8816
+ if (report.confirmation) {
8817
+ emit(stdout, "Listing published.");
8818
+ } else if (report.review) {
8819
+ emit(stdout, "Listing published via legacy submit-review alias.");
8820
+ } else if (receipt.registration_mode === "upgrade") {
8821
+ emit(stdout, "Upgrade staged.");
8822
+ } else if (receipt.registration_mode === "refresh") {
8823
+ emit(stdout, "Draft refreshed.");
8824
+ } else {
8825
+ emit(stdout, "Draft listing created.");
8826
+ }
8459
8827
  emit(stdout, `listing_id: ${receipt.listing_id}`);
8460
- emit(stdout, `status: ${receipt.status}`);
8828
+ emit(stdout, `receipt_status: ${receipt.status}`);
8829
+ if (receipt.listing_status) emit(stdout, `listing_status: ${receipt.listing_status}`);
8830
+ if (receipt.oauth_status) emit(stdout, `oauth_configured: ${Boolean(receipt.oauth_status.configured)}`);
8461
8831
  if (receipt.review_url) emit(stdout, `review_url: ${receipt.review_url}`);
8462
8832
  if (receipt.trace_id) emit(stdout, `trace_id: ${receipt.trace_id}`);
8463
8833
  if (receipt.request_id) emit(stdout, `request_id: ${receipt.request_id}`);
8834
+ if (report.confirmation) {
8835
+ const confirmation = report.confirmation;
8836
+ if (confirmation.status) emit(stdout, `confirmation_status: ${confirmation.status}`);
8837
+ if (confirmation.release?.release_status) emit(stdout, `release_status: ${confirmation.release.release_status}`);
8838
+ } else if (report.review) {
8839
+ const review = report.review;
8840
+ if (review.status) emit(stdout, `publish_status: ${review.status}`);
8841
+ }
8464
8842
  const preflight = report.registration_preflight;
8465
8843
  if (preflight?.remote_quality) {
8466
8844
  emit(stdout, `preflight_quality: ${preflight.remote_quality.grade} (${preflight.remote_quality.overall_score}/100)`);