@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.
@@ -1330,6 +1330,9 @@ function parseListing(data) {
1330
1330
  short_description: stringOrNull(data.short_description),
1331
1331
  docs_url: stringOrNull(data.docs_url),
1332
1332
  support_contact: stringOrNull(data.support_contact),
1333
+ seller_display_name: stringOrNull(data.seller_display_name),
1334
+ seller_homepage_url: stringOrNull(data.seller_homepage_url),
1335
+ seller_social_url: stringOrNull(data.seller_social_url),
1333
1336
  review_status: stringOrNull(data.review_status),
1334
1337
  review_note: stringOrNull(data.review_note),
1335
1338
  submission_blockers: Array.isArray(data.submission_blockers) ? data.submission_blockers.filter((item) => typeof item === "string") : [],
@@ -2546,6 +2549,13 @@ var init_client = __esm({
2546
2549
  if (options.runtime_validation) {
2547
2550
  payload.runtime_validation = coerceMapping(options.runtime_validation, "runtime_validation");
2548
2551
  }
2552
+ if (options.oauth_credentials) {
2553
+ payload.oauth_credentials = Array.isArray(options.oauth_credentials) ? {
2554
+ items: options.oauth_credentials.map(
2555
+ (item, index) => coerceMapping(item, `oauth_credentials[${index}]`)
2556
+ )
2557
+ } : coerceMapping(options.oauth_credentials, "oauth_credentials");
2558
+ }
2549
2559
  if (options.metadata) {
2550
2560
  payload.metadata = coerceMapping(options.metadata, "metadata");
2551
2561
  }
@@ -2564,6 +2574,8 @@ var init_client = __esm({
2564
2574
  "docs_url",
2565
2575
  "documentation_url",
2566
2576
  "support_contact",
2577
+ "seller_homepage_url",
2578
+ "seller_social_url",
2567
2579
  "jurisdiction",
2568
2580
  "price_model",
2569
2581
  "price_value_minor",
@@ -2579,10 +2591,14 @@ var init_client = __esm({
2579
2591
  }
2580
2592
  const docsUrl = String(manifestPayload.docs_url ?? manifestPayload.documentation_url ?? "").trim();
2581
2593
  const supportContact = String(manifestPayload.support_contact ?? "").trim();
2582
- if (docsUrl || supportContact) {
2594
+ const sellerHomepageUrl = String(manifestPayload.seller_homepage_url ?? "").trim();
2595
+ const sellerSocialUrl = String(manifestPayload.seller_social_url ?? "").trim();
2596
+ if (docsUrl || supportContact || sellerHomepageUrl || sellerSocialUrl) {
2583
2597
  const publisherIdentity = {
2584
2598
  documentation_url: docsUrl || null,
2585
- support_contact: supportContact || null
2599
+ support_contact: supportContact || null,
2600
+ seller_homepage_url: sellerHomepageUrl || null,
2601
+ seller_social_url: sellerSocialUrl || null
2586
2602
  };
2587
2603
  payload.publisher_identity = publisherIdentity;
2588
2604
  payload.legal = { publisher_identity: publisherIdentity };
@@ -2596,36 +2612,30 @@ var init_client = __esm({
2596
2612
  return {
2597
2613
  listing_id,
2598
2614
  status: String(data.status ?? "draft"),
2615
+ registration_mode: stringOrNull(data.registration_mode),
2616
+ listing_status: stringOrNull(data.listing_status),
2599
2617
  auto_manifest: toRecord(data.auto_manifest),
2600
2618
  confidence: toRecord(data.confidence),
2601
2619
  validation_report: toRecord(data.validation_report),
2620
+ oauth_status: toRecord(data.oauth_status),
2602
2621
  review_url: stringOrNull(data.review_url),
2603
2622
  trace_id: meta.trace_id,
2604
2623
  request_id: meta.request_id
2605
2624
  };
2606
2625
  }
2607
2626
  async confirm_registration(listing_id, options = {}) {
2608
- const pending = this.pendingConfirmations.get(listing_id);
2609
- const manifestPayload = options.manifest ? coerceMapping(options.manifest, "manifest") : pending?.manifest ?? {};
2610
- const toolManualPayload = options.tool_manual ? coerceMapping(options.tool_manual, "tool_manual") : pending?.tool_manual ?? {};
2611
- const overrides = {};
2612
- for (const fieldName of ["name", "job_to_be_done"]) {
2613
- if (manifestPayload[fieldName]) {
2614
- overrides[fieldName] = manifestPayload[fieldName];
2615
- }
2616
- }
2617
- if (Object.keys(toolManualPayload).length > 0) {
2618
- overrides.tool_manual = toolManualPayload;
2619
- }
2627
+ void options;
2620
2628
  const payload = { approved: true };
2621
- if (Object.keys(overrides).length > 0) {
2622
- payload.overrides = overrides;
2623
- }
2624
2629
  const [data, meta] = await this.request("POST", `/market/capabilities/${listing_id}/confirm-auto-register`, { json_body: payload });
2625
2630
  this.pendingConfirmations.delete(listing_id);
2631
+ const checklist = isRecord(data.checklist) ? Object.fromEntries(
2632
+ Object.entries(data.checklist).map(([key, value]) => [key, Boolean(value)])
2633
+ ) : {};
2626
2634
  return {
2627
2635
  listing_id: String(data.listing_id ?? listing_id),
2628
2636
  status: String(data.status ?? ""),
2637
+ message: stringOrNull(data.message),
2638
+ checklist,
2629
2639
  release: toRecord(data.release),
2630
2640
  quality: parseRegistrationQuality(toRecord(data.quality)),
2631
2641
  trace_id: meta.trace_id,
@@ -7145,6 +7155,15 @@ async function loadProject(path = ".") {
7145
7155
  const tool_manual = tool_manual_path ? JSON.parse(await readFile(tool_manual_path, "utf8")) : buildToolManualTemplate(manifest);
7146
7156
  const runtime_validation_path = await findRuntimeValidationPath(root_dir);
7147
7157
  const runtime_validation = runtime_validation_path ? await loadJsonObject(runtime_validation_path, "runtime_validation") : void 0;
7158
+ const oauth_credentials_path = await findOauthCredentialsPath(root_dir);
7159
+ let oauth_credentials;
7160
+ if (oauth_credentials_path) {
7161
+ const parsed = JSON.parse(await readFile(oauth_credentials_path, "utf8"));
7162
+ if (!isRecord(parsed) && !Array.isArray(parsed)) {
7163
+ throw new SiglumeProjectError("oauth_credentials must be a JSON object or array");
7164
+ }
7165
+ oauth_credentials = parsed;
7166
+ }
7148
7167
  return {
7149
7168
  root_dir,
7150
7169
  adapter_path,
@@ -7153,9 +7172,110 @@ async function loadProject(path = ".") {
7153
7172
  tool_manual_path: tool_manual_path ?? void 0,
7154
7173
  tool_manual,
7155
7174
  runtime_validation_path: runtime_validation_path ?? void 0,
7156
- runtime_validation
7175
+ runtime_validation,
7176
+ oauth_credentials_path: oauth_credentials_path ?? void 0,
7177
+ oauth_credentials
7157
7178
  };
7158
7179
  }
7180
+ var OAUTH_PROVIDER_ALIASES = {
7181
+ x: "twitter",
7182
+ "x-twitter": "twitter",
7183
+ twitter: "twitter",
7184
+ slack: "slack",
7185
+ google: "google",
7186
+ gmail: "google",
7187
+ "google-drive": "google",
7188
+ "google-calendar": "google",
7189
+ github: "github",
7190
+ linear: "linear",
7191
+ notion: "notion"
7192
+ };
7193
+ function oauthProviderKeyFromRequirement(value) {
7194
+ const raw = String(value ?? "").trim().toLowerCase().replaceAll("_", "-");
7195
+ if (!raw) return null;
7196
+ if (OAUTH_PROVIDER_ALIASES[raw]) {
7197
+ return OAUTH_PROVIDER_ALIASES[raw];
7198
+ }
7199
+ for (const token of raw.replaceAll("/", "-").replaceAll(":", "-").split("-")) {
7200
+ const next = token.trim();
7201
+ if (OAUTH_PROVIDER_ALIASES[next]) {
7202
+ return OAUTH_PROVIDER_ALIASES[next];
7203
+ }
7204
+ }
7205
+ return null;
7206
+ }
7207
+ function requiredOauthProviders(requirements) {
7208
+ const providers = [];
7209
+ for (const item of requirements ?? []) {
7210
+ const providerKey = oauthProviderKeyFromRequirement(item);
7211
+ if (providerKey && !providers.includes(providerKey)) {
7212
+ providers.push(providerKey);
7213
+ }
7214
+ }
7215
+ return providers;
7216
+ }
7217
+ function oauthProviderRecordsMap(payload) {
7218
+ if (!payload) {
7219
+ return {};
7220
+ }
7221
+ const items = Array.isArray(payload) ? payload : Array.isArray(payload.items) ? payload.items : [payload];
7222
+ const resolved = {};
7223
+ for (const [index, item] of items.entries()) {
7224
+ if (!isRecord(item)) {
7225
+ throw new SiglumeProjectError(`oauth_credentials[${index}] must be a JSON object.`);
7226
+ }
7227
+ const providerKey = oauthProviderKeyFromRequirement(item.provider_key ?? item.provider);
7228
+ if (!providerKey) {
7229
+ throw new SiglumeProjectError(`oauth_credentials[${index}].provider_key is unsupported.`);
7230
+ }
7231
+ const clientId = String(item.client_id ?? "").trim();
7232
+ const clientSecret = String(item.client_secret ?? "").trim();
7233
+ if (!clientId || !clientSecret) {
7234
+ throw new SiglumeProjectError(`oauth_credentials[${index}] must include client_id and client_secret.`);
7235
+ }
7236
+ const rawScopes = item.required_scopes ?? item.scopes;
7237
+ let scopes = [];
7238
+ if (rawScopes == null) {
7239
+ scopes = [];
7240
+ } else if (!Array.isArray(rawScopes)) {
7241
+ throw new SiglumeProjectError(`oauth_credentials[${index}].required_scopes must be a JSON array.`);
7242
+ } else {
7243
+ scopes = rawScopes.map((scope) => String(scope ?? "").trim()).filter(Boolean);
7244
+ }
7245
+ resolved[providerKey] = {
7246
+ provider_key: providerKey,
7247
+ client_id: clientId,
7248
+ client_secret: clientSecret,
7249
+ required_scopes: scopes
7250
+ };
7251
+ }
7252
+ return resolved;
7253
+ }
7254
+ function canonicalOauthCredentialsPayload(payload) {
7255
+ const records = oauthProviderRecordsMap(payload);
7256
+ const providerKeys = Object.keys(records).sort();
7257
+ if (providerKeys.length === 0) {
7258
+ return void 0;
7259
+ }
7260
+ return {
7261
+ items: providerKeys.map((providerKey) => records[providerKey])
7262
+ };
7263
+ }
7264
+ function ensureRequiredOauthCredentials(project) {
7265
+ const requiredProviders = requiredOauthProviders(project.manifest.required_connected_accounts ?? []);
7266
+ if (requiredProviders.length === 0) {
7267
+ return;
7268
+ }
7269
+ const provided = new Set(Object.keys(oauthProviderRecordsMap(project.oauth_credentials)));
7270
+ const missing = requiredProviders.filter((provider) => !provided.has(provider));
7271
+ if (missing.length === 0) {
7272
+ return;
7273
+ }
7274
+ const path = project.oauth_credentials_path ?? join(project.root_dir, "oauth_credentials.json");
7275
+ throw new SiglumeProjectError(
7276
+ `${path} is required for OAuth-backed APIs. Missing provider seeds: ${missing.join(", ")}`
7277
+ );
7278
+ }
7159
7279
  async function validateProject(path = ".", deps = {}) {
7160
7280
  const project = await loadProject(path);
7161
7281
  const manifest_issues = await projectValidationIssues(project);
@@ -7192,17 +7312,31 @@ function ensureManifestPublisherIdentity(project) {
7192
7312
  const manifestPayload = project.manifest;
7193
7313
  const docsUrl = String(manifestPayload.docs_url ?? manifestPayload.documentation_url ?? "").trim();
7194
7314
  const supportContact = String(manifestPayload.support_contact ?? "").trim();
7315
+ const sellerHomepageUrl = String(manifestPayload.seller_homepage_url ?? "").trim();
7316
+ const sellerSocialUrl = String(manifestPayload.seller_social_url ?? "").trim();
7195
7317
  const jurisdiction = String(manifestPayload.jurisdiction ?? "").trim();
7196
7318
  const issues = [];
7197
7319
  if (!docsUrl) {
7198
7320
  issues.push("manifest.docs_url is required");
7199
7321
  } else if (looksLikePlaceholder(docsUrl)) {
7200
7322
  issues.push("manifest.docs_url must be replaced with your public documentation URL");
7323
+ } else if (!looksLikeHttpUrl(docsUrl)) {
7324
+ issues.push("manifest.docs_url must be an http(s) URL");
7325
+ } else if (looksLikeRootUrl(docsUrl)) {
7326
+ issues.push("manifest.docs_url must be a dedicated API usage page, not a root homepage URL");
7201
7327
  }
7202
7328
  if (!supportContact) {
7203
7329
  issues.push("manifest.support_contact is required");
7204
7330
  } else if (looksLikePlaceholder(supportContact)) {
7205
7331
  issues.push("manifest.support_contact must be replaced with your real support email or support URL");
7332
+ } else if (!looksLikeEmail(supportContact) && !looksLikeHttpUrl(supportContact)) {
7333
+ issues.push("manifest.support_contact must be a real email address or http(s) support URL");
7334
+ }
7335
+ if (sellerHomepageUrl && (looksLikePlaceholder(sellerHomepageUrl) || !looksLikeHttpUrl(sellerHomepageUrl))) {
7336
+ issues.push("manifest.seller_homepage_url must be a real http(s) official homepage URL when provided");
7337
+ }
7338
+ if (sellerSocialUrl && (looksLikePlaceholder(sellerSocialUrl) || !looksLikeHttpUrl(sellerSocialUrl))) {
7339
+ issues.push("manifest.seller_social_url must be a real http(s) official social/profile URL when provided");
7206
7340
  }
7207
7341
  if (!jurisdiction) issues.push("manifest.jurisdiction is required");
7208
7342
  if (issues.length > 0) {
@@ -7214,7 +7348,25 @@ ${issues.map((issue2) => `- ${issue2}`).join("\n")}`
7214
7348
  }
7215
7349
  function looksLikePlaceholder(value) {
7216
7350
  const normalized = value.trim().toLowerCase();
7217
- 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");
7351
+ 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");
7352
+ }
7353
+ function looksLikeHttpUrl(value) {
7354
+ try {
7355
+ const parsed = new URL(value.trim());
7356
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
7357
+ } catch {
7358
+ return false;
7359
+ }
7360
+ }
7361
+ function looksLikeRootUrl(value) {
7362
+ if (!looksLikeHttpUrl(value)) return false;
7363
+ const parsed = new URL(value.trim());
7364
+ return parsed.pathname.replace(/^\/+|\/+$/g, "") === "";
7365
+ }
7366
+ function looksLikeEmail(value) {
7367
+ const normalized = value.trim();
7368
+ const domain = normalized.split("@").at(-1) ?? "";
7369
+ return normalized.includes("@") && !normalized.includes(" ") && domain.includes(".") && !normalized.startsWith("@");
7218
7370
  }
7219
7371
  function runtimePlaceholderIssues(runtimeValidation) {
7220
7372
  const issues = [];
@@ -7274,6 +7426,9 @@ async function registrationPreflight(project, client) {
7274
7426
  const manifestIssues = await projectValidationIssues(project);
7275
7427
  const [toolManualValid, toolManualIssues] = validate_tool_manual(project.tool_manual);
7276
7428
  const remoteQuality = await client.preview_quality_score(project.tool_manual);
7429
+ const requiredOauthProvidersList = requiredOauthProviders(project.manifest.required_connected_accounts ?? []);
7430
+ const oauthProviderRecords = oauthProviderRecordsMap(project.oauth_credentials);
7431
+ const missingOauthProviders = requiredOauthProvidersList.filter((provider) => !oauthProviderRecords[provider]);
7277
7432
  const blockingToolManualIssues = toolManualIssues.filter((issue2) => issue2.severity === "error");
7278
7433
  const errors = [
7279
7434
  ...manifestIssues.map((issue2) => String(issue2)),
@@ -7285,11 +7440,17 @@ async function registrationPreflight(project, client) {
7285
7440
  if (!remoteQualityOk(remoteQuality)) {
7286
7441
  errors.push(`remote Tool Manual quality is not publishable: ${remoteQuality.grade} (${remoteQuality.overall_score}/100)`);
7287
7442
  }
7443
+ if (missingOauthProviders.length > 0) {
7444
+ errors.push(`oauth_credentials.json is required for OAuth-backed APIs: ${missingOauthProviders.join(", ")}`);
7445
+ }
7288
7446
  const preflight = {
7289
7447
  manifest_issues: manifestIssues,
7290
7448
  tool_manual_valid: toolManualValid,
7291
7449
  tool_manual_issues: toolManualIssues.map((issue2) => toJsonable(issue2)),
7292
7450
  remote_quality: toJsonable(remoteQuality),
7451
+ required_oauth_providers: requiredOauthProvidersList,
7452
+ oauth_credentials_path: project.oauth_credentials_path ?? null,
7453
+ oauth_missing_providers: missingOauthProviders,
7293
7454
  ok: errors.length === 0
7294
7455
  };
7295
7456
  if (errors.length > 0) {
@@ -7305,6 +7466,7 @@ async function runRegistration(path = ".", options = {}, deps = {}) {
7305
7466
  ensureExplicitToolManual(project);
7306
7467
  ensureManifestPublisherIdentity(project);
7307
7468
  ensureRuntimeValidationReady(project);
7469
+ ensureRequiredOauthCredentials(project);
7308
7470
  const client = await createClient(deps);
7309
7471
  const preflight = await registrationPreflight(project, client);
7310
7472
  let developerPortalPreflight = null;
@@ -7313,18 +7475,20 @@ async function runRegistration(path = ".", options = {}, deps = {}) {
7313
7475
  const verifiedDestination = portal.payout_readiness?.verified_destination;
7314
7476
  if (verifiedDestination !== true) {
7315
7477
  throw new SiglumeProjectError(
7316
- "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."
7478
+ "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."
7317
7479
  );
7318
7480
  }
7319
7481
  developerPortalPreflight = toJsonable(portal);
7320
7482
  }
7321
7483
  const receipt = await client.auto_register(project.manifest, project.tool_manual, {
7322
- runtime_validation: project.runtime_validation
7484
+ runtime_validation: project.runtime_validation,
7485
+ oauth_credentials: canonicalOauthCredentialsPayload(project.oauth_credentials)
7323
7486
  });
7324
7487
  const result = {
7325
7488
  receipt: toJsonable(receipt),
7326
7489
  registration_preflight: preflight,
7327
- runtime_validation_path: project.runtime_validation_path ?? null
7490
+ runtime_validation_path: project.runtime_validation_path ?? null,
7491
+ oauth_credentials_path: project.oauth_credentials_path ?? null
7328
7492
  };
7329
7493
  if (developerPortalPreflight) {
7330
7494
  result.developer_portal_preflight = developerPortalPreflight;
@@ -7339,6 +7503,37 @@ async function runRegistration(path = ".", options = {}, deps = {}) {
7339
7503
  }
7340
7504
  return result;
7341
7505
  }
7506
+ async function runPreflight(path = ".", deps = {}) {
7507
+ const project = await loadProject(path);
7508
+ ensureExplicitToolManual(project);
7509
+ ensureManifestPublisherIdentity(project);
7510
+ ensureRuntimeValidationReady(project);
7511
+ ensureRequiredOauthCredentials(project);
7512
+ const client = await createClient(deps);
7513
+ const preflight = await registrationPreflight(project, client);
7514
+ let developerPortalPreflight = null;
7515
+ if (String(project.manifest.price_model ?? "free").toLowerCase() !== "free") {
7516
+ const portal = await client.get_developer_portal();
7517
+ const verifiedDestination = portal.payout_readiness?.verified_destination;
7518
+ if (verifiedDestination !== true) {
7519
+ throw new SiglumeProjectError(
7520
+ "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."
7521
+ );
7522
+ }
7523
+ developerPortalPreflight = toJsonable(portal);
7524
+ }
7525
+ const result = {
7526
+ ok: true,
7527
+ adapter_path: project.adapter_path,
7528
+ registration_preflight: preflight,
7529
+ runtime_validation_path: project.runtime_validation_path ?? null,
7530
+ oauth_credentials_path: project.oauth_credentials_path ?? null
7531
+ };
7532
+ if (developerPortalPreflight) {
7533
+ result.developer_portal_preflight = developerPortalPreflight;
7534
+ }
7535
+ return result;
7536
+ }
7342
7537
  async function createSupportCaseReport(options, deps = {}) {
7343
7538
  const client = await createClient(deps);
7344
7539
  const supportCase = await client.create_support_case(options.subject, options.body, { trace_id: options.trace_id });
@@ -7385,8 +7580,12 @@ async function writeInitTemplate(template, destination) {
7385
7580
  const manifest_path = join(root, "manifest.json");
7386
7581
  const tool_manual_path = join(root, "tool_manual.json");
7387
7582
  const runtime_validation_path = join(root, "runtime_validation.json");
7583
+ const gitignore_path = join(root, ".gitignore");
7388
7584
  const readme_path = join(root, "README.md");
7389
- for (const filePath of [adapter_path, manifest_path, tool_manual_path, runtime_validation_path, readme_path]) {
7585
+ const docs_dir = join(root, "docs");
7586
+ await mkdir(docs_dir, { recursive: true });
7587
+ const docs_usage_path = join(docs_dir, "api-usage.md");
7588
+ for (const filePath of [adapter_path, manifest_path, tool_manual_path, runtime_validation_path, docs_usage_path, readme_path]) {
7390
7589
  if (existsSync(filePath)) {
7391
7590
  throw new SiglumeProjectError(`${basename(filePath)} already exists in ${root}`);
7392
7591
  }
@@ -7397,8 +7596,10 @@ async function writeInitTemplate(template, destination) {
7397
7596
  await writeFile(manifest_path, renderJson(manifest), "utf8");
7398
7597
  await writeFile(tool_manual_path, renderJson(toolManual), "utf8");
7399
7598
  await writeFile(runtime_validation_path, renderJson(buildRuntimeValidationTemplate(toolManual)), "utf8");
7599
+ await writeFile(docs_usage_path, apiUsageDocsTemplate(manifest), "utf8");
7600
+ await writeOrMergeGitignore(gitignore_path);
7400
7601
  await writeFile(readme_path, readmeTemplate(template), "utf8");
7401
- return [adapter_path, manifest_path, tool_manual_path, runtime_validation_path, readme_path];
7602
+ return [adapter_path, manifest_path, tool_manual_path, runtime_validation_path, docs_usage_path, gitignore_path, readme_path];
7402
7603
  }
7403
7604
  async function listOperationCatalog(options = {}, deps = {}) {
7404
7605
  const resolvedAgentId = String(options.agent_id ?? "").trim();
@@ -7792,12 +7993,19 @@ function operationReadmeTemplate(operation, manifest, warning) {
7792
7993
  "- `stubs.ts`: mock fallback used when `SIGLUME_API_KEY` is not set",
7793
7994
  "- `manifest.json`: reviewable manifest snapshot",
7794
7995
  "- `tool_manual.json`: machine-generated ToolManual scaffold",
7795
- "- `runtime_validation.json`: public endpoint and review-key checks used by auto-register",
7996
+ "- `runtime_validation.json`: local public endpoint and review-key checks used by auto-register",
7997
+ "- `docs/api-usage.md`: publishable API usage guide template for `docs_url`",
7998
+ "- `.gitignore`: keeps runtime review keys and OAuth client secrets out of Git",
7796
7999
  "- `tests/test_adapter.ts`: smoke test for `AppTestHarness`",
7797
8000
  "",
7798
8001
  "Before registering, replace all generated placeholders:",
7799
- "- In `adapter.ts` and `manifest.json`, replace `docs_url` and `support_contact` with your public documentation and support contact.",
7800
- "- In `runtime_validation.json`, replace the public URL and review-key placeholders.",
8002
+ "- In `adapter.ts` and `manifest.json`, replace `docs_url` with a dedicated public API usage guide, not a homepage.",
8003
+ "- Replace `support_contact` with a real support email address or public support URL.",
8004
+ "- Optional `seller_homepage_url` is the seller's official site and can stay blank.",
8005
+ "- In the local `runtime_validation.json`, replace the public URL and review-key placeholders.",
8006
+ "- If the API uses seller-side OAuth, create a local `oauth_credentials.json` next to the adapter.",
8007
+ "- Do not commit real review keys or OAuth client secrets; the generated `.gitignore` excludes those files.",
8008
+ "- Because `runtime_validation.json` is ignored, GitHub samples do not commit review-key values.",
7801
8009
  "",
7802
8010
  "## Commands",
7803
8011
  "",
@@ -7809,16 +8017,108 @@ function operationReadmeTemplate(operation, manifest, warning) {
7809
8017
  "siglume score . --offline",
7810
8018
  "```",
7811
8019
  "",
7812
- "After placeholders are replaced and `SIGLUME_API_KEY` is set, run the server-aligned checks and register:",
8020
+ "After placeholders are replaced and `SIGLUME_API_KEY` is issued from Developer Portal -> CLI / API keys, run the server-aligned checks:",
7813
8021
  "",
7814
8022
  "```bash",
7815
8023
  "siglume validate .",
7816
8024
  "siglume score . --remote",
8025
+ "siglume preflight .",
8026
+ "siglume register .",
8027
+ "# inspect the draft, then explicitly approve publish:",
7817
8028
  "siglume register . --confirm",
7818
8029
  "```",
7819
8030
  ""
7820
8031
  ].join("\n");
7821
8032
  }
8033
+ function apiUsageDocsTemplate(manifest) {
8034
+ const name = String(manifest.name ?? manifest.capability_key ?? "Siglume API");
8035
+ const capabilityKey = String(manifest.capability_key ?? "replace-with-capability-key");
8036
+ const jobToBeDone = String(manifest.job_to_be_done ?? "Describe what this API lets an agent do.");
8037
+ const permissionClass = String(manifest.permission_class ?? "read-only");
8038
+ const priceModel = String(manifest.price_model ?? "free");
8039
+ const requiredAccounts = (manifest.required_connected_accounts ?? []).join(", ") || "none";
8040
+ const supportContact = String(manifest.support_contact ?? "replace-with-support-contact");
8041
+ return [
8042
+ `# ${name} API Usage Guide`,
8043
+ "",
8044
+ `This page is the dedicated public usage guide for the Siglume API listing \`${capabilityKey}\`.`,
8045
+ "Publish this page at an anonymous HTTP 200 URL and use that URL as `docs_url`.",
8046
+ "",
8047
+ "Do not use your company homepage as `docs_url`; keep seller/company homepages in `seller_homepage_url`.",
8048
+ "",
8049
+ "## What This API Does",
8050
+ "",
8051
+ jobToBeDone,
8052
+ "",
8053
+ "## Permission Model",
8054
+ "",
8055
+ `- Permission class: \`${permissionClass}\``,
8056
+ `- Price model: \`${priceModel}\``,
8057
+ `- Required connected accounts: \`${requiredAccounts}\``,
8058
+ "",
8059
+ "## Inputs",
8060
+ "",
8061
+ "Describe the request fields your API accepts. Keep this aligned with `tool_manual.json` and `runtime_validation.json`.",
8062
+ "",
8063
+ "## Outputs",
8064
+ "",
8065
+ "Describe the response fields your API returns. Include the fields in `runtime_validation.expected_response_fields`.",
8066
+ "",
8067
+ "## Errors And Support",
8068
+ "",
8069
+ "Explain common error messages and how an owner should recover.",
8070
+ "",
8071
+ `Support contact: ${supportContact}`,
8072
+ ""
8073
+ ].join("\n");
8074
+ }
8075
+ function generatedGitignore() {
8076
+ return [
8077
+ "# Local secrets and registration-only runtime checks.",
8078
+ ".env",
8079
+ ".env.*",
8080
+ "!.env.example",
8081
+ "runtime_validation.json",
8082
+ "runtime-validation.json",
8083
+ "oauth_credentials.json",
8084
+ "oauth-credentials.json",
8085
+ "",
8086
+ "# Python / test artifacts.",
8087
+ "__pycache__/",
8088
+ "*.py[cod]",
8089
+ ".pytest_cache/",
8090
+ ".mypy_cache/",
8091
+ ".coverage",
8092
+ "htmlcov/",
8093
+ "dist/",
8094
+ "build/",
8095
+ "*.egg-info/",
8096
+ "",
8097
+ "# JavaScript tooling if this project also uses TypeScript helpers.",
8098
+ "node_modules/",
8099
+ "coverage/",
8100
+ ""
8101
+ ].join("\n");
8102
+ }
8103
+ async function writeOrMergeGitignore(filePath) {
8104
+ const generated = generatedGitignore();
8105
+ if (!existsSync(filePath)) {
8106
+ await writeFile(filePath, generated, "utf8");
8107
+ return;
8108
+ }
8109
+ const existing = await readFile(filePath, "utf8");
8110
+ const existingEntries = new Set(existing.split(/\r?\n/).map((line) => line.trim()));
8111
+ const additions = generated.split(/\r?\n/).filter((line) => line.trim() && !existingEntries.has(line.trim()));
8112
+ if (additions.length === 0) {
8113
+ return;
8114
+ }
8115
+ const prefix = existing.endsWith("\n") ? existing : `${existing}
8116
+ `;
8117
+ await writeFile(filePath, `${prefix}
8118
+ # Siglume generated ignores.
8119
+ ${additions.join("\n")}
8120
+ `, "utf8");
8121
+ }
7822
8122
  async function writeOperationTemplate(operation_key, destination, options = {}, deps = {}) {
7823
8123
  const root = resolve(destination);
7824
8124
  await mkdir(root, { recursive: true });
@@ -7829,9 +8129,22 @@ async function writeOperationTemplate(operation_key, destination, options = {},
7829
8129
  const manifest_path = join(root, "manifest.json");
7830
8130
  const tool_manual_path = join(root, "tool_manual.json");
7831
8131
  const runtime_validation_path = join(root, "runtime_validation.json");
8132
+ const gitignore_path = join(root, ".gitignore");
7832
8133
  const readme_path = join(root, "README.md");
7833
8134
  const test_path = join(testsDir, "test_adapter.ts");
7834
- for (const filePath of [adapter_path, stubs_path, manifest_path, tool_manual_path, runtime_validation_path, readme_path, test_path]) {
8135
+ const docs_dir = join(root, "docs");
8136
+ await mkdir(docs_dir, { recursive: true });
8137
+ const docs_usage_path = join(docs_dir, "api-usage.md");
8138
+ for (const filePath of [
8139
+ adapter_path,
8140
+ stubs_path,
8141
+ manifest_path,
8142
+ tool_manual_path,
8143
+ runtime_validation_path,
8144
+ docs_usage_path,
8145
+ readme_path,
8146
+ test_path
8147
+ ]) {
7835
8148
  if (existsSync(filePath)) {
7836
8149
  throw new SiglumeProjectError(`${basename(filePath)} already exists in ${root}`);
7837
8150
  }
@@ -7859,10 +8172,22 @@ async function writeOperationTemplate(operation_key, destination, options = {},
7859
8172
  await writeFile(manifest_path, renderJson(manifest), "utf8");
7860
8173
  await writeFile(tool_manual_path, renderJson(tool_manual), "utf8");
7861
8174
  await writeFile(runtime_validation_path, renderJson(buildRuntimeValidationTemplate(tool_manual)), "utf8");
8175
+ await writeFile(docs_usage_path, apiUsageDocsTemplate(manifest), "utf8");
8176
+ await writeOrMergeGitignore(gitignore_path);
7862
8177
  await writeFile(readme_path, operationReadmeTemplate(operation, manifest, warning), "utf8");
7863
8178
  await writeFile(test_path, operationTestSource(operation), "utf8");
7864
8179
  return {
7865
- files: [adapter_path, stubs_path, manifest_path, tool_manual_path, runtime_validation_path, readme_path, test_path],
8180
+ files: [
8181
+ adapter_path,
8182
+ stubs_path,
8183
+ manifest_path,
8184
+ tool_manual_path,
8185
+ runtime_validation_path,
8186
+ docs_usage_path,
8187
+ gitignore_path,
8188
+ readme_path,
8189
+ test_path
8190
+ ],
7866
8191
  operation,
7867
8192
  report: {
7868
8193
  tool_manual_valid,
@@ -8011,6 +8336,15 @@ async function findRuntimeValidationPath(root_dir) {
8011
8336
  }
8012
8337
  return null;
8013
8338
  }
8339
+ async function findOauthCredentialsPath(root_dir) {
8340
+ for (const name of ["oauth_credentials.json", "oauth-credentials.json"]) {
8341
+ const candidate = join(root_dir, name);
8342
+ if (existsSync(candidate)) {
8343
+ return candidate;
8344
+ }
8345
+ }
8346
+ return null;
8347
+ }
8014
8348
  async function loadJsonObject(path, label) {
8015
8349
  let payload;
8016
8350
  try {
@@ -8234,11 +8568,18 @@ function readmeTemplate(template) {
8234
8568
  "- `adapter.ts`: your AppAdapter implementation",
8235
8569
  "- `manifest.json`: serialized AppManifest snapshot",
8236
8570
  "- `tool_manual.json`: editable ToolManual draft for validation and registration",
8237
- "- `runtime_validation.json`: live API smoke-test contract used during registration",
8571
+ "- `runtime_validation.json`: local live API smoke-test contract used during registration",
8572
+ "- `docs/api-usage.md`: publish this page and use its public URL as `docs_url`",
8573
+ "- `.gitignore`: keeps runtime review keys and OAuth client secrets out of Git",
8238
8574
  "",
8239
8575
  "Before registering, replace all generated placeholders:",
8240
- "- In `adapter.ts` and `manifest.json`, replace `docs_url` and `support_contact` with your public documentation and support contact.",
8241
- "- In `runtime_validation.json`, replace the public URL and review-key placeholders.",
8576
+ "- In `adapter.ts` and `manifest.json`, replace `docs_url` with a dedicated public API usage guide, not a homepage.",
8577
+ "- Replace `support_contact` with a real support email address or public support URL.",
8578
+ "- Optional `seller_homepage_url` is the seller's official site and can stay blank.",
8579
+ "- In the local `runtime_validation.json`, replace the public URL and review-key placeholders.",
8580
+ "- If the API uses seller-side OAuth, create a local `oauth_credentials.json` next to the adapter.",
8581
+ "- Do not commit real review keys or OAuth client secrets; the generated `.gitignore` excludes those files.",
8582
+ "- Because `runtime_validation.json` is ignored, GitHub samples do not commit review-key values.",
8242
8583
  "",
8243
8584
  "Suggested workflow:",
8244
8585
  "",
@@ -8249,11 +8590,14 @@ function readmeTemplate(template) {
8249
8590
  "siglume score . --offline",
8250
8591
  "```",
8251
8592
  "",
8252
- "After placeholders are replaced and `SIGLUME_API_KEY` is set, run the server-aligned checks and register:",
8593
+ "After placeholders are replaced and `SIGLUME_API_KEY` is issued from Developer Portal -> CLI / API keys, run the server-aligned checks:",
8253
8594
  "",
8254
8595
  "```bash",
8255
8596
  "siglume validate .",
8256
8597
  "siglume score . --remote",
8598
+ "siglume preflight .",
8599
+ "siglume register .",
8600
+ "# inspect the draft, then explicitly approve publish:",
8257
8601
  "siglume register . --confirm",
8258
8602
  "```",
8259
8603
  ""
@@ -8422,18 +8766,52 @@ async function runCli(argv, deps = {}) {
8422
8766
  throw new SiglumeProjectError("Score failed.");
8423
8767
  }
8424
8768
  });
8425
- 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) => {
8769
+ program.command("preflight").option("--json", "emit machine-readable JSON", false).argument("[path]", ".", "project path").action(async (path, options) => {
8770
+ const report = await runPreflight(path, deps);
8771
+ if (options.json) {
8772
+ emit(stdout, renderJson(report));
8773
+ return;
8774
+ }
8775
+ emit(stdout, "Preflight passed.");
8776
+ const preflight = report.registration_preflight;
8777
+ if (preflight?.remote_quality) {
8778
+ emit(stdout, `preflight_quality: ${preflight.remote_quality.grade} (${preflight.remote_quality.overall_score}/100)`);
8779
+ }
8780
+ if (report.runtime_validation_path) emit(stdout, `runtime_validation_path: ${String(report.runtime_validation_path)}`);
8781
+ if (report.oauth_credentials_path) emit(stdout, `oauth_credentials_path: ${String(report.oauth_credentials_path)}`);
8782
+ });
8783
+ 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) => {
8426
8784
  const report = await runRegistration(path, { confirm: options.confirm, submit_review: options.submitReview }, deps);
8427
8785
  if (options.json) {
8428
8786
  emit(stdout, renderJson(report));
8429
8787
  } else {
8430
8788
  const receipt = report.receipt;
8431
- emit(stdout, "Draft listing created.");
8789
+ if (report.confirmation) {
8790
+ emit(stdout, "Listing published.");
8791
+ } else if (report.review) {
8792
+ emit(stdout, "Listing published via legacy submit-review alias.");
8793
+ } else if (receipt.registration_mode === "upgrade") {
8794
+ emit(stdout, "Upgrade staged.");
8795
+ } else if (receipt.registration_mode === "refresh") {
8796
+ emit(stdout, "Draft refreshed.");
8797
+ } else {
8798
+ emit(stdout, "Draft listing created.");
8799
+ }
8432
8800
  emit(stdout, `listing_id: ${receipt.listing_id}`);
8433
- emit(stdout, `status: ${receipt.status}`);
8801
+ emit(stdout, `receipt_status: ${receipt.status}`);
8802
+ if (receipt.listing_status) emit(stdout, `listing_status: ${receipt.listing_status}`);
8803
+ if (receipt.oauth_status) emit(stdout, `oauth_configured: ${Boolean(receipt.oauth_status.configured)}`);
8434
8804
  if (receipt.review_url) emit(stdout, `review_url: ${receipt.review_url}`);
8435
8805
  if (receipt.trace_id) emit(stdout, `trace_id: ${receipt.trace_id}`);
8436
8806
  if (receipt.request_id) emit(stdout, `request_id: ${receipt.request_id}`);
8807
+ if (report.confirmation) {
8808
+ const confirmation = report.confirmation;
8809
+ if (confirmation.status) emit(stdout, `confirmation_status: ${confirmation.status}`);
8810
+ if (confirmation.release?.release_status) emit(stdout, `release_status: ${confirmation.release.release_status}`);
8811
+ } else if (report.review) {
8812
+ const review = report.review;
8813
+ if (review.status) emit(stdout, `publish_status: ${review.status}`);
8814
+ }
8437
8815
  const preflight = report.registration_preflight;
8438
8816
  if (preflight?.remote_quality) {
8439
8817
  emit(stdout, `preflight_quality: ${preflight.remote_quality.grade} (${preflight.remote_quality.overall_score}/100)`);