@kaelen-ai/cli 0.1.15 → 0.1.16

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
@@ -9,16 +9,16 @@ import { transform, build } from 'esbuild';
9
9
  import { spawn } from 'child_process';
10
10
  import { createRequire } from 'module';
11
11
  import { URL as URL$1, pathToFileURL } from 'url';
12
+ import { randomBytes, createHash } from 'crypto';
12
13
  import { tmpdir, homedir } from 'os';
13
14
  import { createWriteStream } from 'fs';
14
15
  import archiver from 'archiver';
15
16
  import { createInterface } from 'readline';
16
- import open from 'open';
17
- import { createServer } from 'http';
18
- import { randomBytes, createHash } from 'crypto';
19
17
  import WebSocket from 'ws';
20
18
  import { Socket } from 'phoenix';
21
19
  import * as AbsintheSocket from '@absinthe/socket';
20
+ import open from 'open';
21
+ import { createServer } from 'http';
22
22
 
23
23
  // src/errors.ts
24
24
  var CliError = class extends Error {
@@ -159,7 +159,14 @@ function validChannelPattern(value) {
159
159
 
160
160
  // src/build/parser.ts
161
161
  var SDK_PACKAGES = /* @__PURE__ */ new Set(["@io/sdk", "@kaelen-ai/sdk", "@eleven-am/sdk"]);
162
- var EXECUTION_KEYS = /* @__PURE__ */ new Set(["memory", "timeout", "retries", "concurrency"]);
162
+ var EXECUTION_KEYS = /* @__PURE__ */ new Set([
163
+ "memory",
164
+ "timeout",
165
+ "maxToolCalls",
166
+ "rateBudget",
167
+ "retries",
168
+ "concurrency"
169
+ ]);
163
170
  function loc(node, file) {
164
171
  const position = node.loc?.start;
165
172
  return {
@@ -311,7 +318,7 @@ function extractOnField(propValue, file, sdkImports) {
311
318
  loc(propValue, file)
312
319
  );
313
320
  }
314
- function extractExecutionConfig(properties) {
321
+ function extractExecutionConfig(properties, file) {
315
322
  const config = {};
316
323
  for (const prop of properties) {
317
324
  if (prop.type === "SpreadElement") {
@@ -321,10 +328,7 @@ function extractExecutionConfig(properties) {
321
328
  if (!keyName || !EXECUTION_KEYS.has(keyName)) {
322
329
  continue;
323
330
  }
324
- const value = prop.value;
325
- if (value.type === "Literal") {
326
- config[keyName] = value.value;
327
- }
331
+ config[keyName] = extractStaticValue(prop.value, file);
328
332
  }
329
333
  return Object.keys(config).length > 0 ? config : void 0;
330
334
  }
@@ -364,7 +368,6 @@ function parseBehaviorCall(node, args, ancestors, file, out, sdkImports) {
364
368
  const properties = spec.properties;
365
369
  let onRaw;
366
370
  let hasRun = false;
367
- let messageHistory;
368
371
  for (const prop of properties) {
369
372
  if (prop.type === "SpreadElement") {
370
373
  continue;
@@ -377,23 +380,6 @@ function parseBehaviorCall(node, args, ancestors, file, out, sdkImports) {
377
380
  onRaw = extractOnField(prop.value, file, sdkImports);
378
381
  } else if (keyName === "run") {
379
382
  hasRun = true;
380
- } else if (keyName === "trackingId") {
381
- const value = prop.value;
382
- if (value.type !== "ArrowFunctionExpression" && value.type !== "FunctionExpression") {
383
- throw new BuildError(
384
- `behavior '${name}' trackingId must be a function in ${file}:${location.line}`,
385
- location
386
- );
387
- }
388
- } else if (keyName === "messageHistory") {
389
- const value = prop.value;
390
- if (value.type !== "Literal" || typeof value.value !== "number" || !Number.isInteger(value.value) || value.value <= 0) {
391
- throw new BuildError(
392
- `behavior '${name}' messageHistory must be a positive integer literal in ${file}:${location.line}`,
393
- location
394
- );
395
- }
396
- messageHistory = value.value;
397
383
  }
398
384
  }
399
385
  if (!onRaw) {
@@ -402,14 +388,13 @@ function parseBehaviorCall(node, args, ancestors, file, out, sdkImports) {
402
388
  if (!hasRun) {
403
389
  throw new BuildError(`behavior '${name}' is missing a run function`, location);
404
390
  }
405
- const execution = extractExecutionConfig(properties);
391
+ const execution = extractExecutionConfig(properties, file);
406
392
  const bounds = findStatementBounds(ancestors);
407
393
  out.push({
408
394
  name,
409
395
  onRaw,
410
396
  on: "",
411
397
  execution,
412
- messageHistory,
413
398
  location,
414
399
  statementStart: bounds.start,
415
400
  statementEnd: bounds.end
@@ -490,7 +475,7 @@ function parseConversationCall(node, args, ancestors, file, out) {
490
475
  location
491
476
  );
492
477
  }
493
- const execution = extractExecutionConfig(properties);
478
+ const execution = extractExecutionConfig(properties, file);
494
479
  const bounds = findStatementBounds(ancestors);
495
480
  out.push({
496
481
  name,
@@ -565,11 +550,7 @@ var MEMORY_EVENT_MAP = {
565
550
  onTensionEmerged: "knowledge:tension.emerged",
566
551
  onTensionEscalated: "knowledge:tension.escalated",
567
552
  onTrajectoryDeteriorated: "knowledge:trajectory.deteriorated",
568
- onRoutineBroken: "knowledge:routine.broken",
569
- onContradiction: "knowledge:contradiction",
570
- onDeadlinePassed: "knowledge:deadline_passed",
571
- onIntentionExpired: "knowledge:intention_expired",
572
- onTemporalDueSoon: "knowledge:temporal_due_soon"
553
+ onRoutineBroken: "knowledge:routine.broken"
573
554
  };
574
555
  function resolveAll(files) {
575
556
  const signalVarMap = /* @__PURE__ */ new Map();
@@ -854,6 +835,228 @@ function validateEndpointsSection(endpoints, errors) {
854
835
  var RESERVED_BUNDLE_PREFIX = "__kaelen.";
855
836
  var BINDING_SCOPES = /* @__PURE__ */ new Set(["project", "principal"]);
856
837
  var CREDENTIAL_SOURCES = /* @__PURE__ */ new Set(["none", "platform", "secret_bundle"]);
838
+ var TARGET_EXECUTION_KEYS = /* @__PURE__ */ new Set([
839
+ "applications",
840
+ "endpoints",
841
+ "secret_names",
842
+ "timeout_ms",
843
+ "memory_mb",
844
+ "max_tool_calls",
845
+ "rate_budget"
846
+ ]);
847
+ var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
848
+ "authorization",
849
+ "proxy-authorization",
850
+ "proxy-authenticate",
851
+ "www-authenticate",
852
+ "cookie",
853
+ "set-cookie",
854
+ "x-api-key",
855
+ "x-auth-token",
856
+ "x-access-token",
857
+ "x-secret",
858
+ "x-secret-key",
859
+ "x-amz-security-token"
860
+ ]);
861
+ var HTTP_LIKE_KINDS = /* @__PURE__ */ new Set(["http", "graphql", "httpApi", "graphqlApi"]);
862
+ var OP_RESOURCE_KINDS = /* @__PURE__ */ new Set(["httpApi", "graphqlApi"]);
863
+ function countPlaceholders(format) {
864
+ let count = 0;
865
+ let idx = 0;
866
+ while ((idx = format.indexOf("{}", idx)) !== -1) {
867
+ count++;
868
+ idx += 2;
869
+ }
870
+ return count;
871
+ }
872
+ function validateStaticHeaders(owner, headers, errors) {
873
+ if (headers === void 0) return;
874
+ if (!isRecord2(headers)) {
875
+ errors.push(`${owner} headers must be an object`);
876
+ return;
877
+ }
878
+ for (const name of Object.keys(headers)) {
879
+ if (SECRET_HEADER_NAMES.has(name.toLowerCase())) {
880
+ errors.push(
881
+ `${owner} headers entry "${name}" is secret-bearing; declare it via credentialHeaders instead`
882
+ );
883
+ }
884
+ }
885
+ }
886
+ function validateCredentialHeadersBlock(owner, declared, errors, secretBundleNames, resourceSecretBundle, secretBundleSlots) {
887
+ if (declared === void 0) return;
888
+ if (!Array.isArray(declared)) {
889
+ errors.push(`${owner} credentialHeaders must be an array`);
890
+ return;
891
+ }
892
+ for (const entry of declared) {
893
+ if (!isRecord2(entry)) {
894
+ errors.push(`${owner} credentialHeaders entry must be an object`);
895
+ continue;
896
+ }
897
+ const name = stringField2(entry, "name");
898
+ if (!name) {
899
+ errors.push(`${owner} credentialHeaders entry is missing a string name`);
900
+ continue;
901
+ }
902
+ if (SECRET_HEADER_NAMES.has(name.toLowerCase())) ;
903
+ const source = entry.source;
904
+ if (!isRecord2(source)) {
905
+ errors.push(
906
+ `${owner} credentialHeaders "${name}" source must be an object`
907
+ );
908
+ continue;
909
+ }
910
+ const kind = stringField2(source, "kind");
911
+ if (kind === "credential") {
912
+ const field = stringField2(source, "field");
913
+ if (!field) {
914
+ errors.push(
915
+ `${owner} credentialHeaders "${name}" credential source missing field`
916
+ );
917
+ continue;
918
+ }
919
+ if (resourceSecretBundle === void 0) {
920
+ errors.push(
921
+ `${owner} credentialHeaders "${name}" uses credential source but resource declares no auth/secret_bundle`
922
+ );
923
+ } else if (!resourceSecretBundle.startsWith(RESERVED_BUNDLE_PREFIX)) {
924
+ const slots = secretBundleSlots.get(resourceSecretBundle);
925
+ if (slots && !slots.has(field)) {
926
+ errors.push(
927
+ `${owner} credentialHeaders "${name}" credential field "${field}" is not declared in secret bundle "${resourceSecretBundle}"`
928
+ );
929
+ }
930
+ }
931
+ } else if (kind === "rawSecret" || kind === "raw_secret") {
932
+ const secretName = stringField2(source, "name");
933
+ if (!secretName) {
934
+ errors.push(
935
+ `${owner} credentialHeaders "${name}" raw_secret source missing name`
936
+ );
937
+ continue;
938
+ }
939
+ if (!secretName.startsWith(RESERVED_BUNDLE_PREFIX) && !secretBundleNames.has(secretName)) {
940
+ errors.push(
941
+ `${owner} credentialHeaders "${name}" references unknown raw_secret "${secretName}"`
942
+ );
943
+ }
944
+ } else {
945
+ errors.push(
946
+ `${owner} credentialHeaders "${name}" source.kind must be credential or rawSecret`
947
+ );
948
+ continue;
949
+ }
950
+ const format = entry.format;
951
+ if (format !== void 0) {
952
+ if (typeof format !== "string" || countPlaceholders(format) !== 1) {
953
+ errors.push(
954
+ `${owner} credentialHeaders "${name}" format must contain exactly one {} placeholder`
955
+ );
956
+ }
957
+ }
958
+ }
959
+ }
960
+ function validateRequestMappingBlock(owner, declared, errors) {
961
+ if (declared === void 0) return;
962
+ if (!isRecord2(declared)) {
963
+ errors.push(`${owner} requestMapping must be an object`);
964
+ return;
965
+ }
966
+ for (const key of ["pathParams", "query"]) {
967
+ const value = declared[key];
968
+ if (value === void 0) continue;
969
+ if (!Array.isArray(value)) {
970
+ errors.push(`${owner} requestMapping.${key} must be an array`);
971
+ continue;
972
+ }
973
+ for (const entry of value) {
974
+ if (typeof entry === "string") {
975
+ if (!entry.trim()) {
976
+ errors.push(
977
+ `${owner} requestMapping.${key} entries must be non-empty strings or {from, as?} objects`
978
+ );
979
+ }
980
+ continue;
981
+ }
982
+ if (isRecord2(entry)) {
983
+ if (!stringField2(entry, "from")) {
984
+ errors.push(
985
+ `${owner} requestMapping.${key} entry.from must be a non-empty string`
986
+ );
987
+ continue;
988
+ }
989
+ if (entry.as !== void 0 && (typeof entry.as !== "string" || !entry.as.trim())) {
990
+ errors.push(
991
+ `${owner} requestMapping.${key} entry.as must be a non-empty string when provided`
992
+ );
993
+ }
994
+ continue;
995
+ }
996
+ errors.push(
997
+ `${owner} requestMapping.${key} entries must be strings or {from, as?} objects`
998
+ );
999
+ }
1000
+ }
1001
+ if (declared.body !== void 0 && typeof declared.body !== "string" && declared.body !== null) {
1002
+ errors.push(`${owner} requestMapping.body must be a string or null`);
1003
+ }
1004
+ }
1005
+ function validateHttpLikeConfig(appName, resourceName, resourceKind, config, secretBundleNames, resourceSecretBundle, secretBundleSlots, errors) {
1006
+ if (!HTTP_LIKE_KINDS.has(resourceKind)) return;
1007
+ if (!isRecord2(config)) return;
1008
+ const base = `resource "${appName}/${resourceName}"`;
1009
+ validateStaticHeaders(base, config.headers, errors);
1010
+ validateCredentialHeadersBlock(
1011
+ base,
1012
+ config.credentialHeaders,
1013
+ errors,
1014
+ secretBundleNames,
1015
+ resourceSecretBundle,
1016
+ secretBundleSlots
1017
+ );
1018
+ if (resourceKind === "http") {
1019
+ validateRequestMappingBlock(base, config.requestMapping, errors);
1020
+ }
1021
+ if (resourceKind === "graphql") {
1022
+ if (config.variablesMapping !== void 0 && typeof config.variablesMapping !== "string") {
1023
+ errors.push(`${base} variablesMapping must be a string`);
1024
+ }
1025
+ }
1026
+ if (OP_RESOURCE_KINDS.has(resourceKind)) {
1027
+ const ops = config.operations;
1028
+ if (ops !== void 0 && !isRecord2(ops)) {
1029
+ errors.push(`${base} operations must be an object`);
1030
+ return;
1031
+ }
1032
+ if (isRecord2(ops)) {
1033
+ for (const [opName, opSpec] of Object.entries(ops)) {
1034
+ const owner = `${base} operation "${opName}"`;
1035
+ if (!isRecord2(opSpec)) {
1036
+ errors.push(`${owner} must be an object`);
1037
+ continue;
1038
+ }
1039
+ validateStaticHeaders(owner, opSpec.headers, errors);
1040
+ validateCredentialHeadersBlock(
1041
+ owner,
1042
+ opSpec.credentialHeaders,
1043
+ errors,
1044
+ secretBundleNames,
1045
+ resourceSecretBundle,
1046
+ secretBundleSlots
1047
+ );
1048
+ if (resourceKind === "httpApi") {
1049
+ validateRequestMappingBlock(owner, opSpec.requestMapping, errors);
1050
+ }
1051
+ if (resourceKind === "graphqlApi") {
1052
+ if (opSpec.variablesMapping !== void 0 && typeof opSpec.variablesMapping !== "string") {
1053
+ errors.push(`${owner} variablesMapping must be a string`);
1054
+ }
1055
+ }
1056
+ }
1057
+ }
1058
+ }
1059
+ }
857
1060
  function isRecord2(value) {
858
1061
  return !!value && typeof value === "object" && !Array.isArray(value);
859
1062
  }
@@ -865,12 +1068,6 @@ function stringField2(record, key) {
865
1068
  const value = record[key];
866
1069
  return typeof value === "string" && value.trim() ? value : void 0;
867
1070
  }
868
- function scopeListField(record, key) {
869
- const value = record[key];
870
- if (value === void 0) return void 0;
871
- if (!Array.isArray(value)) return [];
872
- return value.flatMap((entry) => scopeEntryName(entry) ?? []);
873
- }
874
1071
  function scopeEntryName(entry) {
875
1072
  if (typeof entry === "string" && entry.trim()) return entry;
876
1073
  if (isRecord2(entry)) return stringField2(entry, "name");
@@ -916,6 +1113,11 @@ function validateTargetExecution(target, declaredApps, declaredEndpoints, errors
916
1113
  return;
917
1114
  }
918
1115
  const executionRecord = isRecord2(execution) ? execution : {};
1116
+ for (const key of Object.keys(executionRecord)) {
1117
+ if (!TARGET_EXECUTION_KEYS.has(key)) {
1118
+ errors.push(`${owner} execution contains unsupported key "${key}"`);
1119
+ }
1120
+ }
919
1121
  const applicationRefs = validateScopeList(
920
1122
  owner,
921
1123
  "execution.applications",
@@ -938,31 +1140,60 @@ function validateTargetExecution(target, declaredApps, declaredEndpoints, errors
938
1140
  errors.push(`${owner} references undeclared endpoint "${ref}"`);
939
1141
  }
940
1142
  }
941
- for (const field of ["secret_names", "raw_secrets"]) {
942
- const refs = validateScopeList(owner, `execution.${field}`, executionRecord[field], errors);
943
- for (const ref of refs) {
944
- if (ref.startsWith(RESERVED_BUNDLE_PREFIX)) {
945
- errors.push(
946
- `${owner} execution.${field} entry "${ref}" uses the reserved "${RESERVED_BUNDLE_PREFIX}" prefix`
947
- );
948
- }
1143
+ const secretRefs = validateScopeList(
1144
+ owner,
1145
+ "execution.secret_names",
1146
+ executionRecord.secret_names,
1147
+ errors
1148
+ );
1149
+ for (const ref of secretRefs) {
1150
+ if (ref.startsWith(RESERVED_BUNDLE_PREFIX)) {
1151
+ errors.push(
1152
+ `${owner} execution.secret_names entry "${ref}" uses the reserved "${RESERVED_BUNDLE_PREFIX}" prefix`
1153
+ );
949
1154
  }
950
1155
  }
951
- const legacyEndpointRefs = scopeListField(target, "endpoints");
952
- if (target.endpoints !== void 0 && legacyEndpointRefs === void 0) {
953
- errors.push(`${owner} endpoints must be an array of strings`);
954
- } else if (target.endpoints !== void 0) {
955
- if (endpointRefs.length === 0) {
956
- errors.push(`${owner} must declare endpoint scope under execution.endpoints`);
957
- }
958
- for (const ref of legacyEndpointRefs ?? []) {
959
- if (!declaredEndpoints.has(ref)) {
960
- errors.push(`${owner} references undeclared endpoint "${ref}"`);
961
- }
962
- if (endpointRefs.length > 0 && !endpointRefs.includes(ref)) {
963
- errors.push(`${owner} legacy endpoints entry "${ref}" is missing from execution.endpoints`);
964
- }
1156
+ validatePositiveInteger(owner, "execution.timeout_ms", executionRecord.timeout_ms, errors);
1157
+ validatePositiveInteger(owner, "execution.memory_mb", executionRecord.memory_mb, errors);
1158
+ validateNonNegativeInteger(owner, "execution.max_tool_calls", executionRecord.max_tool_calls, errors);
1159
+ validateRateBudget(owner, executionRecord.rate_budget, errors);
1160
+ if (target.endpoints !== void 0) {
1161
+ errors.push(`${owner} endpoints is no longer supported; use execution.endpoints`);
1162
+ }
1163
+ }
1164
+ function validateRateBudget(owner, value, errors) {
1165
+ if (value === void 0) return;
1166
+ if (!isRecord2(value)) {
1167
+ errors.push(`${owner} execution.rate_budget must be an object`);
1168
+ return;
1169
+ }
1170
+ validateBudgetMap(owner, "execution.rate_budget.per_application", value.per_application, errors);
1171
+ validateBudgetMap(owner, "execution.rate_budget.per_capability", value.per_capability, errors);
1172
+ }
1173
+ function validateBudgetMap(owner, field, value, errors) {
1174
+ if (value === void 0) return;
1175
+ if (!isRecord2(value)) {
1176
+ errors.push(`${owner} ${field} must be an object`);
1177
+ return;
1178
+ }
1179
+ for (const [key, budget] of Object.entries(value)) {
1180
+ if (!key.trim()) {
1181
+ errors.push(`${owner} ${field} keys must be non-empty strings`);
1182
+ continue;
965
1183
  }
1184
+ validateNonNegativeInteger(owner, `${field}.${key}`, budget, errors);
1185
+ }
1186
+ }
1187
+ function validatePositiveInteger(owner, field, value, errors) {
1188
+ if (value === void 0) return;
1189
+ if (!Number.isInteger(value) || value <= 0) {
1190
+ errors.push(`${owner} ${field} must be a positive integer`);
1191
+ }
1192
+ }
1193
+ function validateNonNegativeInteger(owner, field, value, errors) {
1194
+ if (value === void 0) return;
1195
+ if (!Number.isInteger(value) || value < 0) {
1196
+ errors.push(`${owner} ${field} must be a non-negative integer`);
966
1197
  }
967
1198
  }
968
1199
  function capabilityId(applicationName, resourceName, toolName) {
@@ -1056,11 +1287,11 @@ function validateManifest(manifest) {
1056
1287
  const root = isRecord2(manifest) ? manifest : {};
1057
1288
  const errors = [];
1058
1289
  const seenCapabilities = /* @__PURE__ */ new Set();
1059
- if (root.contract_version !== "io.invocation.v1") {
1060
- errors.push("contract_version must be io.invocation.v1");
1290
+ if (root.contract_version !== "io.invocation.v2") {
1291
+ errors.push("contract_version must be io.invocation.v2");
1061
1292
  }
1062
- if (root.snapshot_version !== "io_runtime.snapshot.v1") {
1063
- errors.push("snapshot_version must be io_runtime.snapshot.v1");
1293
+ if (root.snapshot_version !== "io_runtime.snapshot.v2") {
1294
+ errors.push("snapshot_version must be io_runtime.snapshot.v2");
1064
1295
  }
1065
1296
  const secretBundles = arrayField(root, "secret_bundles").filter(isRecord2);
1066
1297
  duplicateNames(secretBundles, "secret bundle", errors);
@@ -1075,6 +1306,14 @@ function validateManifest(manifest) {
1075
1306
  const secretNames = new Set(
1076
1307
  secretBundles.map((entry) => stringField2(entry, "name")).filter((name) => !!name)
1077
1308
  );
1309
+ const secretBundleSlots = /* @__PURE__ */ new Map();
1310
+ for (const bundle of secretBundles) {
1311
+ const bundleName = stringField2(bundle, "name");
1312
+ if (!bundleName) continue;
1313
+ const slots = bundle.slots;
1314
+ if (!isRecord2(slots)) continue;
1315
+ secretBundleSlots.set(bundleName, new Set(Object.keys(slots)));
1316
+ }
1078
1317
  const declaredEndpoints = validateEndpointsSection(
1079
1318
  arrayField(root, "endpoints"),
1080
1319
  errors
@@ -1121,6 +1360,17 @@ function validateManifest(manifest) {
1121
1360
  } else {
1122
1361
  validateResourceCapability(appName, resourceName, capability, errors);
1123
1362
  }
1363
+ const resourceKindForConfig = stringField2(resource, "kind") ?? "";
1364
+ validateHttpLikeConfig(
1365
+ appName,
1366
+ resourceName,
1367
+ resourceKindForConfig,
1368
+ resource.config,
1369
+ secretNames,
1370
+ secretBundle,
1371
+ secretBundleSlots,
1372
+ errors
1373
+ );
1124
1374
  const tools = toolEntries.filter(isRecord2);
1125
1375
  duplicateNames(tools, `resource "${appName}/${resourceName}" tool`, errors);
1126
1376
  for (const tool of tools) {
@@ -1588,6 +1838,12 @@ var RUNTIME_MEMORY_SIGNALS = {
1588
1838
  onTrajectoryDeteriorated: "knowledge:trajectory.deteriorated",
1589
1839
  onRoutineBroken: "knowledge:routine.broken"
1590
1840
  };
1841
+ var RUNTIME_SDK_PASSTHROUGH_EXPORTS = /* @__PURE__ */ new Set([
1842
+ "google",
1843
+ "microsoft",
1844
+ "httpApi",
1845
+ "graphqlApi"
1846
+ ]);
1591
1847
  var RUNTIME_SDK_EXPORTS = /* @__PURE__ */ new Set([
1592
1848
  "ioSchema",
1593
1849
  "zodSchema",
@@ -1608,7 +1864,8 @@ var RUNTIME_SDK_EXPORTS = /* @__PURE__ */ new Set([
1608
1864
  "graphql",
1609
1865
  "http",
1610
1866
  "calendar",
1611
- "contacts"
1867
+ "contacts",
1868
+ ...RUNTIME_SDK_PASSTHROUGH_EXPORTS
1612
1869
  ]);
1613
1870
  var COMPILED_AWAY_EXPORTS = /* @__PURE__ */ new Set(["behavior", "conversation"]);
1614
1871
  function normalizeSdkSpecifier(specifier) {
@@ -1870,6 +2127,29 @@ ${entries.map(([key, value]) => ` ${key}: Object.freeze({ name: ${JSON.stringif
1870
2127
  }`
1871
2128
  );
1872
2129
  }
2130
+ const needsPassthroughHelper = [...RUNTIME_SDK_PASSTHROUGH_EXPORTS].some(
2131
+ (name) => usage.exports.has(name)
2132
+ );
2133
+ if (needsPassthroughHelper) {
2134
+ lines.push(
2135
+ `function passthroughFrozen(options) {
2136
+ if (options && typeof options === "object" && !Array.isArray(options)) {
2137
+ return Object.freeze({ ...options });
2138
+ }
2139
+ return Object.freeze({});
2140
+ }`
2141
+ );
2142
+ for (const passthrough of RUNTIME_SDK_PASSTHROUGH_EXPORTS) {
2143
+ if (!usage.exports.has(passthrough)) {
2144
+ continue;
2145
+ }
2146
+ lines.push(
2147
+ `export function ${passthrough}(options) {
2148
+ return passthroughFrozen(options);
2149
+ }`
2150
+ );
2151
+ }
2152
+ }
1873
2153
  return lines.join("\n\n");
1874
2154
  }
1875
2155
  function sdkAliasPlugin() {
@@ -2208,65 +2488,514 @@ async function generateManifest(input) {
2208
2488
  );
2209
2489
  return m;
2210
2490
  }
2211
- var INTERNAL_CONFIG = "__io_cli_compiled_config";
2212
- var INTERNAL_RUN = "__io_cli_compiled_run";
2213
- var INTERNAL_TRACKING_ID = "__io_cli_compiled_tracking_id";
2214
- var INTERNAL_ON_START = "__io_cli_compiled_on_start";
2215
- var INTERNAL_ON_MESSAGE = "__io_cli_compiled_on_message";
2216
- var INTERNAL_ON_END = "__io_cli_compiled_on_end";
2217
- var INTERNAL_APPLICATIONS = "__io_cli_compiled_applications";
2218
- function conversationChannel(name) {
2219
- return `session:${name}`;
2220
- }
2221
- function parseModule2(source) {
2222
- return acorn.parse(source, {
2223
- ecmaVersion: "latest",
2224
- sourceType: "module"
2225
- });
2491
+
2492
+ // src/build/stable-json.ts
2493
+ function stableStringify(value, indent) {
2494
+ return JSON.stringify(canonicalize(value), null, indent);
2226
2495
  }
2227
- function rootStatementFor(source, statementStart, kind, name) {
2228
- let ast;
2229
- try {
2230
- ast = parseModule2(source);
2231
- } catch (error) {
2232
- throw new BuildError(
2233
- `failed to parse compiled target source: ${error instanceof Error ? error.message : String(error)}`
2234
- );
2496
+ function canonicalize(value) {
2497
+ if (value === null) return null;
2498
+ if (Array.isArray(value)) {
2499
+ return value.map(canonicalize);
2235
2500
  }
2236
- const body = ast.body;
2237
- const stmt = body.find(
2238
- (candidate) => candidate.start === statementStart
2239
- );
2240
- if (stmt) {
2241
- return stmt;
2501
+ if (typeof value === "object") {
2502
+ const obj = value;
2503
+ const sorted = {};
2504
+ for (const key of Object.keys(obj).sort()) {
2505
+ sorted[key] = canonicalize(obj[key]);
2506
+ }
2507
+ return sorted;
2242
2508
  }
2243
- const fallback = body.find((candidate) => {
2244
- if (candidate.type !== "ExpressionStatement") {
2245
- return false;
2509
+ return value;
2510
+ }
2511
+
2512
+ // src/build/catalog.ts
2513
+ var CATALOG_CONTRACT_VERSION = "io.invocation.v2";
2514
+ var CATALOG_SNAPSHOT_VERSION = "io_runtime.snapshot.v2";
2515
+ var WEBHOOK_RESOURCE_KIND = "webhook";
2516
+ var PLATFORM_TOOL_KINDS = /* @__PURE__ */ new Set(["email", "calendar", "contacts"]);
2517
+ var STREAM_PUBLISH_KINDS = /* @__PURE__ */ new Set([
2518
+ "kafka",
2519
+ "rabbitmq",
2520
+ "redis",
2521
+ "pubsub",
2522
+ "sqs",
2523
+ "websocket"
2524
+ ]);
2525
+ function sha256Hex(value) {
2526
+ return `sha256:${createHash("sha256").update(stableStringify(value)).digest("hex")}`;
2527
+ }
2528
+ function isRecord3(value) {
2529
+ return !!value && typeof value === "object" && !Array.isArray(value);
2530
+ }
2531
+ function getString(record, key) {
2532
+ const value = record[key];
2533
+ return typeof value === "string" ? value : void 0;
2534
+ }
2535
+ function getRecord(record, key) {
2536
+ const value = record[key];
2537
+ return isRecord3(value) ? value : void 0;
2538
+ }
2539
+ function getArray(record, key) {
2540
+ const value = record[key];
2541
+ return Array.isArray(value) ? value : [];
2542
+ }
2543
+ function getNumber(record, key) {
2544
+ const value = record[key];
2545
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2546
+ }
2547
+ function countPlaceholders2(format) {
2548
+ let count = 0;
2549
+ let idx = 0;
2550
+ while ((idx = format.indexOf("{}", idx)) !== -1) {
2551
+ count++;
2552
+ idx += 2;
2553
+ }
2554
+ return count;
2555
+ }
2556
+ function deriveAuditRequirement(effect) {
2557
+ return effect === "write" ? "pre_call" : "finish_only";
2558
+ }
2559
+ function deriveEffect(toolMode) {
2560
+ return toolMode === "read" ? "read" : "write";
2561
+ }
2562
+ function normalizeCredentialHeaders(declared, resourceId, context, warnings) {
2563
+ if (!Array.isArray(declared)) return [];
2564
+ const out = [];
2565
+ for (const entry of declared) {
2566
+ if (!isRecord3(entry)) {
2567
+ warnings.push(`${context}: skipped non-object credential header entry`);
2568
+ continue;
2246
2569
  }
2247
- const expression = candidate.expression;
2248
- if (expression.type !== "CallExpression") {
2249
- return false;
2570
+ const name = getString(entry, "name");
2571
+ if (!name) {
2572
+ warnings.push(`${context}: credential header missing name`);
2573
+ continue;
2250
2574
  }
2251
- const args = expression.arguments;
2252
- if (args.length < 2 || args[0]?.type !== "Literal" || args[1]?.type !== "ObjectExpression") {
2253
- return false;
2575
+ const source = getRecord(entry, "source");
2576
+ if (!source) {
2577
+ warnings.push(`${context}: credential header "${name}" missing source`);
2578
+ continue;
2254
2579
  }
2255
- const callee = expression.callee;
2256
- if (callee.type !== "Identifier") {
2257
- return false;
2580
+ const sourceKind = getString(source, "kind");
2581
+ let normalizedSource;
2582
+ if (sourceKind === "credential") {
2583
+ const field = getString(source, "field");
2584
+ if (!field) {
2585
+ warnings.push(
2586
+ `${context}: credential header "${name}" credential source missing field`
2587
+ );
2588
+ continue;
2589
+ }
2590
+ normalizedSource = { kind: "credential", resource_id: resourceId, field };
2591
+ } else if (sourceKind === "rawSecret" || sourceKind === "raw_secret") {
2592
+ const secretName = getString(source, "name");
2593
+ if (!secretName) {
2594
+ warnings.push(
2595
+ `${context}: credential header "${name}" raw_secret source missing name`
2596
+ );
2597
+ continue;
2598
+ }
2599
+ normalizedSource = { kind: "raw_secret", name: secretName };
2600
+ } else {
2601
+ warnings.push(
2602
+ `${context}: credential header "${name}" has unsupported source kind "${sourceKind}"`
2603
+ );
2604
+ continue;
2258
2605
  }
2259
- return callee.name === kind && args[0].value === name;
2260
- });
2261
- if (!fallback) {
2262
- throw new BuildError(
2263
- `failed to locate compiled ${kind} root '${name}' at byte offset ${statementStart}`
2264
- );
2606
+ const format = getString(entry, "format") ?? "{}";
2607
+ if (countPlaceholders2(format) !== 1) {
2608
+ warnings.push(
2609
+ `${context}: credential header "${name}" format must contain exactly one {} placeholder`
2610
+ );
2611
+ continue;
2612
+ }
2613
+ out.push({ name, source: normalizedSource, format });
2265
2614
  }
2266
- return fallback;
2615
+ return out;
2267
2616
  }
2268
- function rootSpecNode(stmt) {
2269
- if (stmt.type !== "ExpressionStatement") {
2617
+ function normalizeRequestMapping(declared) {
2618
+ if (!isRecord3(declared)) {
2619
+ return { path_params: [], query: [] };
2620
+ }
2621
+ const pathParams = collectMappingValues(declared["pathParams"]);
2622
+ const query = collectMappingValues(declared["query"]);
2623
+ const body = declared["body"];
2624
+ const bodyValue = typeof body === "string" ? body : body === null ? null : void 0;
2625
+ return {
2626
+ path_params: pathParams,
2627
+ query,
2628
+ ...bodyValue !== void 0 ? { body: bodyValue } : {}
2629
+ };
2630
+ }
2631
+ function collectMappingValues(value) {
2632
+ if (!Array.isArray(value)) return [];
2633
+ const out = [];
2634
+ for (const entry of value) {
2635
+ if (typeof entry === "string" && entry.trim()) {
2636
+ out.push(entry);
2637
+ } else if (isRecord3(entry)) {
2638
+ const from = getString(entry, "from");
2639
+ if (!from?.trim()) continue;
2640
+ const alias = getString(entry, "as");
2641
+ out.push(alias?.trim() ? { from, as: alias } : from);
2642
+ }
2643
+ }
2644
+ return out;
2645
+ }
2646
+ function buildExecutor(ctx) {
2647
+ if (PLATFORM_TOOL_KINDS.has(ctx.resourceKind)) {
2648
+ return {
2649
+ kind: "platform_tool",
2650
+ provider_kind: ctx.providerKind,
2651
+ backend_kind: ctx.backendKind,
2652
+ operation: ctx.toolName
2653
+ };
2654
+ }
2655
+ if (ctx.resourceKind === WEBHOOK_RESOURCE_KIND && ctx.toolName === "respond") {
2656
+ return { kind: "webhook_response" };
2657
+ }
2658
+ if (STREAM_PUBLISH_KINDS.has(ctx.resourceKind)) {
2659
+ return {
2660
+ kind: "stream_publish",
2661
+ provider_kind: ctx.providerKind,
2662
+ backend_kind: ctx.backendKind,
2663
+ destination_ref: ctx.configHash
2664
+ };
2665
+ }
2666
+ if (ctx.resourceKind === "http") {
2667
+ return buildHttpRequestExecutor(ctx);
2668
+ }
2669
+ if (ctx.resourceKind === "graphql") {
2670
+ return buildGraphQlRequestExecutor(ctx);
2671
+ }
2672
+ if (ctx.resourceKind === "httpApi") {
2673
+ return buildHttpOperationExecutor(ctx);
2674
+ }
2675
+ if (ctx.resourceKind === "graphqlApi") {
2676
+ return buildGraphQlOperationExecutor(ctx);
2677
+ }
2678
+ return {
2679
+ kind: "unsupported",
2680
+ reason: `unknown_resource_kind:${ctx.resourceKind}`
2681
+ };
2682
+ }
2683
+ function buildHttpRequestExecutor(ctx) {
2684
+ const config = ctx.resourceConfig;
2685
+ const url = getString(config, "url") ?? "";
2686
+ const method = (getString(config, "method") ?? "GET").toUpperCase();
2687
+ const headers = getRecord(config, "headers") ?? {};
2688
+ const queryParams = getRecord(config, "queryParams") ?? {};
2689
+ const body = config["body"];
2690
+ const credentialHeaders = normalizeCredentialHeaders(
2691
+ config["credentialHeaders"],
2692
+ ctx.resourceId,
2693
+ `tool "${ctx.resourceId}.${ctx.toolName}"`,
2694
+ ctx.warnings
2695
+ );
2696
+ return {
2697
+ kind: "http_request",
2698
+ url,
2699
+ method,
2700
+ headers_ref: ctx.registerBlob(headers),
2701
+ credential_headers: credentialHeaders,
2702
+ query_params_ref: ctx.registerBlob(queryParams),
2703
+ body_ref: ctx.registerBlob(body ?? null),
2704
+ timeout_seconds: getNumber(config, "timeoutSeconds") ?? null
2705
+ };
2706
+ }
2707
+ function buildGraphQlRequestExecutor(ctx) {
2708
+ const config = ctx.resourceConfig;
2709
+ const endpoint = getString(config, "endpoint") ?? "";
2710
+ const query = getString(config, "query") ?? "";
2711
+ const headers = getRecord(config, "headers") ?? {};
2712
+ const variables = getRecord(config, "variables") ?? {};
2713
+ const operationName = getString(config, "operationName") ?? null;
2714
+ const credentialHeaders = normalizeCredentialHeaders(
2715
+ config["credentialHeaders"],
2716
+ ctx.resourceId,
2717
+ `tool "${ctx.resourceId}.${ctx.toolName}"`,
2718
+ ctx.warnings
2719
+ );
2720
+ return {
2721
+ kind: "graphql_request",
2722
+ endpoint,
2723
+ query_ref: ctx.registerBlob(query),
2724
+ operation_name: operationName,
2725
+ variables_ref: ctx.registerBlob(variables),
2726
+ headers_ref: ctx.registerBlob(headers),
2727
+ credential_headers: credentialHeaders,
2728
+ timeout_seconds: getNumber(config, "timeoutSeconds") ?? null
2729
+ };
2730
+ }
2731
+ function buildHttpOperationExecutor(ctx) {
2732
+ const config = ctx.resourceConfig;
2733
+ const ops = getRecord(config, "operations") ?? {};
2734
+ const opSpec = getRecord(ops, ctx.toolName);
2735
+ if (!opSpec) {
2736
+ ctx.warnings.push(
2737
+ `tool "${ctx.resourceId}.${ctx.toolName}" has no matching operation in resource config`
2738
+ );
2739
+ return {
2740
+ kind: "unsupported",
2741
+ reason: `httpApi_missing_op:${ctx.toolName}`
2742
+ };
2743
+ }
2744
+ const baseUrl = getString(config, "baseUrl") ?? "";
2745
+ const resourceHeaders = getRecord(config, "headers") ?? {};
2746
+ const opHeaders = getRecord(opSpec, "headers") ?? {};
2747
+ const mergedHeaders = { ...resourceHeaders, ...opHeaders };
2748
+ const opCredentialHeaders = opSpec["credentialHeaders"];
2749
+ const resourceCredentialHeaders = config["credentialHeaders"];
2750
+ const credentialHeaders = normalizeCredentialHeaders(
2751
+ opCredentialHeaders ?? resourceCredentialHeaders,
2752
+ ctx.resourceId,
2753
+ `tool "${ctx.resourceId}.${ctx.toolName}"`,
2754
+ ctx.warnings
2755
+ );
2756
+ const method = (getString(opSpec, "method") ?? "GET").toUpperCase();
2757
+ const path = getString(opSpec, "path") ?? "";
2758
+ const timeoutSeconds = getNumber(opSpec, "timeoutSeconds") ?? getNumber(config, "timeoutSeconds") ?? null;
2759
+ return {
2760
+ kind: "http_operation",
2761
+ base_url: baseUrl,
2762
+ method,
2763
+ path,
2764
+ headers_ref: ctx.registerBlob(mergedHeaders),
2765
+ credential_headers: credentialHeaders,
2766
+ timeout_seconds: timeoutSeconds,
2767
+ request_mapping: normalizeRequestMapping(opSpec["requestMapping"])
2768
+ };
2769
+ }
2770
+ function buildGraphQlOperationExecutor(ctx) {
2771
+ const config = ctx.resourceConfig;
2772
+ const ops = getRecord(config, "operations") ?? {};
2773
+ const opSpec = getRecord(ops, ctx.toolName);
2774
+ if (!opSpec) {
2775
+ ctx.warnings.push(
2776
+ `tool "${ctx.resourceId}.${ctx.toolName}" has no matching operation in resource config`
2777
+ );
2778
+ return {
2779
+ kind: "unsupported",
2780
+ reason: `graphqlApi_missing_op:${ctx.toolName}`
2781
+ };
2782
+ }
2783
+ const endpoint = getString(config, "endpoint") ?? "";
2784
+ const resourceHeaders = getRecord(config, "headers") ?? {};
2785
+ const opHeaders = getRecord(opSpec, "headers") ?? {};
2786
+ const mergedHeaders = { ...resourceHeaders, ...opHeaders };
2787
+ const opCredentialHeaders = opSpec["credentialHeaders"];
2788
+ const resourceCredentialHeaders = config["credentialHeaders"];
2789
+ const credentialHeaders = normalizeCredentialHeaders(
2790
+ opCredentialHeaders ?? resourceCredentialHeaders,
2791
+ ctx.resourceId,
2792
+ `tool "${ctx.resourceId}.${ctx.toolName}"`,
2793
+ ctx.warnings
2794
+ );
2795
+ const query = getString(opSpec, "query") ?? "";
2796
+ const operationName = getString(opSpec, "operationName") ?? null;
2797
+ const variablesMapping = getString(opSpec, "variablesMapping") ?? "input";
2798
+ const timeoutSeconds = getNumber(opSpec, "timeoutSeconds") ?? getNumber(config, "timeoutSeconds") ?? null;
2799
+ return {
2800
+ kind: "graphql_operation",
2801
+ endpoint,
2802
+ query_ref: ctx.registerBlob(query),
2803
+ operation_name: operationName,
2804
+ headers_ref: ctx.registerBlob(mergedHeaders),
2805
+ credential_headers: credentialHeaders,
2806
+ timeout_seconds: timeoutSeconds,
2807
+ variables_mapping: variablesMapping
2808
+ };
2809
+ }
2810
+ function generateCatalog(manifest) {
2811
+ const warnings = [];
2812
+ const resources = {};
2813
+ const signals2 = {};
2814
+ const tools = {};
2815
+ const schemas = {};
2816
+ const registerBlob = (value) => {
2817
+ const hash = sha256Hex(value ?? {});
2818
+ if (!(hash in schemas)) {
2819
+ schemas[hash] = value ?? {};
2820
+ }
2821
+ return hash;
2822
+ };
2823
+ const apps = getArray(manifest, "apps").filter(isRecord3);
2824
+ for (const app of apps) {
2825
+ const appName = getString(app, "name");
2826
+ if (!appName) continue;
2827
+ const appResources2 = getArray(app, "resources").filter(isRecord3);
2828
+ for (const resource of appResources2) {
2829
+ const resourceName = getString(resource, "name");
2830
+ const resourceKind = getString(resource, "kind") ?? "";
2831
+ const backend = getString(resource, "backend") ?? "";
2832
+ const providerKind = getString(resource, "provider_kind") ?? resourceKind;
2833
+ if (!resourceName) continue;
2834
+ const resourceId = `${appName}.${resourceName}`;
2835
+ const config = getRecord(resource, "config") ?? {};
2836
+ const configHash = registerBlob(config);
2837
+ const capability = getRecord(resource, "capability") ?? {};
2838
+ const bindingScope = getString(capability, "binding_scope") ?? "project";
2839
+ const credentialSource = getString(capability, "credential_source") ?? "none";
2840
+ const requiresBindingValue = capability.requires_binding;
2841
+ const credentialExposure = requiresBindingValue === true ? "stdlib" : "none";
2842
+ const resourceSignals = getArray(resource, "signals").filter(isRecord3);
2843
+ const resourceTools2 = getArray(resource, "tools").filter(isRecord3);
2844
+ const emittedSignalIds = [];
2845
+ for (const signal of resourceSignals) {
2846
+ const signalName = getString(signal, "name");
2847
+ if (!signalName) continue;
2848
+ const signalId = `${resourceId}.${signalName}`;
2849
+ const signalScope = getString(signal, "scope") === "principal" ? "principal" : "project";
2850
+ const signalSchema = getRecord(signal, "schema") ?? {};
2851
+ const schemaRef = registerBlob(signalSchema);
2852
+ signals2[signalId] = {
2853
+ id: signalId,
2854
+ resource_id: resourceId,
2855
+ name: signalName,
2856
+ event_name: getString(signal, "event_name") ?? "",
2857
+ scope: signalScope,
2858
+ mode: getString(signal, "mode") ?? "",
2859
+ schema_ref: schemaRef
2860
+ };
2861
+ emittedSignalIds.push(signalId);
2862
+ }
2863
+ const emittedToolIds = [];
2864
+ for (const tool of resourceTools2) {
2865
+ const toolName = getString(tool, "name");
2866
+ if (!toolName) continue;
2867
+ const toolId = `${resourceId}.${toolName}`;
2868
+ const effect = deriveEffect(tool.mode);
2869
+ const toolCapability = getRecord(tool, "capability") ?? {};
2870
+ const toolBackendKind = getString(toolCapability, "backend_kind") ?? backend;
2871
+ const toolProviderKind = getString(toolCapability, "provider_kind") ?? providerKind;
2872
+ const toolCredentialExposure = getString(toolCapability, "credential_exposure") ?? "none";
2873
+ const argsSchemaRef = registerBlob(
2874
+ getRecord(tool, "args_schema") ?? {}
2875
+ );
2876
+ const resultSchemaRef = registerBlob(
2877
+ getRecord(tool, "result_schema") ?? {}
2878
+ );
2879
+ const executor = buildExecutor({
2880
+ resourceId,
2881
+ resourceKind,
2882
+ providerKind: toolProviderKind,
2883
+ backendKind: toolBackendKind,
2884
+ toolName,
2885
+ resourceConfig: config,
2886
+ configHash,
2887
+ registerBlob,
2888
+ warnings
2889
+ });
2890
+ if (executor.kind === "unsupported") {
2891
+ warnings.push(
2892
+ `tool "${toolId}" executor deferred: ${executor.reason}`
2893
+ );
2894
+ }
2895
+ tools[toolId] = {
2896
+ id: toolId,
2897
+ resource_id: resourceId,
2898
+ name: toolName,
2899
+ effect,
2900
+ audit_requirement: deriveAuditRequirement(effect),
2901
+ credential_exposure: toolCredentialExposure,
2902
+ args_schema_ref: argsSchemaRef,
2903
+ result_schema_ref: resultSchemaRef,
2904
+ executor
2905
+ };
2906
+ emittedToolIds.push(toolId);
2907
+ }
2908
+ resources[resourceId] = {
2909
+ id: resourceId,
2910
+ application_name: appName,
2911
+ resource_name: resourceName,
2912
+ resource_kind: resourceKind,
2913
+ provider_kind: providerKind,
2914
+ backend_kind: backend,
2915
+ binding_scope: bindingScope,
2916
+ credential_source: credentialSource,
2917
+ credential_exposure: credentialExposure,
2918
+ config_ref: configHash,
2919
+ signals: emittedSignalIds,
2920
+ tools: emittedToolIds
2921
+ };
2922
+ }
2923
+ }
2924
+ const catalogBody = {
2925
+ contract_version: CATALOG_CONTRACT_VERSION,
2926
+ snapshot_version: CATALOG_SNAPSHOT_VERSION,
2927
+ resources,
2928
+ signals: signals2,
2929
+ tools,
2930
+ schemas
2931
+ };
2932
+ const catalogHash = sha256Hex(catalogBody);
2933
+ return {
2934
+ catalog: {
2935
+ ...catalogBody,
2936
+ catalog_hash: catalogHash
2937
+ },
2938
+ warnings
2939
+ };
2940
+ }
2941
+ var INTERNAL_CONFIG = "__io_cli_compiled_config";
2942
+ var INTERNAL_RUN = "__io_cli_compiled_run";
2943
+ var INTERNAL_ON_START = "__io_cli_compiled_on_start";
2944
+ var INTERNAL_ON_MESSAGE = "__io_cli_compiled_on_message";
2945
+ var INTERNAL_ON_END = "__io_cli_compiled_on_end";
2946
+ var INTERNAL_APPLICATIONS = "__io_cli_compiled_applications";
2947
+ function conversationChannel(name) {
2948
+ return `session:${name}`;
2949
+ }
2950
+ function parseModule2(source) {
2951
+ return acorn.parse(source, {
2952
+ ecmaVersion: "latest",
2953
+ sourceType: "module"
2954
+ });
2955
+ }
2956
+ function rootStatementFor(source, statementStart, kind, name) {
2957
+ let ast;
2958
+ try {
2959
+ ast = parseModule2(source);
2960
+ } catch (error) {
2961
+ throw new BuildError(
2962
+ `failed to parse compiled target source: ${error instanceof Error ? error.message : String(error)}`
2963
+ );
2964
+ }
2965
+ const body = ast.body;
2966
+ const stmt = body.find(
2967
+ (candidate) => candidate.start === statementStart
2968
+ );
2969
+ if (stmt) {
2970
+ return stmt;
2971
+ }
2972
+ const fallback = body.find((candidate) => {
2973
+ if (candidate.type !== "ExpressionStatement") {
2974
+ return false;
2975
+ }
2976
+ const expression = candidate.expression;
2977
+ if (expression.type !== "CallExpression") {
2978
+ return false;
2979
+ }
2980
+ const args = expression.arguments;
2981
+ if (args.length < 2 || args[0]?.type !== "Literal" || args[1]?.type !== "ObjectExpression") {
2982
+ return false;
2983
+ }
2984
+ const callee = expression.callee;
2985
+ if (callee.type !== "Identifier") {
2986
+ return false;
2987
+ }
2988
+ return callee.name === kind && args[0].value === name;
2989
+ });
2990
+ if (!fallback) {
2991
+ throw new BuildError(
2992
+ `failed to locate compiled ${kind} root '${name}' at byte offset ${statementStart}`
2993
+ );
2994
+ }
2995
+ return fallback;
2996
+ }
2997
+ function rootSpecNode(stmt) {
2998
+ if (stmt.type !== "ExpressionStatement") {
2270
2999
  throw new BuildError("compiled target root must be an expression statement");
2271
3000
  }
2272
3001
  const expression = stmt.expression;
@@ -2567,13 +3296,90 @@ function pruneUnusedTopLevel(source, seeds) {
2567
3296
  }
2568
3297
  return applyEdits2(source, edits);
2569
3298
  }
3299
+ function parseDurationMs(value) {
3300
+ if (value === void 0) return void 0;
3301
+ if (typeof value === "number") {
3302
+ if (Number.isInteger(value) && value > 0) return value;
3303
+ throw new BuildError("execution.timeout must be a positive integer number of milliseconds");
3304
+ }
3305
+ if (typeof value !== "string") {
3306
+ throw new BuildError("execution.timeout must be a duration string or milliseconds");
3307
+ }
3308
+ const match = value.trim().toLowerCase().match(/^(\d+)\s*(ms|s|m|h)$/);
3309
+ if (!match) {
3310
+ throw new BuildError("execution.timeout must use ms, s, m, or h units");
3311
+ }
3312
+ const amount = Number(match[1]);
3313
+ const unit = match[2];
3314
+ const multiplier = unit === "ms" ? 1 : unit === "s" ? 1e3 : unit === "m" ? 6e4 : 36e5;
3315
+ return amount * multiplier;
3316
+ }
3317
+ function parseMemoryMb(value) {
3318
+ if (value === void 0) return void 0;
3319
+ if (typeof value === "number") {
3320
+ if (Number.isInteger(value) && value > 0) return value;
3321
+ throw new BuildError("execution.memory must be a positive integer number of megabytes");
3322
+ }
3323
+ if (typeof value !== "string") {
3324
+ throw new BuildError("execution.memory must be a size string or megabytes");
3325
+ }
3326
+ const match = value.trim().toLowerCase().match(/^(\d+)\s*(mb|m|gb|g)$/);
3327
+ if (!match) {
3328
+ throw new BuildError("execution.memory must use mb or gb units");
3329
+ }
3330
+ const amount = Number(match[1]);
3331
+ const unit = match[2];
3332
+ return unit === "gb" || unit === "g" ? amount * 1024 : amount;
3333
+ }
3334
+ function nonNegativeInteger(value, field) {
3335
+ if (value === void 0) return void 0;
3336
+ if (Number.isInteger(value) && value >= 0) return value;
3337
+ throw new BuildError(`${field} must be a non-negative integer`);
3338
+ }
3339
+ function normalizeBudgetMap(value, field) {
3340
+ if (value === void 0) return void 0;
3341
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3342
+ throw new BuildError(`${field} must be an object`);
3343
+ }
3344
+ const out = {};
3345
+ for (const [key, budget] of Object.entries(value)) {
3346
+ if (!key.trim()) {
3347
+ throw new BuildError(`${field} keys must be non-empty strings`);
3348
+ }
3349
+ out[key] = nonNegativeInteger(budget, `${field}.${key}`);
3350
+ }
3351
+ return out;
3352
+ }
3353
+ function normalizeRateBudget(rateBudget) {
3354
+ if (rateBudget === void 0) return void 0;
3355
+ const perApplication = normalizeBudgetMap(
3356
+ rateBudget.perApplication,
3357
+ "execution.rateBudget.perApplication"
3358
+ );
3359
+ const perCapability = normalizeBudgetMap(
3360
+ rateBudget.perCapability,
3361
+ "execution.rateBudget.perCapability"
3362
+ );
3363
+ const out = {
3364
+ ...perApplication ? { per_application: perApplication } : {},
3365
+ ...perCapability ? { per_capability: perCapability } : {}
3366
+ };
3367
+ return Object.keys(out).length > 0 ? out : void 0;
3368
+ }
2570
3369
  function compactExecution(execution) {
2571
3370
  if (!execution) {
2572
3371
  return void 0;
2573
3372
  }
2574
- const compact = Object.fromEntries(
2575
- Object.entries(execution).filter(([, value]) => value !== void 0)
2576
- );
3373
+ const timeoutMs = parseDurationMs(execution.timeout);
3374
+ const memoryMb = parseMemoryMb(execution.memory);
3375
+ const maxToolCalls = nonNegativeInteger(execution.maxToolCalls, "execution.maxToolCalls");
3376
+ const rateBudget = normalizeRateBudget(execution.rateBudget);
3377
+ const compact = {
3378
+ ...timeoutMs !== void 0 ? { timeout_ms: timeoutMs } : {},
3379
+ ...memoryMb !== void 0 ? { memory_mb: memoryMb } : {},
3380
+ ...maxToolCalls !== void 0 ? { max_tool_calls: maxToolCalls } : {},
3381
+ ...rateBudget !== void 0 ? { rate_budget: rateBudget } : {}
3382
+ };
2577
3383
  return Object.keys(compact).length > 0 ? compact : void 0;
2578
3384
  }
2579
3385
  function buildBehaviorReplacement(source, spec, behavior) {
@@ -2583,8 +3389,6 @@ function buildBehaviorReplacement(source, spec, behavior) {
2583
3389
  }
2584
3390
  const execution = compactExecution(behavior.execution);
2585
3391
  const runSource = functionSourceFromProperty(source, run, "run");
2586
- const trackingId = findProperty(spec, "trackingId");
2587
- const trackingIdSource = trackingId ? functionSourceFromProperty(source, trackingId, "trackingId") : void 0;
2588
3392
  const applications = findProperty(spec, "applications");
2589
3393
  const applicationsSource = applications ? expressionSourceFromProperty(source, applications, "applications") : void 0;
2590
3394
  const configEntries = [
@@ -2595,9 +3399,6 @@ function buildBehaviorReplacement(source, spec, behavior) {
2595
3399
  if (execution) {
2596
3400
  configEntries.push(`execution: ${JSON.stringify(execution)}`);
2597
3401
  }
2598
- if (behavior.messageHistory !== void 0) {
2599
- configEntries.push(`messageHistory: ${JSON.stringify(behavior.messageHistory)}`);
2600
- }
2601
3402
  if (applicationsSource) {
2602
3403
  configEntries.push(`applications: ${INTERNAL_APPLICATIONS}`);
2603
3404
  }
@@ -2606,17 +3407,14 @@ function buildBehaviorReplacement(source, spec, behavior) {
2606
3407
  replacement: [
2607
3408
  ...applicationsSource ? [`const ${INTERNAL_APPLICATIONS} = ${applicationsSource};`] : [],
2608
3409
  `const ${INTERNAL_CONFIG} = ${configSource};`,
2609
- `const ${INTERNAL_TRACKING_ID} = ${trackingIdSource ?? "undefined"};`,
2610
3410
  `const ${INTERNAL_RUN} = ${runSource};`,
2611
- `export { ${INTERNAL_CONFIG} as config, ${INTERNAL_TRACKING_ID} as trackingId, ${INTERNAL_RUN} as run };`
3411
+ `export { ${INTERNAL_CONFIG} as config, ${INTERNAL_RUN} as run };`
2612
3412
  ].join("\n"),
2613
3413
  refs: /* @__PURE__ */ new Set([
2614
3414
  INTERNAL_CONFIG,
2615
- INTERNAL_TRACKING_ID,
2616
3415
  INTERNAL_RUN,
2617
3416
  ...applicationsSource ? [INTERNAL_APPLICATIONS] : [],
2618
3417
  ...refsFromExpressionSource(runSource),
2619
- ...trackingIdSource ? refsFromExpressionSource(trackingIdSource) : [],
2620
3418
  ...applicationsSource ? refsFromExpressionSource(applicationsSource) : []
2621
3419
  ])
2622
3420
  };
@@ -2756,7 +3554,15 @@ async function buildPipeline(config, cwd = process.cwd(), minify = false, buildD
2756
3554
  conversationBundles
2757
3555
  });
2758
3556
  validateManifest(manifest);
2759
- return { manifest, outDir };
3557
+ const { catalog: catalog2, warnings } = generateCatalog(manifest);
3558
+ if (warnings.length > 0) {
3559
+ throw new BuildError(
3560
+ `Catalog generation produced ${warnings.length} unrecoverable issue${warnings.length === 1 ? "" : "s"} \u2014 refusing to ship a partial catalog:
3561
+ ${warnings.map((w) => ` - ${w}`).join("\n")}`
3562
+ );
3563
+ }
3564
+ await writeFile(join(outDir, "catalog.json"), stableStringify(catalog2, 2), "utf-8");
3565
+ return { manifest, catalog: catalog2, outDir };
2760
3566
  }
2761
3567
  async function createStagingDir() {
2762
3568
  return mkdtemp(join(tmpdir(), "io-cli-"));
@@ -2820,8 +3626,8 @@ async function buildCommand(options) {
2820
3626
  async function zipBuildOutput(buildDir, outPath) {
2821
3627
  const output = createWriteStream(outPath);
2822
3628
  const archive = archiver("zip", { zlib: { level: 9 } });
2823
- const done = new Promise((resolve4, reject) => {
2824
- output.on("close", resolve4);
3629
+ const done = new Promise((resolve5, reject) => {
3630
+ output.on("close", resolve5);
2825
3631
  archive.on("error", reject);
2826
3632
  });
2827
3633
  archive.pipe(output);
@@ -2850,13 +3656,13 @@ async function addDirectory(archive, baseDir, prefix, skipRelativePath) {
2850
3656
  }
2851
3657
 
2852
3658
  // src/deploy/diff.ts
2853
- function stableStringify(value) {
3659
+ function stableStringify2(value) {
2854
3660
  if (Array.isArray(value)) {
2855
- return `[${value.map(stableStringify).join(",")}]`;
3661
+ return `[${value.map(stableStringify2).join(",")}]`;
2856
3662
  }
2857
3663
  if (value && typeof value === "object") {
2858
3664
  const record = value;
2859
- return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
3665
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(record[key])}`).join(",")}}`;
2860
3666
  }
2861
3667
  return JSON.stringify(value);
2862
3668
  }
@@ -2882,7 +3688,7 @@ function diffEntries(local, remote) {
2882
3688
  for (const { name, value } of local) {
2883
3689
  if (!remoteMap.has(name)) {
2884
3690
  entries.push({ name, kind: "added" });
2885
- } else if (stableStringify(value) !== stableStringify(remoteMap.get(name))) {
3691
+ } else if (stableStringify2(value) !== stableStringify2(remoteMap.get(name))) {
2886
3692
  entries.push({ name, kind: "changed" });
2887
3693
  }
2888
3694
  }
@@ -2995,6 +3801,77 @@ function diffManifests(local, remote) {
2995
3801
  hasChanges
2996
3802
  };
2997
3803
  }
3804
+
3805
+ // src/deploy/catalog-diff.ts
3806
+ function stableStringify3(value) {
3807
+ if (Array.isArray(value)) {
3808
+ return `[${value.map(stableStringify3).join(",")}]`;
3809
+ }
3810
+ if (value && typeof value === "object") {
3811
+ const record = value;
3812
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify3(record[key])}`).join(",")}}`;
3813
+ }
3814
+ return JSON.stringify(value);
3815
+ }
3816
+ function diffSection(local, remote) {
3817
+ const localMap = local ?? {};
3818
+ const remoteMap = remote ?? {};
3819
+ const entries = [];
3820
+ const seen = /* @__PURE__ */ new Set();
3821
+ for (const id of Object.keys(localMap).sort()) {
3822
+ seen.add(id);
3823
+ if (!(id in remoteMap)) {
3824
+ entries.push({ id, kind: "added" });
3825
+ } else if (stableStringify3(localMap[id]) !== stableStringify3(remoteMap[id])) {
3826
+ entries.push({ id, kind: "changed" });
3827
+ }
3828
+ }
3829
+ for (const id of Object.keys(remoteMap).sort()) {
3830
+ if (!seen.has(id) && !(id in localMap)) {
3831
+ entries.push({ id, kind: "removed" });
3832
+ }
3833
+ }
3834
+ return entries;
3835
+ }
3836
+ function schemaDiffCounts(local, remote) {
3837
+ const localKeys = new Set(Object.keys(local ?? {}));
3838
+ const remoteKeys = new Set(Object.keys(remote ?? {}));
3839
+ let added = 0;
3840
+ let removed = 0;
3841
+ for (const k of localKeys) {
3842
+ if (!remoteKeys.has(k)) added++;
3843
+ }
3844
+ for (const k of remoteKeys) {
3845
+ if (!localKeys.has(k)) removed++;
3846
+ }
3847
+ return { added, removed };
3848
+ }
3849
+ function diffCatalogs(local, remote) {
3850
+ const localResources = local?.resources ?? {};
3851
+ const remoteResources = remote?.resources ?? {};
3852
+ const localSignals = local?.signals ?? {};
3853
+ const remoteSignals = remote?.signals ?? {};
3854
+ const localTools = local?.tools ?? {};
3855
+ const remoteTools = remote?.tools ?? {};
3856
+ const localSchemas = local?.schemas ?? {};
3857
+ const remoteSchemas = remote?.schemas ?? {};
3858
+ const resources = diffSection(localResources, remoteResources);
3859
+ const signals2 = diffSection(localSignals, remoteSignals);
3860
+ const tools = diffSection(localTools, remoteTools);
3861
+ const schemas = schemaDiffCounts(localSchemas, remoteSchemas);
3862
+ const localHash = local?.catalog_hash ?? null;
3863
+ const remoteHash = remote?.catalog_hash ?? null;
3864
+ const hashChanged = (localHash ?? remoteHash) !== null && localHash !== remoteHash;
3865
+ const hasChanges = resources.length > 0 || signals2.length > 0 || tools.length > 0 || schemas.added > 0 || schemas.removed > 0 || hashChanged;
3866
+ return {
3867
+ hashChanged,
3868
+ resources,
3869
+ signals: signals2,
3870
+ tools,
3871
+ schemas,
3872
+ hasChanges
3873
+ };
3874
+ }
2998
3875
  var SYMBOLS = {
2999
3876
  added: chalk12.green("+ "),
3000
3877
  changed: chalk12.yellow("~ "),
@@ -3014,7 +3891,12 @@ var SECTIONS = [
3014
3891
  { key: "targets", label: "Targets" },
3015
3892
  { key: "subscriptions", label: "Subscriptions" }
3016
3893
  ];
3017
- function formatPlan(diff) {
3894
+ var CATALOG_SECTIONS = [
3895
+ { key: "resources", label: "Catalog resources" },
3896
+ { key: "signals", label: "Catalog signals" },
3897
+ { key: "tools", label: "Catalog tools" }
3898
+ ];
3899
+ function formatPlan(diff, catalogDiff) {
3018
3900
  const lines = [];
3019
3901
  for (const { key, label } of SECTIONS) {
3020
3902
  const entries = diff[key];
@@ -3029,15 +3911,44 @@ function formatPlan(diff) {
3029
3911
  }
3030
3912
  }
3031
3913
  }
3914
+ if (catalogDiff) {
3915
+ lines.push("");
3916
+ lines.push(
3917
+ chalk12.bold(
3918
+ catalogDiff.hashChanged ? " Catalog (hash changed):" : " Catalog:"
3919
+ )
3920
+ );
3921
+ for (const { key, label } of CATALOG_SECTIONS) {
3922
+ const entries = catalogDiff[key];
3923
+ lines.push(` ${label}:`);
3924
+ if (entries.length === 0) {
3925
+ lines.push(chalk12.gray(" (no changes)"));
3926
+ } else {
3927
+ for (const e of entries) {
3928
+ lines.push(
3929
+ ` ${SYMBOLS[e.kind]}${e.id} ${chalk12.gray(`(${LABELS[e.kind]})`)}`
3930
+ );
3931
+ }
3932
+ }
3933
+ }
3934
+ const schemaLine = catalogDiff.schemas;
3935
+ if (schemaLine.added > 0 || schemaLine.removed > 0) {
3936
+ lines.push(
3937
+ ` ${chalk12.gray("Schemas:")} ${chalk12.green(`+${schemaLine.added}`)} ${chalk12.red(`-${schemaLine.removed}`)}`
3938
+ );
3939
+ } else {
3940
+ lines.push(` ${chalk12.gray("Schemas: (no changes)")}`);
3941
+ }
3942
+ }
3032
3943
  return lines.join("\n");
3033
3944
  }
3034
3945
  async function confirmPlan(skip) {
3035
3946
  if (skip) return true;
3036
3947
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3037
- return new Promise((resolve4) => {
3948
+ return new Promise((resolve5) => {
3038
3949
  rl.question("\nApply these changes? (y/n) ", (answer) => {
3039
3950
  rl.close();
3040
- resolve4(answer.trim().toLowerCase() === "y");
3951
+ resolve5(answer.trim().toLowerCase() === "y");
3041
3952
  });
3042
3953
  });
3043
3954
  }
@@ -3111,40 +4022,6 @@ function socketAuthParams(creds) {
3111
4022
  return { authorization: `Bearer ${creds.token}` };
3112
4023
  }
3113
4024
 
3114
- // src/build/stable-json.ts
3115
- function stableStringify2(value, indent) {
3116
- return JSON.stringify(canonicalize(value), null, indent);
3117
- }
3118
- function canonicalize(value) {
3119
- if (value === null) return null;
3120
- if (Array.isArray(value)) {
3121
- return value.map(canonicalize);
3122
- }
3123
- if (typeof value === "object") {
3124
- const obj = value;
3125
- const sorted = {};
3126
- for (const key of Object.keys(obj).sort()) {
3127
- sorted[key] = canonicalize(obj[key]);
3128
- }
3129
- return sorted;
3130
- }
3131
- return value;
3132
- }
3133
-
3134
- // src/runtime-signal-name.ts
3135
- var EVENT_NAME_REGEX2 = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
3136
- var SIGNAL_NAME_REGEX = /^signal:[A-Za-z0-9][A-Za-z0-9._-]*$/;
3137
- function normalizeRuntimeSignalName2(value) {
3138
- const trimmed = value.trim();
3139
- if (EVENT_NAME_REGEX2.test(trimmed)) {
3140
- return `signal:${trimmed}`;
3141
- }
3142
- if (SIGNAL_NAME_REGEX.test(trimmed)) {
3143
- return trimmed;
3144
- }
3145
- return null;
3146
- }
3147
-
3148
4025
  // src/api/client.ts
3149
4026
  var REFRESH_MUTATION = `
3150
4027
  mutation Refresh($input: RefreshAuthInput!) {
@@ -3245,8 +4122,32 @@ function graphqlErrorMessage(errors) {
3245
4122
  }
3246
4123
  return null;
3247
4124
  }
3248
- function isMissingSnapshotJsonFieldError(error) {
3249
- return error instanceof ApiError && error.message.includes("snapshotJson") && error.message.includes("Cannot query field");
4125
+ function isPlainObject(value) {
4126
+ return !!value && typeof value === "object" && !Array.isArray(value);
4127
+ }
4128
+ function isCatalogShape(value) {
4129
+ if (!isPlainObject(value)) return false;
4130
+ return value.contract_version === "io.invocation.v2" && value.snapshot_version === "io_runtime.snapshot.v2" && typeof value.catalog_hash === "string" && isPlainObject(value.resources) && isPlainObject(value.signals) && isPlainObject(value.tools) && isPlainObject(value.schemas);
4131
+ }
4132
+ function isManifestShape(value) {
4133
+ if (!isPlainObject(value)) return false;
4134
+ return value.contract_version === "io.invocation.v2" && value.snapshot_version === "io_runtime.snapshot.v2" && Array.isArray(value.secret_bundles) && Array.isArray(value.apps) && Array.isArray(value.endpoints) && Array.isArray(value.targets) && Array.isArray(value.subscriptions);
4135
+ }
4136
+ function parseRemoteCatalog(catalogJson) {
4137
+ let parsed;
4138
+ try {
4139
+ parsed = JSON.parse(catalogJson);
4140
+ } catch (err) {
4141
+ throw new ConfigError(
4142
+ `Remote deployment catalog was not valid JSON: ${err instanceof Error ? err.message : String(err)}`
4143
+ );
4144
+ }
4145
+ if (!isCatalogShape(parsed)) {
4146
+ throw new ConfigError(
4147
+ "Remote deployment catalog does not match the io.invocation.v2 catalog shape."
4148
+ );
4149
+ }
4150
+ return parsed;
3250
4151
  }
3251
4152
  function parseRemoteManifest(snapshotJson) {
3252
4153
  let parsed;
@@ -3257,11 +4158,22 @@ function parseRemoteManifest(snapshotJson) {
3257
4158
  `Remote deployment snapshot was not valid JSON: ${err instanceof Error ? err.message : String(err)}`
3258
4159
  );
3259
4160
  }
3260
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3261
- throw new ConfigError("Remote deployment snapshot was not a JSON object.");
4161
+ if (!isManifestShape(parsed)) {
4162
+ throw new ConfigError(
4163
+ "Remote deployment snapshot does not match the io_runtime.snapshot.v2 manifest shape."
4164
+ );
3262
4165
  }
3263
4166
  return parsed;
3264
4167
  }
4168
+ var EMPTY_MANIFEST = {
4169
+ contract_version: "io.invocation.v2",
4170
+ snapshot_version: "io_runtime.snapshot.v2",
4171
+ secret_bundles: [],
4172
+ apps: [],
4173
+ endpoints: [],
4174
+ targets: [],
4175
+ subscriptions: []
4176
+ };
3265
4177
  async function executeGraphQLUnauth(apiUrl, query, variables, headers = {}) {
3266
4178
  const response = await fetch(`${trimBaseUrl(apiUrl)}/graphql`, {
3267
4179
  method: "POST",
@@ -3478,6 +4390,23 @@ function createClient(config) {
3478
4390
  );
3479
4391
  return data.createProject;
3480
4392
  }
4393
+ async function deleteProject(organizationId, projectIdValue) {
4394
+ const data = await graphql(
4395
+ `
4396
+ mutation DeleteProject($organizationId: ID!, $id: ID!) {
4397
+ deleteProject(organizationId: $organizationId, id: $id) {
4398
+ id
4399
+ name
4400
+ slug
4401
+ description
4402
+ organizationId
4403
+ }
4404
+ }
4405
+ `,
4406
+ { organizationId, id: projectIdValue }
4407
+ );
4408
+ return data.deleteProject;
4409
+ }
3481
4410
  async function principals2(projectIdValue) {
3482
4411
  const data = await graphql(
3483
4412
  `
@@ -3607,28 +4536,34 @@ function createClient(config) {
3607
4536
  return data.deleteSecret === true;
3608
4537
  }
3609
4538
  async function fetchRemoteManifest(projectIdValue) {
3610
- let data;
3611
- try {
3612
- data = await graphql(
3613
- `
4539
+ const data = await graphql(
4540
+ `
3614
4541
  query ActiveDeploymentSnapshot($projectId: ID!) {
3615
4542
  activeDeployment(projectId: $projectId) {
3616
4543
  snapshotJson
3617
4544
  }
3618
4545
  }
3619
4546
  `,
3620
- { projectId: projectIdValue }
3621
- );
3622
- } catch (err) {
3623
- if (isMissingSnapshotJsonFieldError(err)) {
3624
- return {};
3625
- }
3626
- throw err;
3627
- }
4547
+ { projectId: projectIdValue }
4548
+ );
3628
4549
  const snapshotJson = data.activeDeployment?.snapshotJson;
3629
- return typeof snapshotJson === "string" && snapshotJson.trim() !== "" ? parseRemoteManifest(snapshotJson) : {};
4550
+ return typeof snapshotJson === "string" && snapshotJson.trim() !== "" ? parseRemoteManifest(snapshotJson) : EMPTY_MANIFEST;
4551
+ }
4552
+ async function fetchRemoteCatalog(projectIdValue) {
4553
+ const data = await graphql(
4554
+ `
4555
+ query ActiveDeploymentCatalog($projectId: ID!) {
4556
+ activeDeployment(projectId: $projectId) {
4557
+ catalogJson
4558
+ }
4559
+ }
4560
+ `,
4561
+ { projectId: projectIdValue }
4562
+ );
4563
+ const catalogJson = data.activeDeployment?.catalogJson;
4564
+ return typeof catalogJson === "string" && catalogJson.trim() !== "" ? parseRemoteCatalog(catalogJson) : null;
3630
4565
  }
3631
- async function deployBundle(bundlePath, manifest) {
4566
+ async function deployBundle(bundlePath, manifest, catalog2) {
3632
4567
  const MAX_BUNDLE_BYTES = 100 * 1024 * 1024;
3633
4568
  const fileStat = await stat(bundlePath);
3634
4569
  if (fileStat.size > MAX_BUNDLE_BYTES) {
@@ -3666,7 +4601,8 @@ function createClient(config) {
3666
4601
  projectId: projectIdValue,
3667
4602
  bundleHash: upload.bundleHash,
3668
4603
  revision: nextRevision,
3669
- snapshotJson: stableStringify2(manifest)
4604
+ snapshotJson: stableStringify(manifest),
4605
+ catalogJson: stableStringify(catalog2)
3670
4606
  }
3671
4607
  }
3672
4608
  );
@@ -3677,7 +4613,7 @@ function createClient(config) {
3677
4613
  if (!input.principalId || input.principalId.trim() === "") {
3678
4614
  throw new ConfigError("principalId is required to emit a signal");
3679
4615
  }
3680
- const eventName = normalizeRuntimeSignalName2(input.signal);
4616
+ const eventName = normalizeRuntimeSignalName(input.signal);
3681
4617
  if (!eventName) {
3682
4618
  throw new ConfigError(
3683
4619
  "signal name must be a bare event name like smoke.start or use the signal: namespace"
@@ -3730,6 +4666,37 @@ function createClient(config) {
3730
4666
  );
3731
4667
  return data.activeRevision;
3732
4668
  }
4669
+ async function reconcileProject(projectIdValue) {
4670
+ const data = await graphql(
4671
+ `
4672
+ mutation ReconcileProject($projectId: ID!) {
4673
+ reconcileProject(projectId: $projectId)
4674
+ }
4675
+ `,
4676
+ { projectId: projectIdValue }
4677
+ );
4678
+ return data.reconcileProject === true;
4679
+ }
4680
+ async function deploymentStatus(projectIdValue, revision) {
4681
+ const data = await graphql(
4682
+ `
4683
+ query DeploymentStatus($projectId: ID!, $revision: Int!) {
4684
+ deploymentStatus(projectId: $projectId, revision: $revision) {
4685
+ id
4686
+ projectId
4687
+ revision
4688
+ bundleHash
4689
+ status
4690
+ activatedAt
4691
+ insertedAt
4692
+ updatedAt
4693
+ }
4694
+ }
4695
+ `,
4696
+ { projectId: projectIdValue, revision }
4697
+ );
4698
+ return data.deploymentStatus;
4699
+ }
3733
4700
  async function quotaStatuses() {
3734
4701
  const data = await graphql(
3735
4702
  `
@@ -3781,6 +4748,7 @@ function createClient(config) {
3781
4748
  organizations,
3782
4749
  projects: projects2,
3783
4750
  createProject,
4751
+ deleteProject,
3784
4752
  principals: principals2,
3785
4753
  createPrincipal,
3786
4754
  updatePrincipal,
@@ -3789,14 +4757,245 @@ function createClient(config) {
3789
4757
  putSecret,
3790
4758
  deleteSecret,
3791
4759
  fetchRemoteManifest,
4760
+ fetchRemoteCatalog,
3792
4761
  deployBundle,
4762
+ deploymentStatus,
4763
+ reconcileProject,
3793
4764
  emitSignal,
3794
4765
  quotaStatuses,
3795
4766
  projectEvents
3796
4767
  };
3797
4768
  }
4769
+ Object.assign(globalThis, { WebSocket });
4770
+ var SessionTokenNotSupportedError = class extends Error {
4771
+ constructor(message) {
4772
+ super(
4773
+ message ?? "WebSocket subscriptions require API key authentication on the current io_runtime socket contract."
4774
+ );
4775
+ this.name = "SessionTokenNotSupportedError";
4776
+ }
4777
+ };
4778
+ function buildSocketUrl(apiUrl) {
4779
+ const url = new URL(apiUrl);
4780
+ const basePath = url.pathname.replace(/\/+$/, "");
4781
+ const socketBase = basePath.endsWith("/graphql") ? basePath.slice(0, -"/graphql".length) : basePath;
4782
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
4783
+ url.pathname = `${socketBase === "/" ? "" : socketBase}/socket`;
4784
+ url.search = "";
4785
+ url.hash = "";
4786
+ return url.toString();
4787
+ }
4788
+ function createSocketClient(options) {
4789
+ let phoenixSocket = null;
4790
+ let absintheSocket = null;
4791
+ const notifiers = /* @__PURE__ */ new Set();
4792
+ function ensureConnection() {
4793
+ if (!options.credentials.apiKey) {
4794
+ throw new SessionTokenNotSupportedError();
4795
+ }
4796
+ if (!phoenixSocket || !absintheSocket) {
4797
+ phoenixSocket = new Socket(buildSocketUrl(options.apiUrl), {
4798
+ params: socketAuthParams(options.credentials),
4799
+ timeout: options.timeoutMs ?? 1e4
4800
+ });
4801
+ absintheSocket = AbsintheSocket.create(phoenixSocket);
4802
+ phoenixSocket.connect();
4803
+ }
4804
+ return { absintheSocket };
4805
+ }
4806
+ function tearDownIfIdle() {
4807
+ if (notifiers.size === 0 && phoenixSocket) {
4808
+ phoenixSocket.disconnect();
4809
+ phoenixSocket = null;
4810
+ absintheSocket = null;
4811
+ }
4812
+ }
4813
+ return {
4814
+ subscribe(operation, variables, observer) {
4815
+ const conn = ensureConnection();
4816
+ const notifier = AbsintheSocket.send(conn.absintheSocket, {
4817
+ operation,
4818
+ variables
4819
+ });
4820
+ notifiers.add(notifier);
4821
+ AbsintheSocket.observe(conn.absintheSocket, notifier, {
4822
+ onAbort: (err) => {
4823
+ notifiers.delete(notifier);
4824
+ if (observer.onAbort) {
4825
+ observer.onAbort(err);
4826
+ } else {
4827
+ observer.onError?.(err);
4828
+ }
4829
+ tearDownIfIdle();
4830
+ },
4831
+ onCancel: () => {
4832
+ notifiers.delete(notifier);
4833
+ tearDownIfIdle();
4834
+ },
4835
+ onError: (err) => {
4836
+ observer.onError?.(err);
4837
+ },
4838
+ onResult: (result) => {
4839
+ observer.onResult?.(result);
4840
+ },
4841
+ onStart: () => {
4842
+ observer.onStart?.();
4843
+ }
4844
+ });
4845
+ return () => {
4846
+ if (!notifiers.has(notifier)) return;
4847
+ notifiers.delete(notifier);
4848
+ try {
4849
+ AbsintheSocket.cancel(conn.absintheSocket, notifier);
4850
+ } catch {
4851
+ }
4852
+ tearDownIfIdle();
4853
+ };
4854
+ },
4855
+ close() {
4856
+ notifiers.clear();
4857
+ if (phoenixSocket) {
4858
+ phoenixSocket.disconnect();
4859
+ }
4860
+ phoenixSocket = null;
4861
+ absintheSocket = null;
4862
+ }
4863
+ };
4864
+ }
4865
+
4866
+ // src/api/watcher.ts
4867
+ var DEFAULT_POLL_INTERVAL_MS = 2e3;
4868
+ var DEFAULT_SEEN_ID_LIMIT = 1e3;
4869
+ var DEFAULT_POLL_SAFETY_LOOPS = 10;
4870
+ function installSigintHandler(onTerminate) {
4871
+ const handler = () => {
4872
+ onTerminate();
4873
+ };
4874
+ process.once("SIGINT", handler);
4875
+ return () => {
4876
+ process.removeListener("SIGINT", handler);
4877
+ };
4878
+ }
4879
+ async function watch(plan, handlers) {
4880
+ const seenIds = /* @__PURE__ */ new Set();
4881
+ const seenOrder = [];
4882
+ const seenLimit = plan.seenIdLimit ?? DEFAULT_SEEN_ID_LIMIT;
4883
+ function rememberId(id) {
4884
+ if (seenIds.has(id)) return;
4885
+ seenIds.add(id);
4886
+ seenOrder.push(id);
4887
+ while (seenOrder.length > seenLimit) {
4888
+ const evicted = seenOrder.shift();
4889
+ if (evicted) seenIds.delete(evicted);
4890
+ }
4891
+ }
4892
+ function handle(event) {
4893
+ const id = plan.getId(event);
4894
+ if (seenIds.has(id)) return false;
4895
+ rememberId(id);
4896
+ handlers.onEvent(event);
4897
+ return true;
4898
+ }
4899
+ let cursor = plan.initialSince ?? (/* @__PURE__ */ new Date()).toISOString();
4900
+ function advanceCursor(event) {
4901
+ if (!plan.getCursor) return;
4902
+ const next = plan.getCursor(event);
4903
+ if (next > cursor) cursor = next;
4904
+ }
4905
+ if (plan.snapshot) {
4906
+ const initial = await plan.snapshot();
4907
+ for (const event of initial) {
4908
+ handle(event);
4909
+ advanceCursor(event);
4910
+ if (plan.isTerminal?.(event)) return;
4911
+ }
4912
+ }
4913
+ return new Promise((resolve5, reject) => {
4914
+ let cleanupSigint;
4915
+ let subscribeHandle;
4916
+ let pollTimer;
4917
+ let terminated = false;
4918
+ function finalize(action) {
4919
+ if (terminated) return;
4920
+ terminated = true;
4921
+ subscribeHandle?.unsubscribe();
4922
+ if (pollTimer) clearTimeout(pollTimer);
4923
+ cleanupSigint?.();
4924
+ action();
4925
+ }
4926
+ cleanupSigint = installSigintHandler(() => {
4927
+ finalize(() => resolve5());
4928
+ });
4929
+ function processLiveEvent(event) {
4930
+ const accepted = handle(event);
4931
+ if (accepted) advanceCursor(event);
4932
+ if (plan.isTerminal?.(event)) finalize(() => resolve5());
4933
+ }
4934
+ async function startPolling() {
4935
+ if (!plan.poll) {
4936
+ finalize(() => reject(new Error("No polling fallback configured for this watch.")));
4937
+ return;
4938
+ }
4939
+ handlers.onConnected?.("polling");
4940
+ const interval = plan.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
4941
+ const safetyLoops = plan.pollBatchSafetyLoops ?? DEFAULT_POLL_SAFETY_LOOPS;
4942
+ const loop = async () => {
4943
+ if (terminated) return;
4944
+ try {
4945
+ for (let i = 0; i < safetyLoops; i += 1) {
4946
+ const batch = await plan.poll(cursor);
4947
+ let anyNew = false;
4948
+ for (const event of batch) {
4949
+ if (handle(event)) anyNew = true;
4950
+ advanceCursor(event);
4951
+ if (plan.isTerminal?.(event)) {
4952
+ finalize(() => resolve5());
4953
+ return;
4954
+ }
4955
+ }
4956
+ if (!anyNew) break;
4957
+ }
4958
+ } catch (err) {
4959
+ finalize(
4960
+ () => reject(err instanceof Error ? err : new Error(String(err)))
4961
+ );
4962
+ return;
4963
+ }
4964
+ pollTimer = setTimeout(loop, interval);
4965
+ };
4966
+ void loop();
4967
+ }
4968
+ if (!plan.subscribe) {
4969
+ void startPolling();
4970
+ return;
4971
+ }
4972
+ try {
4973
+ subscribeHandle = plan.subscribe(
4974
+ processLiveEvent,
4975
+ (err) => {
4976
+ if (err instanceof SessionTokenNotSupportedError && plan.poll) {
4977
+ subscribeHandle = void 0;
4978
+ void startPolling();
4979
+ return;
4980
+ }
4981
+ finalize(() => reject(err));
4982
+ },
4983
+ () => handlers.onConnected?.("subscription")
4984
+ );
4985
+ } catch (err) {
4986
+ if (err instanceof SessionTokenNotSupportedError && plan.poll) {
4987
+ void startPolling();
4988
+ return;
4989
+ }
4990
+ finalize(() => reject(err instanceof Error ? err : new Error(String(err))));
4991
+ }
4992
+ });
4993
+ }
3798
4994
 
3799
4995
  // src/commands/deploy.ts
4996
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["active", "failed", "superseded"]);
4997
+ var WATCH_POLL_INTERVAL_MS = 2e3;
4998
+ var WATCH_DEADLINE_MS = 5 * 6e4;
3800
4999
  async function deployCommand(options) {
3801
5000
  let stagingDir;
3802
5001
  try {
@@ -3805,7 +5004,7 @@ async function deployCommand(options) {
3805
5004
  });
3806
5005
  console.log(chalk12.gray("Planning deployment..."));
3807
5006
  stagingDir = await createStagingDir();
3808
- const { manifest } = await buildPipeline(
5007
+ const { manifest, catalog: catalog2 } = await buildPipeline(
3809
5008
  config,
3810
5009
  process.cwd(),
3811
5010
  options.minify ?? true,
@@ -3824,13 +5023,17 @@ async function deployCommand(options) {
3824
5023
  projectId: config.projectId
3825
5024
  });
3826
5025
  console.log(chalk12.gray("\nComparing with remote..."));
3827
- const remoteManifest = await client.fetchRemoteManifest(client.projectId());
5026
+ const remoteManifest = await client.fetchRemoteManifest(
5027
+ client.projectId()
5028
+ );
5029
+ const remoteCatalog = await client.fetchRemoteCatalog(client.projectId());
3828
5030
  const diff = diffManifests(manifest, remoteManifest);
3829
- if (!diff.hasChanges) {
5031
+ const catalogDiff = diffCatalogs(catalog2, remoteCatalog);
5032
+ if (!diff.hasChanges && !catalogDiff.hasChanges) {
3830
5033
  console.log(chalk12.green("\n\u2713 No changes to deploy."));
3831
5034
  return;
3832
5035
  }
3833
- console.log("\n" + formatPlan(diff));
5036
+ console.log("\n" + formatPlan(diff, catalogDiff));
3834
5037
  const confirmed = await confirmPlan(options.yes ?? false);
3835
5038
  if (!confirmed) {
3836
5039
  console.log(chalk12.yellow("Deploy cancelled."));
@@ -3839,11 +5042,14 @@ async function deployCommand(options) {
3839
5042
  console.log(chalk12.gray("\nApplying deployment..."));
3840
5043
  const zipPath = join(stagingDir, "deploy.zip");
3841
5044
  await zipBuildOutput(stagingDir, zipPath);
3842
- const revision = await client.deployBundle(zipPath, manifest);
5045
+ const revision = await client.deployBundle(zipPath, manifest, catalog2);
3843
5046
  console.log(
3844
5047
  chalk12.green(`
3845
- \u2713 Deployment complete (revision ${revision.revision}, ${revision.status})`)
5048
+ \u2713 Deployment submitted (revision ${revision.revision}, ${revision.status})`)
3846
5049
  );
5050
+ if (options.watch) {
5051
+ await watchDeployment(client, revision);
5052
+ }
3847
5053
  } catch (err) {
3848
5054
  if (err instanceof CliError) {
3849
5055
  console.error(chalk12.red(err.message));
@@ -3854,6 +5060,72 @@ async function deployCommand(options) {
3854
5060
  await cleanupStagingDir(stagingDir);
3855
5061
  }
3856
5062
  }
5063
+ function colorStatus(status) {
5064
+ switch (status) {
5065
+ case "active":
5066
+ return chalk12.green(status);
5067
+ case "failed":
5068
+ return chalk12.red(status);
5069
+ case "superseded":
5070
+ return chalk12.yellow(status);
5071
+ case "staged":
5072
+ return chalk12.cyan(status);
5073
+ case "draft":
5074
+ default:
5075
+ return chalk12.gray(status);
5076
+ }
5077
+ }
5078
+ async function watchDeployment(client, initial) {
5079
+ if (TERMINAL_STATUSES.has(initial.status)) {
5080
+ return;
5081
+ }
5082
+ console.log(chalk12.gray(`
5083
+ Watching revision ${initial.revision}\u2026`));
5084
+ let lastStatus = initial.status;
5085
+ const startedAt = Date.now();
5086
+ await watch(
5087
+ {
5088
+ poll: async () => {
5089
+ if (Date.now() - startedAt > WATCH_DEADLINE_MS) {
5090
+ throw new Error(
5091
+ `Deployment did not reach a terminal status within ${WATCH_DEADLINE_MS / 1e3}s.`
5092
+ );
5093
+ }
5094
+ const next = await client.deploymentStatus(
5095
+ client.projectId(),
5096
+ initial.revision
5097
+ );
5098
+ return next ? [next] : [];
5099
+ },
5100
+ pollIntervalMs: WATCH_POLL_INTERVAL_MS,
5101
+ getId: (rev) => `${rev.revision}:${rev.status}:${rev.updatedAt}`,
5102
+ isTerminal: (rev) => TERMINAL_STATUSES.has(rev.status)
5103
+ },
5104
+ {
5105
+ onEvent: (rev) => {
5106
+ if (rev.status !== lastStatus) {
5107
+ console.log(
5108
+ ` revision ${rev.revision} \u2192 ${colorStatus(rev.status)}`
5109
+ );
5110
+ lastStatus = rev.status;
5111
+ }
5112
+ }
5113
+ }
5114
+ );
5115
+ if (lastStatus === "active") {
5116
+ console.log(chalk12.green(`
5117
+ \u2713 Deployment active (revision ${initial.revision}).`));
5118
+ } else if (lastStatus === "failed") {
5119
+ console.log(chalk12.red(`
5120
+ \u2717 Deployment failed (revision ${initial.revision}).`));
5121
+ process.exit(1);
5122
+ } else if (lastStatus === "superseded") {
5123
+ console.log(
5124
+ chalk12.yellow(`
5125
+ ! Revision ${initial.revision} was superseded by a later deploy.`)
5126
+ );
5127
+ }
5128
+ }
3857
5129
  var SUCCESS_HTML = `<!DOCTYPE html>
3858
5130
  <html>
3859
5131
  <head><title>IO CLI</title><style>
@@ -4092,10 +5364,10 @@ async function whoamiCommand() {
4092
5364
  var CONFIG_FILE = "io.config.json";
4093
5365
  async function prompt(question) {
4094
5366
  const rl = createInterface({ input: process.stdin, output: process.stdout });
4095
- return new Promise((resolve4) => {
5367
+ return new Promise((resolve5) => {
4096
5368
  rl.question(question, (answer) => {
4097
5369
  rl.close();
4098
- resolve4(answer.trim());
5370
+ resolve5(answer.trim());
4099
5371
  });
4100
5372
  });
4101
5373
  }
@@ -4271,6 +5543,55 @@ async function projectsListCommand() {
4271
5543
  throw err;
4272
5544
  }
4273
5545
  }
5546
+ async function confirmDelete(projectName, projectId) {
5547
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
5548
+ return new Promise((resolve5) => {
5549
+ rl.question(
5550
+ `Delete project "${projectName}" (${projectId})? Type the project name to confirm: `,
5551
+ (answer) => {
5552
+ rl.close();
5553
+ resolve5(answer.trim() === projectName);
5554
+ }
5555
+ );
5556
+ });
5557
+ }
5558
+ async function projectsDeleteCommand(id, options = {}) {
5559
+ try {
5560
+ const config = await loadConfig();
5561
+ const client = createClient(config);
5562
+ const organizationId = options.organizationId ?? await resolveOrganizationId(client);
5563
+ const projects2 = await client.projects(organizationId);
5564
+ const target = projects2.find((p) => p.id === id);
5565
+ if (!target) {
5566
+ throw new ConfigError(
5567
+ `Project "${id}" not found under organization ${organizationId}.`
5568
+ );
5569
+ }
5570
+ if (!options.yes) {
5571
+ const confirmed = await confirmDelete(target.name, target.id);
5572
+ if (!confirmed) {
5573
+ console.log(chalk12.yellow("Delete cancelled (name did not match)."));
5574
+ return;
5575
+ }
5576
+ }
5577
+ const deleted = await client.deleteProject(organizationId, target.id);
5578
+ console.log(chalk12.green(`\u2713 Deleted project: ${deleted.name}`));
5579
+ console.log(chalk12.gray(` ID: ${deleted.id}`));
5580
+ if (config.projectId === deleted.id) {
5581
+ console.log(
5582
+ chalk12.yellow(
5583
+ "\n io.config.json still references this project \u2014 clear the projectId field before redeploying."
5584
+ )
5585
+ );
5586
+ }
5587
+ } catch (err) {
5588
+ if (err instanceof CliError) {
5589
+ console.error(chalk12.red(err.message));
5590
+ process.exit(1);
5591
+ }
5592
+ throw err;
5593
+ }
5594
+ }
4274
5595
  async function projectsCreateCommand(name) {
4275
5596
  try {
4276
5597
  const config = await loadConfig();
@@ -4495,8 +5816,9 @@ async function quotaCommand() {
4495
5816
  throw err;
4496
5817
  }
4497
5818
  }
4498
- Object.assign(globalThis, { WebSocket });
4499
- var ACTIVITY_SUBSCRIPTION = `
5819
+
5820
+ // src/api/subscription.ts
5821
+ var ACTIVITY_EVENT_SUBSCRIPTION = `
4500
5822
  subscription ActivityEventReceived($projectId: ID!, $input: ActivityEventFilterInput) {
4501
5823
  activityEventReceived(projectId: $projectId, input: $input) {
4502
5824
  id
@@ -4517,56 +5839,6 @@ var ACTIVITY_SUBSCRIPTION = `
4517
5839
  }
4518
5840
  }
4519
5841
  `;
4520
- function buildActivityEventsSocketUrl(apiUrl) {
4521
- const url = new URL(apiUrl);
4522
- const basePath = url.pathname.replace(/\/+$/, "");
4523
- const socketBase = basePath.endsWith("/graphql") ? basePath.slice(0, -"/graphql".length) : basePath;
4524
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
4525
- url.pathname = `${socketBase === "/" ? "" : socketBase}/socket`;
4526
- url.search = "";
4527
- url.hash = "";
4528
- return url.toString();
4529
- }
4530
- function subscribeToActivityEvents(options) {
4531
- if (!options.credentials.apiKey) {
4532
- throw new ConfigError(
4533
- "Live activity subscriptions require API key authentication on the current io_beam socket contract."
4534
- );
4535
- }
4536
- const phoenixSocket = new Socket(buildActivityEventsSocketUrl(options.apiUrl), {
4537
- params: socketAuthParams(options.credentials),
4538
- timeout: 1e4
4539
- });
4540
- const absintheSocket = AbsintheSocket.create(phoenixSocket);
4541
- const notifier = AbsintheSocket.send(absintheSocket, {
4542
- operation: ACTIVITY_SUBSCRIPTION,
4543
- variables: { projectId: options.projectId, input: options.input }
4544
- });
4545
- const observer = {
4546
- onAbort: (error) => {
4547
- options.onError?.(error);
4548
- },
4549
- onCancel: () => {
4550
- },
4551
- onError: (error) => {
4552
- options.onError?.(error);
4553
- },
4554
- onResult: (result) => {
4555
- const event = result.data?.activityEventReceived;
4556
- if (event) {
4557
- options.onEvent(event);
4558
- }
4559
- },
4560
- onStart: () => {
4561
- options.onConnected?.();
4562
- }
4563
- };
4564
- AbsintheSocket.observe(absintheSocket, notifier, observer);
4565
- phoenixSocket.connect();
4566
- return () => {
4567
- phoenixSocket.disconnect();
4568
- };
4569
- }
4570
5842
 
4571
5843
  // src/activity/presentation.ts
4572
5844
  var DISPLAY_NAME_NAMESPACES = /* @__PURE__ */ new Set([
@@ -4892,7 +6164,6 @@ var LEVEL_COLORS = {
4892
6164
  scheduled: chalk12.magenta
4893
6165
  };
4894
6166
  var FOLLOW_POLL_INTERVAL_MS = 2e3;
4895
- var FOLLOW_SEEN_ID_LIMIT = 1e3;
4896
6167
  var FOLLOW_BATCH_LIMIT = 200;
4897
6168
  function colorLevel(level) {
4898
6169
  if (!level) return chalk12.gray("\u2014");
@@ -4943,74 +6214,13 @@ function formatEvent(event) {
4943
6214
  detail ? chalk12.gray(detail) : ""
4944
6215
  ].join(" ").trimEnd();
4945
6216
  }
4946
- function latestOccurredAt(events, fallback) {
4947
- return events.reduce((latest, event) => {
4948
- return event.occurredAt > latest ? event.occurredAt : latest;
4949
- }, fallback);
4950
- }
4951
- function trimSeenIds(seenIds, order) {
4952
- while (order.length > FOLLOW_SEEN_ID_LIMIT) {
4953
- const evicted = order.shift();
4954
- if (evicted) {
4955
- seenIds.delete(evicted);
4956
- }
4957
- }
4958
- }
4959
- function orderEvents(events) {
4960
- return [...events].sort((left, right) => {
4961
- if (left.occurredAt === right.occurredAt) {
4962
- return left.id.localeCompare(right.id);
4963
- }
4964
- return left.occurredAt.localeCompare(right.occurredAt);
4965
- });
4966
- }
4967
- function wait(ms) {
4968
- return new Promise((resolve4) => {
4969
- setTimeout(resolve4, ms);
4970
- });
4971
- }
4972
- function followBatchInput(input, since) {
6217
+ function followPollInput(input, since) {
4973
6218
  return {
4974
6219
  ...input,
4975
6220
  since,
4976
6221
  limit: Math.max(input?.limit ?? 0, FOLLOW_BATCH_LIMIT)
4977
6222
  };
4978
6223
  }
4979
- async function drainFollowEvents(client, projectId, input, since, seenIds, seenOrder) {
4980
- let cursor = since;
4981
- const events = [];
4982
- for (let i = 0; i < 10; i += 1) {
4983
- const batch = orderEvents(
4984
- await client.projectEvents(projectId, followBatchInput(input, cursor))
4985
- );
4986
- const unseen = batch.filter((event) => !seenIds.has(event.id));
4987
- if (unseen.length === 0) {
4988
- return { events, since: cursor };
4989
- }
4990
- events.push(...unseen);
4991
- cursor = latestOccurredAt(unseen, cursor);
4992
- for (const event of unseen) {
4993
- seenIds.add(event.id);
4994
- seenOrder.push(event.id);
4995
- }
4996
- trimSeenIds(seenIds, seenOrder);
4997
- if (batch.length < FOLLOW_BATCH_LIMIT) {
4998
- return { events, since: cursor };
4999
- }
5000
- }
5001
- return { events, since: cursor };
5002
- }
5003
- function installSigintHandler(onExit) {
5004
- const handleSigint = () => {
5005
- console.log(chalk12.gray("\n Disconnected."));
5006
- onExit();
5007
- process.exit(0);
5008
- };
5009
- process.once("SIGINT", handleSigint);
5010
- return () => {
5011
- process.removeListener("SIGINT", handleSigint);
5012
- };
5013
- }
5014
6224
  async function logsCommand(options) {
5015
6225
  try {
5016
6226
  const config = await loadConfig();
@@ -5026,18 +6236,18 @@ async function logsCommand(options) {
5026
6236
  const since = parseSince(options.since);
5027
6237
  if (since) input.since = since;
5028
6238
  }
5029
- const events = await client.projectEvents(pid, input);
5030
- if (events.length === 0 && !options.follow) {
6239
+ const initialEvents = await client.projectEvents(pid, input);
6240
+ if (initialEvents.length === 0 && !options.follow) {
5031
6241
  console.log(chalk12.gray("No events found."));
5032
6242
  return;
5033
6243
  }
5034
- if (events.length > 0) {
6244
+ if (initialEvents.length > 0) {
5035
6245
  console.log(chalk12.bold(`
5036
- Activity Log \u2014 ${events.length} events
6246
+ Activity Log \u2014 ${initialEvents.length} events
5037
6247
  `));
5038
6248
  console.log(formatHeader());
5039
6249
  console.log(chalk12.gray(` ${"\u2500".repeat(92)}`));
5040
- for (const event of events) {
6250
+ for (const event of initialEvents) {
5041
6251
  console.log(formatEvent(event));
5042
6252
  }
5043
6253
  }
@@ -5047,7 +6257,7 @@ Activity Log \u2014 ${events.length} events
5047
6257
  client,
5048
6258
  pid,
5049
6259
  input,
5050
- events,
6260
+ initialEvents,
5051
6261
  followStartedAt ?? (/* @__PURE__ */ new Date()).toISOString()
5052
6262
  );
5053
6263
  } else {
@@ -5067,88 +6277,68 @@ async function tailEvents(apiUrl, client, projectId, input, initialEvents, follo
5067
6277
  throw new ConfigError("Not authenticated. Run `io login` first.");
5068
6278
  }
5069
6279
  console.log(chalk12.gray("\n Waiting for events... (Ctrl+C to stop)\n"));
5070
- if (creds.apiKey) {
5071
- return new Promise((_resolve, reject) => {
5072
- let disconnect;
5073
- const seenIds2 = new Set(initialEvents.map((event) => event.id));
5074
- const seenOrder2 = initialEvents.map((event) => event.id);
5075
- const removeSigintHandler2 = installSigintHandler(() => {
5076
- disconnect?.();
5077
- });
5078
- disconnect = subscribeToActivityEvents({
5079
- apiUrl,
5080
- credentials: creds,
5081
- projectId,
5082
- input,
6280
+ const initialSince = initialEvents.reduce(
6281
+ (latest, event) => event.occurredAt > latest ? event.occurredAt : latest,
6282
+ followStartedAt
6283
+ );
6284
+ const socketClient = createSocketClient({ apiUrl, credentials: creds });
6285
+ const subscriptionVars = input;
6286
+ let liveStarted = false;
6287
+ try {
6288
+ await watch(
6289
+ {
6290
+ subscribe: (onEvent, onError, onConnected) => {
6291
+ const unsubscribe = socketClient.subscribe(
6292
+ ACTIVITY_EVENT_SUBSCRIPTION,
6293
+ { projectId, input: subscriptionVars },
6294
+ {
6295
+ onStart: onConnected,
6296
+ onResult: (result) => {
6297
+ const event = result.data?.activityEventReceived;
6298
+ if (event) onEvent(event);
6299
+ },
6300
+ onError,
6301
+ onAbort: onError
6302
+ }
6303
+ );
6304
+ return { unsubscribe };
6305
+ },
6306
+ poll: async (since) => {
6307
+ return client.projectEvents(projectId, followPollInput(input, since));
6308
+ },
6309
+ pollIntervalMs: FOLLOW_POLL_INTERVAL_MS,
6310
+ initialSince,
6311
+ getId: (event) => event.id,
6312
+ getCursor: (event) => event.occurredAt,
6313
+ seenIdLimit: 1e3
6314
+ },
6315
+ {
5083
6316
  onEvent: (event) => {
5084
- if (seenIds2.has(event.id)) {
5085
- return;
6317
+ if (!liveStarted) {
6318
+ liveStarted = true;
5086
6319
  }
5087
6320
  console.log(formatEvent(event));
5088
- seenIds2.add(event.id);
5089
- seenOrder2.push(event.id);
5090
- trimSeenIds(seenIds2, seenOrder2);
5091
6321
  },
5092
- onConnected: () => {
5093
- console.log(chalk12.green(" \u25CF Connected \u2014 tailing live events\n"));
5094
- void drainFollowEvents(
5095
- client,
5096
- projectId,
5097
- input,
5098
- followStartedAt,
5099
- seenIds2,
5100
- seenOrder2
5101
- ).then(({ events }) => {
5102
- for (const event of events) {
5103
- console.log(formatEvent(event));
5104
- }
5105
- }).catch((error) => {
5106
- console.error(chalk12.red(`
5107
- Connection error: ${error.message}`));
5108
- removeSigintHandler2();
5109
- disconnect?.();
5110
- reject(error);
5111
- });
6322
+ onConnected: (mode) => {
6323
+ if (mode === "subscription") {
6324
+ console.log(chalk12.green(" \u25CF Connected \u2014 tailing live events\n"));
6325
+ } else {
6326
+ console.log(
6327
+ chalk12.green(
6328
+ " \u25CF Connected \u2014 polling for live events with your authenticated session\n"
6329
+ )
6330
+ );
6331
+ }
5112
6332
  },
5113
- onError: (error) => {
6333
+ onError: (err) => {
5114
6334
  console.error(chalk12.red(`
5115
- Connection error: ${error.message}`));
5116
- removeSigintHandler2();
5117
- disconnect?.();
5118
- reject(error);
6335
+ Connection error: ${err.message}`));
5119
6336
  }
5120
- });
5121
- });
5122
- }
5123
- console.log(
5124
- chalk12.green(" \u25CF Connected \u2014 polling for live events with your authenticated session\n")
5125
- );
5126
- const seenIds = new Set(initialEvents.map((event) => event.id));
5127
- const seenOrder = initialEvents.map((event) => event.id);
5128
- let since = latestOccurredAt(initialEvents, followStartedAt);
5129
- const removeSigintHandler = installSigintHandler(() => {
5130
- });
5131
- try {
5132
- for (; ; ) {
5133
- const batch = await drainFollowEvents(
5134
- client,
5135
- projectId,
5136
- input,
5137
- since,
5138
- seenIds,
5139
- seenOrder
5140
- );
5141
- const nextEvents = batch.events;
5142
- for (const event of nextEvents) {
5143
- console.log(formatEvent(event));
5144
- }
5145
- if (nextEvents.length > 0) {
5146
- since = batch.since;
5147
6337
  }
5148
- await wait(FOLLOW_POLL_INTERVAL_MS);
5149
- }
6338
+ );
6339
+ console.log(chalk12.gray("\n Disconnected."));
5150
6340
  } finally {
5151
- removeSigintHandler();
6341
+ socketClient.close();
5152
6342
  }
5153
6343
  }
5154
6344
  function parseSince(value) {
@@ -5181,7 +6371,7 @@ async function signalEmitCommand(name, options) {
5181
6371
  try {
5182
6372
  const config = await loadConfig({ projectId: options.projectId });
5183
6373
  const client = createClient(config);
5184
- const signal = normalizeRuntimeSignalName2(name);
6374
+ const signal = normalizeRuntimeSignalName(name);
5185
6375
  if (!signal) {
5186
6376
  throw new ConfigError(
5187
6377
  "signal name must be a bare event name like smoke.start or use the signal: namespace"
@@ -5223,19 +6413,268 @@ async function signalEmitCommand(name, options) {
5223
6413
  throw err;
5224
6414
  }
5225
6415
  }
6416
+ var POLL_INTERVAL_MS = 2e3;
6417
+ var POLL_BATCH_LIMIT = 200;
6418
+ function formatEvent2(event) {
6419
+ const time = new Date(event.occurredAt).toLocaleTimeString("en-GB", {
6420
+ hour12: false
6421
+ });
6422
+ const status = inferActivityEventStatus(event);
6423
+ const category = inferActivityEventCategory(event);
6424
+ const name = inferActivityEventName(event);
6425
+ const detail = inferActivityEventDetail(event) ?? "";
6426
+ return ` ${chalk12.gray(time)} ${chalk12.cyan(category)} ${name}${detail ? ` ${chalk12.gray(detail)}` : ""}${status ? ` ${chalk12.gray(`(${status})`)}` : ""}`;
6427
+ }
6428
+ async function reconcileCommand(options) {
6429
+ try {
6430
+ const config = await loadConfig();
6431
+ const client = createClient(config);
6432
+ const projectId = options.projectId ?? client.projectId();
6433
+ console.log(chalk12.gray(`Requesting reconciliation for project ${projectId}\u2026`));
6434
+ const accepted = await client.reconcileProject(projectId);
6435
+ if (!accepted) {
6436
+ throw new CliError(
6437
+ "Runtime rejected the reconcile request.",
6438
+ "RECONCILE_REJECTED"
6439
+ );
6440
+ }
6441
+ console.log(chalk12.green("\u2713 Reconcile requested."));
6442
+ if (!options.watch) {
6443
+ return;
6444
+ }
6445
+ const creds = await loadCredentials();
6446
+ if (!creds?.token) {
6447
+ throw new ConfigError("Not authenticated. Run `io login` first.");
6448
+ }
6449
+ console.log(
6450
+ chalk12.gray("\nTailing activity for this project (Ctrl+C to stop)\u2026\n")
6451
+ );
6452
+ const socketClient = createSocketClient({
6453
+ apiUrl: config.apiUrl,
6454
+ credentials: creds
6455
+ });
6456
+ const initialSince = (/* @__PURE__ */ new Date()).toISOString();
6457
+ try {
6458
+ await watch(
6459
+ {
6460
+ subscribe: (onEvent, onError, onConnected) => {
6461
+ const unsubscribe = socketClient.subscribe(
6462
+ ACTIVITY_EVENT_SUBSCRIPTION,
6463
+ { projectId, input: { limit: POLL_BATCH_LIMIT } },
6464
+ {
6465
+ onStart: onConnected,
6466
+ onResult: (result) => {
6467
+ const event = result.data?.activityEventReceived;
6468
+ if (event) onEvent(event);
6469
+ },
6470
+ onError,
6471
+ onAbort: onError
6472
+ }
6473
+ );
6474
+ return { unsubscribe };
6475
+ },
6476
+ poll: async (since) => {
6477
+ return client.projectEvents(projectId, {
6478
+ since,
6479
+ limit: POLL_BATCH_LIMIT
6480
+ });
6481
+ },
6482
+ pollIntervalMs: POLL_INTERVAL_MS,
6483
+ initialSince,
6484
+ getId: (event) => event.id,
6485
+ getCursor: (event) => event.occurredAt
6486
+ },
6487
+ {
6488
+ onEvent: (event) => console.log(formatEvent2(event)),
6489
+ onConnected: (mode) => {
6490
+ if (mode === "subscription") {
6491
+ console.log(chalk12.green(" \u25CF Connected \u2014 tailing live events\n"));
6492
+ } else {
6493
+ console.log(
6494
+ chalk12.green(
6495
+ " \u25CF Connected \u2014 polling for live events with your authenticated session\n"
6496
+ )
6497
+ );
6498
+ }
6499
+ }
6500
+ }
6501
+ );
6502
+ console.log(chalk12.gray("\nDisconnected."));
6503
+ } finally {
6504
+ socketClient.close();
6505
+ }
6506
+ } catch (err) {
6507
+ if (err instanceof CliError) {
6508
+ console.error(chalk12.red(err.message));
6509
+ process.exit(1);
6510
+ }
6511
+ throw err;
6512
+ }
6513
+ }
6514
+ function isRecord4(value) {
6515
+ return !!value && typeof value === "object" && !Array.isArray(value);
6516
+ }
6517
+ function isCatalogShape2(value) {
6518
+ if (!isRecord4(value)) return false;
6519
+ return value.contract_version === "io.invocation.v2" && value.snapshot_version === "io_runtime.snapshot.v2" && typeof value.catalog_hash === "string" && isRecord4(value.resources) && isRecord4(value.signals) && isRecord4(value.tools) && isRecord4(value.schemas);
6520
+ }
6521
+ async function readLocalCatalog(buildDir) {
6522
+ const path = resolve(process.cwd(), buildDir, "catalog.json");
6523
+ let raw;
6524
+ try {
6525
+ raw = await readFile(path, "utf-8");
6526
+ } catch (err) {
6527
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
6528
+ throw new CliError(
6529
+ `Local catalog not found at ${path}. Run \`io build\` or \`io deploy\` first.`,
6530
+ "CATALOG_NOT_FOUND"
6531
+ );
6532
+ }
6533
+ throw err;
6534
+ }
6535
+ let parsed;
6536
+ try {
6537
+ parsed = JSON.parse(raw);
6538
+ } catch (err) {
6539
+ throw new CliError(
6540
+ `Local catalog at ${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
6541
+ "CATALOG_INVALID_JSON"
6542
+ );
6543
+ }
6544
+ if (!isCatalogShape2(parsed)) {
6545
+ throw new CliError(
6546
+ `Local catalog at ${path} does not match the io.invocation.v2 catalog shape.`,
6547
+ "CATALOG_SHAPE_MISMATCH"
6548
+ );
6549
+ }
6550
+ return parsed;
6551
+ }
6552
+ function describeExecutor(executor) {
6553
+ switch (executor.kind) {
6554
+ case "platform_tool":
6555
+ return `${executor.provider_kind}.${executor.backend_kind}.${executor.operation}`;
6556
+ case "stream_publish":
6557
+ return `${executor.provider_kind}.${executor.backend_kind} \u2192 ${executor.destination_ref.slice(0, 19)}\u2026`;
6558
+ case "webhook_response":
6559
+ return "webhook response";
6560
+ case "http_request":
6561
+ return `${executor.method} ${executor.url}`;
6562
+ case "graphql_request":
6563
+ return `POST ${executor.endpoint}${executor.operation_name ? ` (${executor.operation_name})` : ""}`;
6564
+ case "http_operation":
6565
+ return `${executor.method} ${executor.base_url}${executor.path}`;
6566
+ case "graphql_operation":
6567
+ return `POST ${executor.endpoint}${executor.operation_name ? ` (${executor.operation_name})` : ""}`;
6568
+ case "unsupported":
6569
+ return chalk12.red(`unsupported (${executor.reason})`);
6570
+ }
6571
+ }
6572
+ function formatCatalog(catalog2, label) {
6573
+ const lines = [];
6574
+ lines.push(chalk12.bold(`
6575
+ Catalog (${label})`));
6576
+ lines.push(
6577
+ ` ${chalk12.gray("contract_version:")} ${catalog2.contract_version}`
6578
+ );
6579
+ lines.push(
6580
+ ` ${chalk12.gray("snapshot_version:")} ${catalog2.snapshot_version}`
6581
+ );
6582
+ lines.push(` ${chalk12.gray("catalog_hash: ")} ${catalog2.catalog_hash}`);
6583
+ lines.push("");
6584
+ lines.push(chalk12.bold(" Counts"));
6585
+ lines.push(` resources: ${Object.keys(catalog2.resources).length}`);
6586
+ lines.push(` signals: ${Object.keys(catalog2.signals).length}`);
6587
+ lines.push(` tools: ${Object.keys(catalog2.tools).length}`);
6588
+ lines.push(` schemas: ${Object.keys(catalog2.schemas).length}`);
6589
+ const resourceIds = Object.keys(catalog2.resources).sort();
6590
+ if (resourceIds.length > 0) {
6591
+ lines.push("");
6592
+ lines.push(chalk12.bold(" Resources"));
6593
+ for (const id of resourceIds) {
6594
+ const resource = catalog2.resources[id];
6595
+ lines.push(
6596
+ ` ${chalk12.cyan(resource.id)} ${chalk12.gray(
6597
+ `${resource.resource_kind}/${resource.backend_kind} scope=${resource.binding_scope} cred=${resource.credential_source}`
6598
+ )}`
6599
+ );
6600
+ for (const signalId of resource.signals) {
6601
+ const signal = catalog2.signals[signalId];
6602
+ if (!signal) continue;
6603
+ lines.push(
6604
+ ` ${chalk12.magenta("signal")} ${signal.name} ${chalk12.gray(
6605
+ `(${signal.mode}, scope=${signal.scope})`
6606
+ )}`
6607
+ );
6608
+ }
6609
+ for (const toolId of resource.tools) {
6610
+ const tool = catalog2.tools[toolId];
6611
+ if (!tool) continue;
6612
+ lines.push(
6613
+ ` ${chalk12.yellow("tool ")} ${tool.name} ${chalk12.gray(
6614
+ `(${tool.effect}, audit=${tool.audit_requirement}) ${describeExecutor(tool.executor)}`
6615
+ )}`
6616
+ );
6617
+ }
6618
+ }
6619
+ }
6620
+ lines.push("");
6621
+ return lines.join("\n");
6622
+ }
6623
+ async function catalogShowCommand(options) {
6624
+ try {
6625
+ let catalog2;
6626
+ let label;
6627
+ if (options.remote) {
6628
+ const config = await loadConfig();
6629
+ if (!config.apiUrl || !config.projectId) {
6630
+ throw new CliError(
6631
+ "No apiUrl or projectId configured. Run `io login` and set a project first.",
6632
+ "CONFIG_MISSING"
6633
+ );
6634
+ }
6635
+ const client = createClient(config);
6636
+ const remote = await client.fetchRemoteCatalog(client.projectId());
6637
+ if (!remote) {
6638
+ console.log(
6639
+ chalk12.gray("No active deployment catalog for this project yet.")
6640
+ );
6641
+ return;
6642
+ }
6643
+ catalog2 = remote;
6644
+ label = `remote: project ${client.projectId()}`;
6645
+ } else {
6646
+ const buildDir = options.buildDir ?? ".io";
6647
+ catalog2 = await readLocalCatalog(buildDir);
6648
+ label = `local: ${join(buildDir, "catalog.json")}`;
6649
+ }
6650
+ if (options.json) {
6651
+ process.stdout.write(stableStringify(catalog2, 2));
6652
+ process.stdout.write("\n");
6653
+ return;
6654
+ }
6655
+ process.stdout.write(formatCatalog(catalog2, label));
6656
+ } catch (err) {
6657
+ if (err instanceof CliError) {
6658
+ console.error(chalk12.red(err.message));
6659
+ process.exit(1);
6660
+ }
6661
+ throw err;
6662
+ }
6663
+ }
5226
6664
 
5227
6665
  // src/index.ts
5228
6666
  var program = new Command();
5229
- program.name("io").description("IO CLI \u2014 build and deploy behaviors").version("0.1.15");
6667
+ program.name("io").description("IO CLI \u2014 build and deploy behaviors").version("0.1.16");
5230
6668
  program.command("init").description("Initialize io.config.json for the current project").option("--yes", "Overwrite existing config without prompting").action(initCommand);
5231
6669
  program.command("build").description("Build behaviors from the io/ directory").option("--dir <path>", "Source directory", "io").option("--minify", "Minify bundles", false).action(buildCommand);
5232
- program.command("deploy").description("Build and package for deployment").option("--dir <path>", "Source directory", "io").option("--no-minify", "Skip minification").option("--yes", "Skip confirmation prompt").action(deployCommand);
6670
+ program.command("deploy").description("Build and package for deployment").option("--dir <path>", "Source directory", "io").option("--no-minify", "Skip minification").option("--yes", "Skip confirmation prompt").option("--watch", "Tail deployment status until it reaches a terminal state").action(deployCommand);
5233
6671
  program.command("login").description("Authenticate with the IO platform").option("--token <value>", "API token for CI/CD environments").action(loginCommand);
5234
6672
  program.command("logout").description("Remove stored credentials").action(logoutCommand);
5235
6673
  program.command("whoami").description("Show current authentication status").action(whoamiCommand);
5236
6674
  var projects = program.command("projects").description("Manage projects");
5237
6675
  projects.command("list").description("List all projects").action(projectsListCommand);
5238
6676
  projects.command("create <name>").description("Create a new project").action(projectsCreateCommand);
6677
+ projects.command("delete <id>").description("Delete a project (requires typing the project name)").option("--yes", "Skip the name confirmation prompt").option("--organization-id <id>", "Organization that owns the project").action(projectsDeleteCommand);
5239
6678
  var secrets = program.command("secrets").description("Manage project secrets");
5240
6679
  secrets.command("set <name> <value>").description("Set a secret value").option("--target <targets...>", "Restrict to specific targets").action(secretsSetCommand);
5241
6680
  secrets.command("list").description("List all secrets").action(secretsListCommand);
@@ -5249,6 +6688,9 @@ var signals = program.command("signals").description("Emit runtime signals");
5249
6688
  signals.command("emit <name>").description("Emit a signal into the active project's event stream").option("--payload <json>", "Signal payload as a JSON object", "{}").option("--metadata <json>", "Signal metadata as a JSON object", "{}").option("--project-id <id>", "Override the configured project ID").requiredOption("--principal-id <id>", "Project principal ID for the emitted signal").option("--correlation-id <id>", "Correlation ID to attach to the signal").option("--causation-id <id>", "Causation ID to attach to the signal").option("--idempotency-key <key>", "Optional idempotency key").option("--source-name <name>", "Optional source name for the emitted signal").option("--json", "Print the raw emitted event as JSON", false).action(signalEmitCommand);
5250
6689
  program.command("quota").description("Show current quota usage for the active organization").action(quotaCommand);
5251
6690
  program.command("logs").description("Show recent activity events").option("--limit <count>", "Number of events to show", "50").option("--type <types...>", "Filter by event types").option("--since <duration>", "Show events since (e.g. 15m, 1h, 7d)").option("--follow", "Tail events in real-time").action(logsCommand);
6691
+ program.command("reconcile").description("Force runtime to reconcile resources for the active project").option("--project-id <id>", "Override the configured project ID").option("--watch", "Tail activity events after the reconcile request").action(reconcileCommand);
6692
+ var catalog = program.command("catalog").description("Inspect the v2 deployment catalog");
6693
+ catalog.command("show").description("Print the local or remote deployment catalog").option("--remote", "Show the active deployment catalog from runtime").option("--json", "Print the raw catalog JSON").option("--build-dir <path>", "Local build directory", ".io").action(catalogShowCommand);
5252
6694
  program.parse();
5253
6695
  //# sourceMappingURL=index.js.map
5254
6696
  //# sourceMappingURL=index.js.map