xapi-to 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  actionSearch,
14
14
  actionServices,
15
15
  actionStream,
16
+ apiKeyApiRequest,
16
17
  assertAllowedHost,
17
18
  deleteOAuthBinding,
18
19
  enableOAuthForKey,
@@ -48,7 +49,7 @@ import {
48
49
  saveConfig,
49
50
  scheme,
50
51
  showConfig
51
- } from "./chunk-TYY6JR6O.js";
52
+ } from "./chunk-UEQCIJ7T.js";
52
53
 
53
54
  // src/codegen.ts
54
55
  var TARGET_MAP = {
@@ -970,6 +971,203 @@ async function balance(args, flags) {
970
971
  }
971
972
  }
972
973
 
974
+ // src/commands/usage.ts
975
+ var READ_RETRIES = 2;
976
+ var USAGE_HELP = `xapi-to usage - Read a finalized request cost receipt
977
+
978
+ USAGE
979
+ xapi-to usage <request-id> [--format json|pretty|table]
980
+ xapi-to usage wait <request-id> [--interval 1s] [--timeout 30s]
981
+
982
+ Use the request ID returned in X-XAPI-Request-Id or the final xapi.usage SSE event.
983
+ The receipt is visible only to the API key that made the request.
984
+
985
+ "usage wait" polls through the normal finalization window. A 404 means the
986
+ receipt is not finalized yet; invalid credentials and other permanent errors
987
+ still fail immediately.
988
+ `;
989
+ function parsePositiveDurationMs(raw, flagName) {
990
+ const match = raw.trim().toLowerCase().match(/^(\d+)(ms|s|m|h)?$/);
991
+ if (!match) {
992
+ err(`${flagName} must be a duration such as 500ms, 2s, 5m, or 1h`);
993
+ }
994
+ const value = Number(match[1]);
995
+ const unit = match[2] || "ms";
996
+ const multiplier = unit === "h" ? 36e5 : unit === "m" ? 6e4 : unit === "s" ? 1e3 : 1;
997
+ const result = value * multiplier;
998
+ if (!Number.isSafeInteger(result) || result <= 0) {
999
+ err(`${flagName} must be greater than 0`);
1000
+ }
1001
+ return result;
1002
+ }
1003
+ function sleep(ms) {
1004
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
1005
+ }
1006
+ function receiptUrl(requestId) {
1007
+ return `${scheme(XAPI_API_HOST)}://${XAPI_API_HOST}/api/usage/requests/${encodeURIComponent(requestId)}`;
1008
+ }
1009
+ async function fetchReceipt(requestId, apiKey, timeoutMs, retries) {
1010
+ return request(
1011
+ receiptUrl(requestId),
1012
+ {
1013
+ method: "GET",
1014
+ headers: { "XAPI-KEY": apiKey }
1015
+ },
1016
+ timeoutMs,
1017
+ retries
1018
+ );
1019
+ }
1020
+ async function waitForReceipt(requestId, apiKey, flags) {
1021
+ const intervalMs = parsePositiveDurationMs(flags.interval || "1s", "--interval");
1022
+ const timeoutMs = parsePositiveDurationMs(flags.timeout || "30s", "--timeout");
1023
+ const startedAt = Date.now();
1024
+ const deadline = startedAt + timeoutMs;
1025
+ while (true) {
1026
+ const remainingMs = deadline - Date.now();
1027
+ if (remainingMs <= 0) {
1028
+ err(
1029
+ "usage receipt wait timeout",
1030
+ `request_id=${requestId}, elapsed_ms=${Date.now() - startedAt}, timeout_ms=${timeoutMs}`
1031
+ );
1032
+ }
1033
+ try {
1034
+ return await fetchReceipt(requestId, apiKey, remainingMs, 0);
1035
+ } catch (e) {
1036
+ const pending = e instanceof HttpError && e.status === 404;
1037
+ if (!pending && !isRetryableRequestError(e)) throw e;
1038
+ }
1039
+ await sleep(Math.min(intervalMs, Math.max(0, deadline - Date.now())));
1040
+ }
1041
+ }
1042
+ async function usage(args, flags) {
1043
+ if (flags.help) {
1044
+ console.log(USAGE_HELP);
1045
+ return;
1046
+ }
1047
+ const shouldWait = args[0] === "wait";
1048
+ const requestId = args[shouldWait ? 1 : 0]?.trim();
1049
+ if (!requestId) {
1050
+ err(
1051
+ "request ID required",
1052
+ shouldWait ? "Run: xapi-to usage wait <request-id>" : "Run: xapi-to usage <request-id>"
1053
+ );
1054
+ }
1055
+ const cfg = getConfig();
1056
+ requireApiKey(cfg);
1057
+ try {
1058
+ const result = shouldWait ? await waitForReceipt(requestId, cfg.apiKey, flags) : await fetchReceipt(requestId, cfg.apiKey, 3e4, READ_RETRIES);
1059
+ output(result, flags.format);
1060
+ } catch (e) {
1061
+ err(
1062
+ shouldWait ? "usage receipt wait failed" : "usage receipt fetch failed",
1063
+ e.message
1064
+ );
1065
+ }
1066
+ }
1067
+
1068
+ // src/commands/earnings.ts
1069
+ var READ_RETRIES2 = 2;
1070
+ var EARNINGS_HELP = `xapi-to earnings - Inspect and reinvest provider earnings
1071
+
1072
+ USAGE
1073
+ xapi-to earnings [summary] [--format json|pretty|table]
1074
+ xapi-to earnings list [--status PENDING|SETTLED] [--limit 20] [--cursor <id>]
1075
+ xapi-to earnings transfer <amount> --idempotency-key <key>
1076
+
1077
+ SCOPES
1078
+ summary/list earnings:read
1079
+ transfer earnings:transfer
1080
+
1081
+ The transfer is one-way: settled provider earnings become spendable xapi balance.
1082
+ Reuse the same idempotency key only when retrying the same amount.
1083
+ `;
1084
+ function baseUrl2() {
1085
+ return `${scheme(XAPI_API_HOST)}://${XAPI_API_HOST}/api/agent`;
1086
+ }
1087
+ function keyHeaders(apiKey) {
1088
+ return { "Content-Type": "application/json", "XAPI-KEY": apiKey };
1089
+ }
1090
+ async function earnings(args, flags) {
1091
+ if (flags.help) {
1092
+ console.log(EARNINGS_HELP);
1093
+ return;
1094
+ }
1095
+ const cfg = getConfig();
1096
+ requireApiKey(cfg);
1097
+ const apiKey = cfg.apiKey;
1098
+ const subcommand = args[0] ?? "summary";
1099
+ try {
1100
+ if (subcommand === "summary") {
1101
+ const result = await request(
1102
+ `${baseUrl2()}/economy`,
1103
+ { method: "GET", headers: keyHeaders(apiKey) },
1104
+ 3e4,
1105
+ READ_RETRIES2
1106
+ );
1107
+ output(result, flags.format);
1108
+ return;
1109
+ }
1110
+ if (subcommand === "list") {
1111
+ const url = new URL(`${baseUrl2()}/earnings`);
1112
+ if (flags.status) {
1113
+ const status = flags.status.toUpperCase();
1114
+ if (!["PENDING", "SETTLED"].includes(status)) {
1115
+ err("invalid earnings status", "Expected PENDING or SETTLED.");
1116
+ }
1117
+ url.searchParams.set("status", status);
1118
+ }
1119
+ if (flags.limit) {
1120
+ const limit = Number(flags.limit);
1121
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
1122
+ err("invalid earnings limit", "Expected an integer from 1 to 100.");
1123
+ }
1124
+ url.searchParams.set("limit", String(limit));
1125
+ }
1126
+ if (flags.cursor) url.searchParams.set("cursor", flags.cursor);
1127
+ const result = await request(
1128
+ url.toString(),
1129
+ { method: "GET", headers: keyHeaders(apiKey) },
1130
+ 3e4,
1131
+ READ_RETRIES2
1132
+ );
1133
+ output(result, flags.format);
1134
+ return;
1135
+ }
1136
+ if (subcommand === "transfer") {
1137
+ const amount = Number(args[1]);
1138
+ if (!Number.isFinite(amount) || amount <= 0) {
1139
+ err("invalid transfer amount", "Pass a positive USD amount.");
1140
+ }
1141
+ const idempotencyKey = flags["idempotency-key"] || flags.idempotencyKey;
1142
+ if (!idempotencyKey) {
1143
+ err(
1144
+ "idempotency key required",
1145
+ "Pass --idempotency-key <stable-key> and reuse it only when retrying this same transfer."
1146
+ );
1147
+ }
1148
+ const result = await request(
1149
+ `${baseUrl2()}/earnings/transfer`,
1150
+ {
1151
+ method: "POST",
1152
+ headers: keyHeaders(apiKey),
1153
+ body: JSON.stringify({ amount, idempotencyKey })
1154
+ },
1155
+ 3e4,
1156
+ // The server binds the idempotency key to the amount, so transport retries are safe.
1157
+ READ_RETRIES2
1158
+ );
1159
+ output(result, flags.format);
1160
+ return;
1161
+ }
1162
+ err(
1163
+ `unknown earnings command: ${subcommand}`,
1164
+ "Valid commands: summary, list, transfer."
1165
+ );
1166
+ } catch (e) {
1167
+ err("earnings request failed", e.message);
1168
+ }
1169
+ }
1170
+
973
1171
  // src/commands/oauth.ts
974
1172
  var oauth_exports = {};
975
1173
  __export(oauth_exports, {
@@ -1038,11 +1236,11 @@ async function findCurrentKeyRecord(plaintextKey, jwtToken) {
1038
1236
  `Current API key (${prefix}...) was not found in your account keys. Run "xapi-to config set apiKey=<key>" with a valid key before binding OAuth.`
1039
1237
  );
1040
1238
  }
1041
- function resolveScopeDefs(provider) {
1042
- if (Array.isArray(provider.scopeDefinitions) && provider.scopeDefinitions.length > 0) {
1043
- return provider.scopeDefinitions;
1239
+ function resolveScopeDefs(provider2) {
1240
+ if (Array.isArray(provider2.scopeDefinitions) && provider2.scopeDefinitions.length > 0) {
1241
+ return provider2.scopeDefinitions;
1044
1242
  }
1045
- const raw = (provider.defaultScopes || "").split(/[\s,]+/).filter(Boolean);
1243
+ const raw = (provider2.defaultScopes || "").split(/[\s,]+/).filter(Boolean);
1046
1244
  return raw.map((s) => ({
1047
1245
  scope: s,
1048
1246
  label: s,
@@ -1051,21 +1249,21 @@ function resolveScopeDefs(provider) {
1051
1249
  category: ""
1052
1250
  }));
1053
1251
  }
1054
- async function selectScopesInteractive(provider) {
1055
- const defs = resolveScopeDefs(provider);
1252
+ async function selectScopesInteractive(provider2) {
1253
+ const defs = resolveScopeDefs(provider2);
1056
1254
  if (defs.length === 0) return "";
1057
- const required = defs.filter((d) => d.required);
1255
+ const required3 = defs.filter((d) => d.required);
1058
1256
  const optional = defs.filter((d) => !d.required);
1059
1257
  const selected = new Set(defs.map((d) => d.scope));
1060
1258
  if (optional.length === 0) {
1061
- return required.map((d) => d.scope).join(" ");
1259
+ return required3.map((d) => d.scope).join(" ");
1062
1260
  }
1063
1261
  const out = process.stderr;
1064
1262
  let cursor = 0;
1065
1263
  const hint = " \u2191\u2193 navigate \xB7 space toggle \xB7 a all \xB7 n none \xB7 enter confirm";
1066
1264
  const buildFrame = () => {
1067
1265
  const lines = [];
1068
- for (const d of required) {
1266
+ for (const d of required3) {
1069
1267
  const desc = d.description ? ` \u2014 ${d.description}` : "";
1070
1268
  lines.push(` \x1B[2m[*] ${d.label}${desc} (required)\x1B[0m`);
1071
1269
  }
@@ -1088,7 +1286,7 @@ async function selectScopesInteractive(provider) {
1088
1286
  out.write("\x1B[J");
1089
1287
  out.write(buildFrame());
1090
1288
  };
1091
- return new Promise((resolve2) => {
1289
+ return new Promise((resolve4) => {
1092
1290
  const { stdin } = process;
1093
1291
  const wasRaw = stdin.isRaw;
1094
1292
  stdin.setRawMode(true);
@@ -1099,7 +1297,7 @@ async function selectScopesInteractive(provider) {
1099
1297
  stdin.pause();
1100
1298
  out.write("\x1B[?25h");
1101
1299
  out.write("\n");
1102
- resolve2(result);
1300
+ resolve4(result);
1103
1301
  };
1104
1302
  const onData = (buf) => {
1105
1303
  const key = buf.toString();
@@ -1178,10 +1376,10 @@ async function oauthBind(args, flags) {
1178
1376
  if (!Array.isArray(providers) || providers.length === 0) {
1179
1377
  throw new Error("No OAuth providers available");
1180
1378
  }
1181
- const provider = providers.find(
1379
+ const provider2 = providers.find(
1182
1380
  (p) => p.type.toLowerCase() === providerName || p.name.toLowerCase().includes(providerName)
1183
1381
  );
1184
- if (!provider) {
1382
+ if (!provider2) {
1185
1383
  const available = providers.map((p) => p.type).join(", ");
1186
1384
  throw new Error(
1187
1385
  `Provider "${providerName}" not found. Available: ${available}`
@@ -1195,13 +1393,13 @@ async function oauthBind(args, flags) {
1195
1393
  if (flags.scopes) {
1196
1394
  scopes = flags.scopes;
1197
1395
  } else if (isTTY) {
1198
- const defs = resolveScopeDefs(provider);
1396
+ const defs = resolveScopeDefs(provider2);
1199
1397
  if (defs.length > 0) {
1200
1398
  console.error(`
1201
- Provider : ${provider.name}`);
1399
+ Provider : ${provider2.name}`);
1202
1400
  console.error(` API Key : ${keyRecord.keyPreview}`);
1203
1401
  headerPrinted = true;
1204
- scopes = await selectScopesInteractive(provider) || void 0;
1402
+ scopes = await selectScopesInteractive(provider2) || void 0;
1205
1403
  }
1206
1404
  }
1207
1405
  const existingBindingIds = /* @__PURE__ */ new Set();
@@ -1210,7 +1408,7 @@ async function oauthBind(args, flags) {
1210
1408
  const existingBindings = await listOAuthBindings(jwtToken, XAPI_API_HOST);
1211
1409
  if (Array.isArray(existingBindings)) {
1212
1410
  for (const binding of existingBindings) {
1213
- if (binding.apiKeyId === keyRecord.id && binding.providerId === provider.id) {
1411
+ if (binding.apiKeyId === keyRecord.id && binding.providerId === provider2.id) {
1214
1412
  existingBindingIds.add(binding.id);
1215
1413
  }
1216
1414
  }
@@ -1219,7 +1417,7 @@ async function oauthBind(args, flags) {
1219
1417
  }
1220
1418
  }
1221
1419
  const authorizationStartedAt = /* @__PURE__ */ new Date();
1222
- const result = await initiateOAuth(keyRecord.id, provider.id, jwtToken, XAPI_API_HOST, scopes);
1420
+ const result = await initiateOAuth(keyRecord.id, provider2.id, jwtToken, XAPI_API_HOST, scopes);
1223
1421
  const { authorizationUrl } = result;
1224
1422
  let authorizationTarget;
1225
1423
  try {
@@ -1234,7 +1432,7 @@ async function oauthBind(args, flags) {
1234
1432
  if (isTTY) {
1235
1433
  if (!headerPrinted) {
1236
1434
  console.error(`
1237
- Provider : ${provider.name}`);
1435
+ Provider : ${provider2.name}`);
1238
1436
  console.error(` API Key : ${keyRecord.keyPreview}`);
1239
1437
  }
1240
1438
  if (scopes) {
@@ -1249,7 +1447,7 @@ async function oauthBind(args, flags) {
1249
1447
  console.error(" Waiting for you to complete authorization in the browser...\n");
1250
1448
  const binding = await pollForBinding(
1251
1449
  keyRecord.id,
1252
- provider.id,
1450
+ provider2.id,
1253
1451
  jwtToken,
1254
1452
  authorizationStartedAt,
1255
1453
  existingBindingIds
@@ -1260,14 +1458,14 @@ async function oauthBind(args, flags) {
1260
1458
  console.error(`
1261
1459
  Authorization complete! Bound to @${account}
1262
1460
  `);
1263
- output({ status: "success", provider: provider.name, account, scopes }, flags.format);
1461
+ output({ status: "success", provider: provider2.name, account, scopes }, flags.format);
1264
1462
  } else {
1265
1463
  err("oauth bind timed out", 'Authorization was not completed within 5 minutes. Run "xapi-to oauth bind" again.');
1266
1464
  }
1267
1465
  } else {
1268
1466
  output({
1269
1467
  status: "pending",
1270
- provider: provider.name,
1468
+ provider: provider2.name,
1271
1469
  apiKey: keyRecord.keyPreview,
1272
1470
  authorizationUrl,
1273
1471
  scopes
@@ -1403,7 +1601,7 @@ function parseDurationMs(raw) {
1403
1601
  return value;
1404
1602
  }
1405
1603
  }
1406
- function parsePositiveDurationMs(raw, flagName) {
1604
+ function parsePositiveDurationMs2(raw, flagName) {
1407
1605
  const ms = parseDurationMs(raw);
1408
1606
  if (ms <= 0) {
1409
1607
  err(`${flagName} must be greater than 0`);
@@ -1417,9 +1615,9 @@ function parsePositiveInt(raw, flagName) {
1417
1615
  }
1418
1616
  return n;
1419
1617
  }
1420
- function sleep(ms) {
1618
+ function sleep2(ms) {
1421
1619
  if (ms <= 0) return Promise.resolve();
1422
- return new Promise((resolve2) => setTimeout(resolve2, ms));
1620
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
1423
1621
  }
1424
1622
  function extractTaskPayload(res) {
1425
1623
  if (res && typeof res === "object") {
@@ -1457,7 +1655,7 @@ async function taskWait(args, flags) {
1457
1655
  showHelpIfRequested2(flags, WAIT_HELP);
1458
1656
  const taskId = args[0];
1459
1657
  if (!taskId) err("usage: xapi-to task wait <task_id>");
1460
- const intervalMs = parsePositiveDurationMs(flags.interval || "2s", "--interval");
1658
+ const intervalMs = parsePositiveDurationMs2(flags.interval || "2s", "--interval");
1461
1659
  const timeoutMs = flags.timeout ? parseDurationMs(flags.timeout) : void 0;
1462
1660
  const maxAttempts = flags["max-attempts"] ? parsePositiveInt(flags["max-attempts"], "--max-attempts") : void 0;
1463
1661
  const cfg = getConfig();
@@ -1514,7 +1712,7 @@ async function taskWait(args, flags) {
1514
1712
  }
1515
1713
  const desiredWaitMs = retryDelayMs ?? intervalMs;
1516
1714
  const waitMs = deadline !== void 0 ? Math.min(desiredWaitMs, Math.max(0, deadline - Date.now())) : desiredWaitMs;
1517
- await sleep(waitMs);
1715
+ await sleep2(waitMs);
1518
1716
  }
1519
1717
  }
1520
1718
  function taskHelp() {
@@ -1561,6 +1759,7 @@ SELECTION FLAGS
1561
1759
  --cpu N --memory N --volume N Minimum resources
1562
1760
  --gpu-count N --gpu-model NAME GPU requirements
1563
1761
  --regions a,b Allowed regions
1762
+ --min-runtime 24h Minimum documented continuous runtime
1564
1763
  --requirements <json> Complete requirements object
1565
1764
  --max-hourly-usd N Price ceiling (sandbox run default: 0.20)
1566
1765
 
@@ -1602,6 +1801,7 @@ SELECTION
1602
1801
  --cpu N --memory N --volume N Minimum resources
1603
1802
  --gpu-count N --gpu-model NAME GPU requirements
1604
1803
  --regions a,b Allowed regions
1804
+ --min-runtime 24h Minimum documented continuous runtime
1605
1805
  --requirements <json> Complete requirements object
1606
1806
  --max-hourly-usd N Hard hourly price ceiling
1607
1807
 
@@ -1705,6 +1905,7 @@ var SELECTION_FLAGS = [
1705
1905
  "gpu-count",
1706
1906
  "gpu-model",
1707
1907
  "regions",
1908
+ "min-runtime",
1708
1909
  "requirements",
1709
1910
  "max-hourly-usd"
1710
1911
  ];
@@ -1826,10 +2027,10 @@ function positiveInteger(raw, name) {
1826
2027
  function durationMs(raw, fallback, name) {
1827
2028
  if (raw === void 0) return fallback;
1828
2029
  if (raw === "true") err(`--${name} requires a value`);
1829
- const match = raw.trim().toLowerCase().match(/^(\d+)(ms|s|m|h)?$/);
1830
- if (!match || Number(match[1]) <= 0) err(`--${name} must be a duration like 500ms, 2s, 5m, or 1h`);
2030
+ const match = raw.trim().toLowerCase().match(/^(\d+)(ms|s|m|h|d)?$/);
2031
+ if (!match || Number(match[1]) <= 0) err(`--${name} must be a duration like 500ms, 2s, 5m, 1h, or 1d`);
1831
2032
  const value = Number(match[1]);
1832
- return value * { ms: 1, s: 1e3, m: 6e4, h: 36e5 }[match[2] || "ms"];
2033
+ return value * { ms: 1, s: 1e3, m: 6e4, h: 36e5, d: 864e5 }[match[2] || "ms"];
1833
2034
  }
1834
2035
  function jsonObject(raw, name) {
1835
2036
  try {
@@ -1848,8 +2049,8 @@ function sandboxOptions(flags) {
1848
2049
  const cfg = getConfig();
1849
2050
  requireApiKey(cfg);
1850
2051
  const host = flagValue(flags, "host") || cfg.sandboxHost || XAPI_SANDBOX_HOST;
1851
- const provider = flagValue(flags, "provider");
1852
- return { sandboxHost: host, apiKey: cfg.apiKey, ...provider ? { provider } : {} };
2052
+ const provider2 = flagValue(flags, "provider");
2053
+ return { sandboxHost: host, apiKey: cfg.apiKey, ...provider2 ? { provider: provider2 } : {} };
1853
2054
  }
1854
2055
  function requirementsFromFlags(flags, defaultCapabilities) {
1855
2056
  const requirements = flagValue(flags, "requirements") ? jsonObject(flagValue(flags, "requirements"), "requirements") : {};
@@ -1860,6 +2061,7 @@ function requirementsFromFlags(flags, defaultCapabilities) {
1860
2061
  const volume = positiveNumber(flagValue(flags, "volume"), "volume");
1861
2062
  const gpuCount = positiveInteger(flagValue(flags, "gpu-count"), "gpu-count");
1862
2063
  const gpuModel = flagValue(flags, "gpu-model");
2064
+ const minRuntime = flagValue(flags, "min-runtime");
1863
2065
  if (gpuModel && gpuCount === void 0 && !(Number(requirements.gpu?.count) > 0)) {
1864
2066
  err("--gpu-model requires --gpu-count (or requirements.gpu.count)");
1865
2067
  }
@@ -1875,6 +2077,11 @@ function requirementsFromFlags(flags, defaultCapabilities) {
1875
2077
  ...gpuModel ? { model: gpuModel } : {}
1876
2078
  };
1877
2079
  }
2080
+ if (minRuntime !== void 0) {
2081
+ requirements.minContinuousRuntimeSeconds = Math.ceil(
2082
+ durationMs(minRuntime, 0, "min-runtime") / 1e3
2083
+ );
2084
+ }
1878
2085
  return requirements;
1879
2086
  }
1880
2087
  function quoteBody(flags, defaultCapabilities) {
@@ -1894,14 +2101,14 @@ function waitSettings(flags) {
1894
2101
  intervalMs: durationMs(flags.interval, 2e3, "interval")
1895
2102
  };
1896
2103
  }
1897
- function commandFrom(args, flags, usage) {
2104
+ function commandFrom(args, flags, usage2) {
1898
2105
  const fromFlag = flagValue(flags, "command");
1899
2106
  const command = fromFlag ?? args.join(" ");
1900
- if (!command.trim()) err(usage);
2107
+ if (!command.trim()) err(usage2);
1901
2108
  return command;
1902
2109
  }
1903
- function instanceId(args, usage) {
1904
- if (!args[0]) err(usage);
2110
+ function instanceId(args, usage2) {
2111
+ if (!args[0]) err(usage2);
1905
2112
  return args[0];
1906
2113
  }
1907
2114
  async function terminateAndWait(opts, id, flags) {
@@ -1921,7 +2128,7 @@ async function terminateAndWait(opts, id, flags) {
1921
2128
  break;
1922
2129
  } catch (error) {
1923
2130
  if (!(error instanceof HttpError) || error.status !== 409) throw error;
1924
- await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
2131
+ await new Promise((resolve4) => setTimeout(resolve4, intervalMs));
1925
2132
  }
1926
2133
  }
1927
2134
  const remaining = Math.max(1, deadline - Date.now());
@@ -2042,7 +2249,8 @@ async function sandboxCreate2(args, flags) {
2042
2249
  "volume",
2043
2250
  "gpu-count",
2044
2251
  "gpu-model",
2045
- "regions"
2252
+ "regions",
2253
+ "min-runtime"
2046
2254
  ].filter((name) => flagValue(flags, name) !== void 0);
2047
2255
  if (quoteId && offeringId) err("--quote-id and --offering-id are mutually exclusive");
2048
2256
  if ((quoteId || offeringId) && requirementFlags.length) {
@@ -2399,6 +2607,512 @@ async function sandboxRun(args, flags) {
2399
2607
  if (remoteExitCode !== void 0) process.exitCode = remoteExitCode;
2400
2608
  }
2401
2609
 
2610
+ // src/commands/provider.ts
2611
+ import { mkdir, open as open2, readFile as readFile2 } from "fs/promises";
2612
+ import { dirname, resolve as resolve2 } from "path";
2613
+ var READ_RETRIES3 = 2;
2614
+ var BASE = "/api/api-services/agent";
2615
+ var PROVIDER_HELP = `xapi-to provider - Manage provider services and their content
2616
+
2617
+ USAGE
2618
+ xapi-to provider list
2619
+ xapi-to provider get <service-id> [--version <version>]
2620
+ xapi-to provider create --file <service.json>
2621
+ xapi-to provider update <service-id> [metadata flags]
2622
+ xapi-to provider versions <service-id>
2623
+ xapi-to provider version update <service-id> <version-id> --file <contract.json> [--replace]
2624
+ xapi-to provider major create <service-id>
2625
+ xapi-to provider revision start <service-id> <major>
2626
+ xapi-to provider publish <service-id> <revision-id> [--changelog <text>|--changelog-file <path>]
2627
+ xapi-to provider rollback <service-id> <major> --revision <revision-id> [--reason <text>|--reason-file <path>]
2628
+ xapi-to provider default-major <service-id> <major>
2629
+ xapi-to provider deprecate|restore <service-id> <major>
2630
+ xapi-to provider review <service-id> <revision-id>
2631
+ xapi-to provider diff <service-id> <major>
2632
+ xapi-to provider metrics [service-id] [--days 30]
2633
+ xapi-to provider events [--after <cursor>] [--limit 50]
2634
+ xapi-to provider skill context <service-id>
2635
+ xapi-to provider skill scaffold <service-id> --output <SKILL.md> [--force]
2636
+ xapi-to provider skill link <service-id> <skill-id>
2637
+ xapi-to provider skill unlink <service-id>
2638
+ xapi-to provider skill fingerprint <service-id> [--skill-version-id <id>]
2639
+ xapi-to provider delete <service-id> --confirm <service-name-or-id>
2640
+
2641
+ METADATA FLAGS
2642
+ --file <metadata.json> Read metadata from JSON
2643
+ --name <name> Service display name
2644
+ --description <text> Marketplace card description
2645
+ --description-file <path|-> Read description from a file or stdin
2646
+ --about <markdown> Long About content
2647
+ --about-file <path|-> Read About Markdown from a file or stdin
2648
+ --clear-about Clear About content
2649
+ --website <url> Public service website
2650
+ --clear-website Clear website
2651
+ --logo-url <url> Service logo URL
2652
+ --category <category> Marketplace category
2653
+
2654
+ SCOPES
2655
+ list/get/versions/review/diff/skill context: service:read
2656
+ create: service:create
2657
+ update/version update/skill link/fingerprint: service:update
2658
+ major/revision start: version:create
2659
+ publish: service:publish
2660
+ rollback/default-major/deprecate/restore: service:rollback
2661
+ metrics/events: observability:read
2662
+ delete: service:delete
2663
+ `;
2664
+ function servicePath(serviceId, suffix = "") {
2665
+ return `${BASE}/services/${encodeURIComponent(serviceId)}${suffix}`;
2666
+ }
2667
+ function required(value, usage2) {
2668
+ if (!value?.trim()) err(`usage: ${usage2}`);
2669
+ return value.trim();
2670
+ }
2671
+ function requiredFlag(value, usage2) {
2672
+ if (!value?.trim() || value === "true") err(`usage: ${usage2}`);
2673
+ return value.trim();
2674
+ }
2675
+ function optionalFlag(value, flagName) {
2676
+ if (value === void 0) return void 0;
2677
+ if (value === "true") err(`${flagName} requires a value`);
2678
+ return value;
2679
+ }
2680
+ function positiveInt(raw, name, max) {
2681
+ if (raw === void 0) return void 0;
2682
+ const value = Number(raw);
2683
+ if (!Number.isInteger(value) || value < 1 || max !== void 0 && value > max) {
2684
+ err(`invalid ${name}`, `Expected an integer from 1${max ? ` to ${max}` : ""}.`);
2685
+ }
2686
+ return value;
2687
+ }
2688
+ function boolFlag(flags, name) {
2689
+ return ["true", "1", "yes"].includes((flags[name] || "").toLowerCase());
2690
+ }
2691
+ async function readText(path, flagName) {
2692
+ if (path === "true") err(`${flagName} requires a path or - for stdin`);
2693
+ if (path === "-") {
2694
+ const chunks = [];
2695
+ for await (const chunk of process.stdin) {
2696
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
2697
+ }
2698
+ return Buffer.concat(chunks).toString("utf8");
2699
+ }
2700
+ return readFile2(resolve2(path), "utf8");
2701
+ }
2702
+ async function textOption(flags, directName, fileName) {
2703
+ const direct = flags[directName];
2704
+ const file = flags[fileName];
2705
+ if (direct !== void 0 && file !== void 0) {
2706
+ err(`--${directName} and --${fileName} are mutually exclusive`);
2707
+ }
2708
+ if (direct === "true") err(`--${directName} requires a value`);
2709
+ if (file !== void 0) return readText(file, `--${fileName}`);
2710
+ return direct;
2711
+ }
2712
+ async function readJsonObject(path, flagName = "--file") {
2713
+ if (!path || path === "true") err(`${flagName} requires a JSON file path or - for stdin`);
2714
+ let parsed;
2715
+ try {
2716
+ parsed = JSON.parse(await readText(path, flagName));
2717
+ } catch (error) {
2718
+ err(`invalid JSON from ${flagName}`, error.message);
2719
+ }
2720
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2721
+ err(`${flagName} must contain a JSON object`);
2722
+ }
2723
+ return parsed;
2724
+ }
2725
+ async function metadataBody(flags) {
2726
+ const body = flags.file ? await readJsonObject(flags.file) : {};
2727
+ const description = await textOption(flags, "description", "description-file");
2728
+ const about = await textOption(flags, "about", "about-file");
2729
+ if (boolFlag(flags, "clear-about") && about !== void 0) {
2730
+ err("--clear-about cannot be combined with --about or --about-file");
2731
+ }
2732
+ if (boolFlag(flags, "clear-website") && flags.website !== void 0) {
2733
+ err("--clear-website cannot be combined with --website");
2734
+ }
2735
+ if (description !== void 0) body.description = description;
2736
+ if (about !== void 0) body.aboutMarkdown = about;
2737
+ for (const [flagName, fieldName] of [
2738
+ ["name", "name"],
2739
+ ["website", "website"],
2740
+ ["logo-url", "logoUrl"],
2741
+ ["category", "category"]
2742
+ ]) {
2743
+ if (flags[flagName] === "true") err(`--${flagName} requires a value`);
2744
+ if (flags[flagName] !== void 0) body[fieldName] = flags[flagName];
2745
+ }
2746
+ if (boolFlag(flags, "clear-about")) body.aboutMarkdown = null;
2747
+ if (boolFlag(flags, "clear-website")) body.website = null;
2748
+ if (Object.keys(body).length === 0) {
2749
+ err("no provider metadata supplied", "Pass --file or at least one metadata flag.");
2750
+ }
2751
+ return body;
2752
+ }
2753
+ async function writeExclusive(path, content, force) {
2754
+ const target = resolve2(path);
2755
+ await mkdir(dirname(target), { recursive: true });
2756
+ const handle = await open2(target, force ? "w" : "wx");
2757
+ try {
2758
+ await handle.writeFile(content, "utf8");
2759
+ } finally {
2760
+ await handle.close();
2761
+ }
2762
+ return target;
2763
+ }
2764
+ async function provider(args, flags) {
2765
+ if (flags.help || args.length === 0) {
2766
+ console.log(PROVIDER_HELP);
2767
+ return;
2768
+ }
2769
+ const cfg = getConfig();
2770
+ requireApiKey(cfg);
2771
+ const apiKey = cfg.apiKey;
2772
+ const [command, ...rest] = args;
2773
+ try {
2774
+ let result;
2775
+ switch (command) {
2776
+ case "list":
2777
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, `${BASE}/services`, { retries: READ_RETRIES3 });
2778
+ break;
2779
+ case "get": {
2780
+ const id = required(rest[0], "xapi-to provider get <service-id>");
2781
+ const path = new URL(`https://placeholder${servicePath(id)}`);
2782
+ const version = optionalFlag(flags.version, "--version");
2783
+ if (version !== void 0) path.searchParams.set("version", version);
2784
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, `${path.pathname}${path.search}`, { retries: READ_RETRIES3 });
2785
+ break;
2786
+ }
2787
+ case "create":
2788
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, `${BASE}/services`, {
2789
+ method: "POST",
2790
+ body: await readJsonObject(required(flags.file, "xapi-to provider create --file <service.json>"))
2791
+ });
2792
+ break;
2793
+ case "update": {
2794
+ const id = required(rest[0], "xapi-to provider update <service-id> [metadata flags]");
2795
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id), {
2796
+ method: "PATCH",
2797
+ body: await metadataBody(flags)
2798
+ });
2799
+ break;
2800
+ }
2801
+ case "versions": {
2802
+ const id = required(rest[0], "xapi-to provider versions <service-id>");
2803
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id, "/version-overview"), { retries: READ_RETRIES3 });
2804
+ break;
2805
+ }
2806
+ case "version": {
2807
+ if (rest[0] !== "update") err("usage: xapi-to provider version update <service-id> <version-id> --file <contract.json> [--replace]");
2808
+ const id = required(rest[1], "xapi-to provider version update <service-id> <version-id> --file <contract.json>");
2809
+ const versionId = required(rest[2], "xapi-to provider version update <service-id> <version-id> --file <contract.json>");
2810
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id, `/versions/${encodeURIComponent(versionId)}`), {
2811
+ method: boolFlag(flags, "replace") ? "PUT" : "PATCH",
2812
+ body: await readJsonObject(required(flags.file, "xapi-to provider version update <service-id> <version-id> --file <contract.json>"))
2813
+ });
2814
+ break;
2815
+ }
2816
+ case "major": {
2817
+ if (rest[0] !== "create") err("usage: xapi-to provider major create <service-id>");
2818
+ const id = required(rest[1], "xapi-to provider major create <service-id>");
2819
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id, "/majors"), { method: "POST" });
2820
+ break;
2821
+ }
2822
+ case "revision": {
2823
+ if (rest[0] !== "start") err("usage: xapi-to provider revision start <service-id> <major>");
2824
+ const id = required(rest[1], "xapi-to provider revision start <service-id> <major>");
2825
+ const major = positiveInt(required(rest[2], "xapi-to provider revision start <service-id> <major>"), "major");
2826
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id, `/majors/${major}/working-revision`), { method: "POST" });
2827
+ break;
2828
+ }
2829
+ case "publish": {
2830
+ const id = required(rest[0], "xapi-to provider publish <service-id> <revision-id>");
2831
+ const revisionId = required(rest[1], "xapi-to provider publish <service-id> <revision-id>");
2832
+ const changelog = await textOption(flags, "changelog", "changelog-file");
2833
+ if (changelog !== void 0 && changelog.length > 2e3) err("changelog is too long", "Maximum length is 2000 characters.");
2834
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id, `/revisions/${encodeURIComponent(revisionId)}/submit`), {
2835
+ method: "POST",
2836
+ body: changelog === void 0 ? {} : { changelog }
2837
+ });
2838
+ break;
2839
+ }
2840
+ case "rollback": {
2841
+ const id = required(rest[0], "xapi-to provider rollback <service-id> <major> --revision <revision-id>");
2842
+ const major = positiveInt(required(rest[1], "xapi-to provider rollback <service-id> <major> --revision <revision-id>"), "major");
2843
+ const revisionId = requiredFlag(flags.revision, "xapi-to provider rollback <service-id> <major> --revision <revision-id>");
2844
+ const reason = await textOption(flags, "reason", "reason-file");
2845
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id, `/majors/${major}/rollback`), {
2846
+ method: "POST",
2847
+ body: { revisionId, ...reason !== void 0 ? { reason } : {} }
2848
+ });
2849
+ break;
2850
+ }
2851
+ case "default-major": {
2852
+ const id = required(rest[0], "xapi-to provider default-major <service-id> <major>");
2853
+ const major = positiveInt(required(rest[1], "xapi-to provider default-major <service-id> <major>"), "major");
2854
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id, "/default-major"), { method: "PUT", body: { major } });
2855
+ break;
2856
+ }
2857
+ case "deprecate":
2858
+ case "restore": {
2859
+ const id = required(rest[0], `xapi-to provider ${command} <service-id> <major>`);
2860
+ const major = positiveInt(required(rest[1], `xapi-to provider ${command} <service-id> <major>`), "major");
2861
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id, `/majors/${major}/deprecated`), {
2862
+ method: "PUT",
2863
+ body: { deprecated: command === "deprecate" }
2864
+ });
2865
+ break;
2866
+ }
2867
+ case "review": {
2868
+ const id = required(rest[0], "xapi-to provider review <service-id> <revision-id>");
2869
+ const revisionId = required(rest[1], "xapi-to provider review <service-id> <revision-id>");
2870
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id, `/revisions/${encodeURIComponent(revisionId)}/review`), { retries: READ_RETRIES3 });
2871
+ break;
2872
+ }
2873
+ case "diff": {
2874
+ const id = required(rest[0], "xapi-to provider diff <service-id> <major>");
2875
+ const major = positiveInt(required(rest[1], "xapi-to provider diff <service-id> <major>"), "major");
2876
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id, `/majors/${major}/diff-preview`), { retries: READ_RETRIES3 });
2877
+ break;
2878
+ }
2879
+ case "metrics": {
2880
+ const days = positiveInt(flags.days, "days", 365);
2881
+ const path = new URL(`https://placeholder${rest[0] ? servicePath(rest[0], "/metrics") : `${BASE}/metrics`}`);
2882
+ if (days) path.searchParams.set("days", String(days));
2883
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, `${path.pathname}${path.search}`, { retries: READ_RETRIES3 });
2884
+ break;
2885
+ }
2886
+ case "events": {
2887
+ const limit = positiveInt(flags.limit, "limit", 100);
2888
+ const path = new URL("https://placeholder/api/agent/events");
2889
+ const after = optionalFlag(flags.after, "--after");
2890
+ if (after !== void 0) path.searchParams.set("after", after);
2891
+ if (limit) path.searchParams.set("limit", String(limit));
2892
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, `${path.pathname}${path.search}`, { retries: READ_RETRIES3 });
2893
+ break;
2894
+ }
2895
+ case "skill": {
2896
+ const subcommand = required(rest[0], "xapi-to provider skill context|scaffold|link|unlink|fingerprint ...");
2897
+ const id = required(rest[1], `xapi-to provider skill ${subcommand} <service-id>`);
2898
+ if (subcommand === "context" || subcommand === "scaffold") {
2899
+ const destination = subcommand === "scaffold" ? requiredFlag(flags.output, "xapi-to provider skill scaffold <service-id> --output <SKILL.md>") : void 0;
2900
+ const context = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id, "/skill-context"), { retries: READ_RETRIES3 });
2901
+ if (subcommand === "scaffold") {
2902
+ if (!context || typeof context.scaffoldMarkdown !== "string") throw new Error("skill context response is missing scaffoldMarkdown");
2903
+ const savedTo = await writeExclusive(destination, context.scaffoldMarkdown, boolFlag(flags, "force"));
2904
+ result = { serviceId: id, savedTo, currentFingerprint: context.currentFingerprint };
2905
+ } else {
2906
+ result = context;
2907
+ }
2908
+ } else if (subcommand === "link") {
2909
+ const skillId = required(rest[2], "xapi-to provider skill link <service-id> <skill-id>");
2910
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id), { method: "PATCH", body: { linkedSkillId: skillId } });
2911
+ } else if (subcommand === "unlink") {
2912
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id), { method: "PATCH", body: { linkedSkillId: null } });
2913
+ } else if (subcommand === "fingerprint") {
2914
+ const skillVersionId = optionalFlag(flags["skill-version-id"], "--skill-version-id");
2915
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id, "/skill-fingerprint"), {
2916
+ method: "PUT",
2917
+ body: skillVersionId !== void 0 ? { skillVersionId } : {}
2918
+ });
2919
+ } else {
2920
+ err(`unknown provider skill command: ${subcommand}`, "Valid commands: context, scaffold, link, unlink, fingerprint.");
2921
+ }
2922
+ break;
2923
+ }
2924
+ case "delete": {
2925
+ const id = required(rest[0], "xapi-to provider delete <service-id> --confirm <service-name-or-id>");
2926
+ const confirm = requiredFlag(flags.confirm, "xapi-to provider delete <service-id> --confirm <service-name-or-id>");
2927
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, servicePath(id), { method: "DELETE", body: { confirm } });
2928
+ break;
2929
+ }
2930
+ default:
2931
+ err(`unknown provider command: ${command}`, 'Run "xapi-to provider --help".');
2932
+ }
2933
+ output(result, flags.format);
2934
+ } catch (error) {
2935
+ err("provider request failed", error.message);
2936
+ }
2937
+ }
2938
+
2939
+ // src/commands/skill.ts
2940
+ import { readdir, readFile as readFile3 } from "fs/promises";
2941
+ import { relative, resolve as resolve3, sep } from "path";
2942
+ var READ_RETRIES4 = 2;
2943
+ var MAX_FILES = 100;
2944
+ var MAX_PACKAGE_BYTES = 2 * 1024 * 1024;
2945
+ var MAX_FILE_BYTES = 512 * 1024;
2946
+ var BASE2 = "/api/skills/agent";
2947
+ var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
2948
+ var SKILL_HELP = `xapi-to skill - Upload and publish service usage skills
2949
+
2950
+ USAGE
2951
+ xapi-to skill spec
2952
+ xapi-to skill submit --dir <skill-directory>
2953
+ xapi-to skill submit --github <public-github-url> [metadata flags]
2954
+ xapi-to skill status <submission-id>
2955
+ xapi-to skill wait <submission-id> [--interval 2s] [--timeout 10m]
2956
+
2957
+ GITHUB METADATA FLAGS
2958
+ --version <semver>
2959
+ --name <display-name>
2960
+ --description <text>
2961
+ --category <value> Repeat is not supported; use comma-separated values
2962
+ --tag <value> Repeat is not supported; use comma-separated values
2963
+
2964
+ Local submissions recursively upload regular files. Symlinks, .git, and
2965
+ node_modules are excluded. The package must contain SKILL.md, have at most
2966
+ 100 files, keep each file at or below 512 KiB, and keep the encoded package
2967
+ at or below 2 MiB.
2968
+
2969
+ SCOPES
2970
+ spec/status/wait: skill:read
2971
+ submit: skill:submit
2972
+ `;
2973
+ function required2(value, usage2) {
2974
+ if (!value?.trim() || value === "true") err(`usage: ${usage2}`);
2975
+ return value.trim();
2976
+ }
2977
+ function parseDuration(raw, flagName) {
2978
+ const match = raw.trim().toLowerCase().match(/^(\d+)(ms|s|m|h)?$/);
2979
+ if (!match) err(`${flagName} must be a duration such as 500ms, 2s, 5m, or 1h`);
2980
+ const value = Number(match[1]);
2981
+ const multiplier = match[2] === "h" ? 36e5 : match[2] === "m" ? 6e4 : match[2] === "s" ? 1e3 : 1;
2982
+ const result = value * multiplier;
2983
+ if (!Number.isSafeInteger(result) || result <= 0) err(`${flagName} must be greater than 0`);
2984
+ return result;
2985
+ }
2986
+ function listFlag(value) {
2987
+ if (!value || value === "true") return void 0;
2988
+ const items = [...new Set(value.split(",").map((item) => item.trim()).filter(Boolean))];
2989
+ return items.length ? items : void 0;
2990
+ }
2991
+ async function collectInlineFiles(directory) {
2992
+ const root = resolve3(directory);
2993
+ const files = [];
2994
+ async function walk(current) {
2995
+ const entries = await readdir(current, { withFileTypes: true });
2996
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
2997
+ if (entry.isSymbolicLink()) continue;
2998
+ const absolute = resolve3(current, entry.name);
2999
+ if (entry.isDirectory()) {
3000
+ if (!IGNORED_DIRECTORIES.has(entry.name)) await walk(absolute);
3001
+ continue;
3002
+ }
3003
+ if (!entry.isFile()) continue;
3004
+ if (files.length >= MAX_FILES) {
3005
+ throw new Error(`skill package exceeds ${MAX_FILES} files`);
3006
+ }
3007
+ const content = await readFile3(absolute);
3008
+ if (content.byteLength > MAX_FILE_BYTES) {
3009
+ throw new Error(`skill file ${entry.name} exceeds ${MAX_FILE_BYTES} bytes`);
3010
+ }
3011
+ const path = relative(root, absolute).split(sep).join("/");
3012
+ if (!path || path.startsWith("../")) throw new Error("skill file escaped the selected directory");
3013
+ files.push({ path, contentBase64: content.toString("base64") });
3014
+ }
3015
+ }
3016
+ await walk(root);
3017
+ if (!files.some((file) => file.path === "SKILL.md")) {
3018
+ throw new Error("skill package root must contain SKILL.md");
3019
+ }
3020
+ const encodedBytes = Buffer.byteLength(JSON.stringify({ sourceType: "inline", files }), "utf8");
3021
+ if (encodedBytes > MAX_PACKAGE_BYTES) {
3022
+ throw new Error(`encoded skill package exceeds ${MAX_PACKAGE_BYTES} bytes`);
3023
+ }
3024
+ return files;
3025
+ }
3026
+ function statusOf(value) {
3027
+ return value?.status || value?.submission?.status || value?.skill?.status;
3028
+ }
3029
+ function sleep3(ms) {
3030
+ return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
3031
+ }
3032
+ async function getSubmission(apiKey, id, timeoutMs = 3e4, retries = READ_RETRIES4) {
3033
+ return apiKeyApiRequest(
3034
+ XAPI_API_HOST,
3035
+ apiKey,
3036
+ `${BASE2}/submissions/${encodeURIComponent(id)}`,
3037
+ { timeoutMs, retries }
3038
+ );
3039
+ }
3040
+ async function waitForSubmission(apiKey, id, flags) {
3041
+ const intervalMs = parseDuration(flags.interval || "2s", "--interval");
3042
+ const timeoutMs = parseDuration(flags.timeout || "10m", "--timeout");
3043
+ const startedAt = Date.now();
3044
+ const deadline = startedAt + timeoutMs;
3045
+ while (true) {
3046
+ const remaining = deadline - Date.now();
3047
+ if (remaining <= 0) {
3048
+ err("skill wait timeout", `submission_id=${id}, elapsed_ms=${Date.now() - startedAt}, timeout_ms=${timeoutMs}`);
3049
+ }
3050
+ try {
3051
+ const result = await getSubmission(apiKey, id, remaining, 0);
3052
+ const status = statusOf(result);
3053
+ if (status === "PUBLISHED") return result;
3054
+ if (["NEEDS_CHANGES", "REJECTED", "SUSPENDED", "ARCHIVED"].includes(String(status))) {
3055
+ output(result, flags.format);
3056
+ process.exit(1);
3057
+ }
3058
+ } catch (error) {
3059
+ const pending = error instanceof HttpError && error.status === 404;
3060
+ if (!pending && !isRetryableRequestError(error)) throw error;
3061
+ }
3062
+ await sleep3(Math.min(intervalMs, Math.max(0, deadline - Date.now())));
3063
+ }
3064
+ }
3065
+ async function skill(args, flags) {
3066
+ if (flags.help || args.length === 0) {
3067
+ console.log(SKILL_HELP);
3068
+ return;
3069
+ }
3070
+ const cfg = getConfig();
3071
+ requireApiKey(cfg);
3072
+ const apiKey = cfg.apiKey;
3073
+ const [command, ...rest] = args;
3074
+ try {
3075
+ let result;
3076
+ if (command === "spec") {
3077
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, `${BASE2}/spec`, { retries: READ_RETRIES4 });
3078
+ } else if (command === "submit") {
3079
+ const directory = flags.dir;
3080
+ const github = flags.github;
3081
+ if (directory && github || !directory && !github) {
3082
+ err("choose exactly one skill source", "Pass either --dir <path> or --github <public-url>.");
3083
+ }
3084
+ if (directory) {
3085
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, `${BASE2}/submissions`, {
3086
+ method: "POST",
3087
+ body: { files: await collectInlineFiles(required2(directory, "xapi-to skill submit --dir <skill-directory>")) }
3088
+ });
3089
+ } else {
3090
+ const body = {
3091
+ url: required2(github, "xapi-to skill submit --github <public-github-url>")
3092
+ };
3093
+ for (const name of ["version", "name", "description"]) {
3094
+ if (flags[name] && flags[name] !== "true") body[name] = flags[name];
3095
+ }
3096
+ const categories = listFlag(flags.category);
3097
+ const tags = listFlag(flags.tag);
3098
+ if (categories) body.categories = categories;
3099
+ if (tags) body.tags = tags;
3100
+ result = await apiKeyApiRequest(XAPI_API_HOST, apiKey, `${BASE2}/submissions/github`, { method: "POST", body });
3101
+ }
3102
+ } else if (command === "status") {
3103
+ result = await getSubmission(apiKey, required2(rest[0], "xapi-to skill status <submission-id>"));
3104
+ } else if (command === "wait") {
3105
+ result = await waitForSubmission(apiKey, required2(rest[0], "xapi-to skill wait <submission-id>"), flags);
3106
+ } else {
3107
+ err(`unknown skill command: ${command}`, "Valid commands: spec, submit, status, wait.");
3108
+ }
3109
+ output(result, flags.format);
3110
+ } catch (error) {
3111
+ if (error instanceof Error && error.message === "process.exit") throw error;
3112
+ err("skill request failed", error.message);
3113
+ }
3114
+ }
3115
+
2402
3116
  // src/args.ts
2403
3117
  function parseArgs(argv) {
2404
3118
  const positional = [];
@@ -2483,6 +3197,16 @@ COMMANDS
2483
3197
  file|port|extension|audit|suspend|resume|terminate
2484
3198
  Run "xapi-to sandbox --help" for selection and safety flags
2485
3199
 
3200
+ provider <command> Manage provider services, releases, metrics, events, and linked skills
3201
+ list|get|create|update|versions|version|major|revision
3202
+ publish|rollback|default-major|deprecate|restore|review|diff
3203
+ metrics|events|skill|delete
3204
+ Run "xapi-to provider --help" for metadata and lifecycle flags
3205
+
3206
+ skill <command> Upload and submit service usage skills
3207
+ spec|submit|status|wait
3208
+ Run "xapi-to skill --help" for local directory and GitHub workflows
3209
+
2486
3210
  oauth bind [--provider twitter] Bind Twitter OAuth to your API key
2487
3211
  oauth status List current OAuth bindings
2488
3212
  oauth unbind <binding-id> Remove an OAuth binding
@@ -2492,6 +3216,14 @@ COMMANDS
2492
3216
  --referral-code <code> Register with an inviter's referral code (also: --referralCode, or as positional arg)
2493
3217
  --force Replace an existing file-based apiKey
2494
3218
  balance Show current account balance
3219
+ usage <request-id> Show a finalized per-request cost receipt
3220
+ usage wait <request-id> Wait until a request receipt is finalized
3221
+ --interval <duration> Poll interval (default: 1s)
3222
+ --timeout <duration> Max wait duration (default: 30s)
3223
+ earnings [summary] Show spendable balance and provider earnings
3224
+ earnings list List provider earning records
3225
+ earnings transfer <usd> --idempotency-key <key>
3226
+ Reinvest settled earnings into xapi balance
2495
3227
  topup [--amount <usd>] [--method stripe|x402] Generate payment URL
2496
3228
 
2497
3229
  health Check backend connectivity
@@ -2533,6 +3265,14 @@ EXAMPLES
2533
3265
  xapi-to categories
2534
3266
  xapi-to services --format table
2535
3267
  xapi-to config set apiKey=xapi_abc123
3268
+ xapi-to earnings
3269
+ xapi-to usage c7fe24d5-e1d4-4bc1-a9bb-e16df8ab93b0
3270
+ xapi-to usage wait c7fe24d5-e1d4-4bc1-a9bb-e16df8ab93b0 --timeout 1m
3271
+ xapi-to earnings transfer 1 --idempotency-key reinvest-001
3272
+ xapi-to provider update svc_123 --about-file ./ABOUT.md --website https://example.com
3273
+ xapi-to provider publish svc_123 rev_456 --changelog-file ./CHANGELOG.md
3274
+ xapi-to skill submit --dir ./skills/my-service
3275
+ xapi-to provider skill link svc_123 11111111-1111-4111-8111-111111111111
2536
3276
  xapi-to health
2537
3277
  `;
2538
3278
  async function main() {
@@ -2568,6 +3308,10 @@ async function main() {
2568
3308
  return actionBatchGet(rest, flags);
2569
3309
  case "call":
2570
3310
  return actionCall2(rest, flags);
3311
+ case "provider":
3312
+ return provider(rest, flags);
3313
+ case "skill":
3314
+ return skill(rest, flags);
2571
3315
  case "task": {
2572
3316
  if (rest.length === 0) {
2573
3317
  console.log(taskHelp());
@@ -2660,6 +3404,10 @@ async function main() {
2660
3404
  return register(rest, flags);
2661
3405
  case "balance":
2662
3406
  return balance(rest, flags);
3407
+ case "usage":
3408
+ return usage(rest, flags);
3409
+ case "earnings":
3410
+ return earnings(rest, flags);
2663
3411
  case "topup":
2664
3412
  return topup(rest, flags);
2665
3413
  case "health":