@kaelen-ai/cli 0.1.15 → 0.1.17

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) {
@@ -1000,11 +1231,14 @@ function validateToolCapability(appName, resourceName, tool, errors, seenCapabil
1000
1231
  errors.push(`tool "${expected}" capability mode does not match the tool mode`);
1001
1232
  }
1002
1233
  const audit = stringField2(capability, "audit");
1003
- if (mode === "write" && audit !== "sync") {
1004
- errors.push(`write tool "${expected}" must use sync audit`);
1234
+ if (audit !== "finish_only" && audit !== "pre_call") {
1235
+ errors.push(`tool "${expected}" capability audit must be finish_only or pre_call`);
1005
1236
  }
1006
- if (mode === "read" && audit !== "async") {
1007
- errors.push(`read tool "${expected}" must use async audit`);
1237
+ if (mode === "write" && audit !== "pre_call") {
1238
+ errors.push(`write tool "${expected}" must use pre_call audit`);
1239
+ }
1240
+ if (mode === "read" && audit !== "finish_only") {
1241
+ errors.push(`read tool "${expected}" must use finish_only audit`);
1008
1242
  }
1009
1243
  const exposure = stringField2(capability, "credential_exposure");
1010
1244
  if (exposure !== "none" && exposure !== "stdlib") {
@@ -1056,11 +1290,11 @@ function validateManifest(manifest) {
1056
1290
  const root = isRecord2(manifest) ? manifest : {};
1057
1291
  const errors = [];
1058
1292
  const seenCapabilities = /* @__PURE__ */ new Set();
1059
- if (root.contract_version !== "io.invocation.v1") {
1060
- errors.push("contract_version must be io.invocation.v1");
1293
+ if (root.contract_version !== "io.invocation.v2") {
1294
+ errors.push("contract_version must be io.invocation.v2");
1061
1295
  }
1062
- if (root.snapshot_version !== "io_runtime.snapshot.v1") {
1063
- errors.push("snapshot_version must be io_runtime.snapshot.v1");
1296
+ if (root.snapshot_version !== "io_runtime.snapshot.v2") {
1297
+ errors.push("snapshot_version must be io_runtime.snapshot.v2");
1064
1298
  }
1065
1299
  const secretBundles = arrayField(root, "secret_bundles").filter(isRecord2);
1066
1300
  duplicateNames(secretBundles, "secret bundle", errors);
@@ -1075,6 +1309,14 @@ function validateManifest(manifest) {
1075
1309
  const secretNames = new Set(
1076
1310
  secretBundles.map((entry) => stringField2(entry, "name")).filter((name) => !!name)
1077
1311
  );
1312
+ const secretBundleSlots = /* @__PURE__ */ new Map();
1313
+ for (const bundle of secretBundles) {
1314
+ const bundleName = stringField2(bundle, "name");
1315
+ if (!bundleName) continue;
1316
+ const slots = bundle.slots;
1317
+ if (!isRecord2(slots)) continue;
1318
+ secretBundleSlots.set(bundleName, new Set(Object.keys(slots)));
1319
+ }
1078
1320
  const declaredEndpoints = validateEndpointsSection(
1079
1321
  arrayField(root, "endpoints"),
1080
1322
  errors
@@ -1121,6 +1363,17 @@ function validateManifest(manifest) {
1121
1363
  } else {
1122
1364
  validateResourceCapability(appName, resourceName, capability, errors);
1123
1365
  }
1366
+ const resourceKindForConfig = stringField2(resource, "kind") ?? "";
1367
+ validateHttpLikeConfig(
1368
+ appName,
1369
+ resourceName,
1370
+ resourceKindForConfig,
1371
+ resource.config,
1372
+ secretNames,
1373
+ secretBundle,
1374
+ secretBundleSlots,
1375
+ errors
1376
+ );
1124
1377
  const tools = toolEntries.filter(isRecord2);
1125
1378
  duplicateNames(tools, `resource "${appName}/${resourceName}" tool`, errors);
1126
1379
  for (const tool of tools) {
@@ -1588,6 +1841,12 @@ var RUNTIME_MEMORY_SIGNALS = {
1588
1841
  onTrajectoryDeteriorated: "knowledge:trajectory.deteriorated",
1589
1842
  onRoutineBroken: "knowledge:routine.broken"
1590
1843
  };
1844
+ var RUNTIME_SDK_PASSTHROUGH_EXPORTS = /* @__PURE__ */ new Set([
1845
+ "google",
1846
+ "microsoft",
1847
+ "httpApi",
1848
+ "graphqlApi"
1849
+ ]);
1591
1850
  var RUNTIME_SDK_EXPORTS = /* @__PURE__ */ new Set([
1592
1851
  "ioSchema",
1593
1852
  "zodSchema",
@@ -1608,7 +1867,8 @@ var RUNTIME_SDK_EXPORTS = /* @__PURE__ */ new Set([
1608
1867
  "graphql",
1609
1868
  "http",
1610
1869
  "calendar",
1611
- "contacts"
1870
+ "contacts",
1871
+ ...RUNTIME_SDK_PASSTHROUGH_EXPORTS
1612
1872
  ]);
1613
1873
  var COMPILED_AWAY_EXPORTS = /* @__PURE__ */ new Set(["behavior", "conversation"]);
1614
1874
  function normalizeSdkSpecifier(specifier) {
@@ -1870,6 +2130,29 @@ ${entries.map(([key, value]) => ` ${key}: Object.freeze({ name: ${JSON.stringif
1870
2130
  }`
1871
2131
  );
1872
2132
  }
2133
+ const needsPassthroughHelper = [...RUNTIME_SDK_PASSTHROUGH_EXPORTS].some(
2134
+ (name) => usage.exports.has(name)
2135
+ );
2136
+ if (needsPassthroughHelper) {
2137
+ lines.push(
2138
+ `function passthroughFrozen(options) {
2139
+ if (options && typeof options === "object" && !Array.isArray(options)) {
2140
+ return Object.freeze({ ...options });
2141
+ }
2142
+ return Object.freeze({});
2143
+ }`
2144
+ );
2145
+ for (const passthrough of RUNTIME_SDK_PASSTHROUGH_EXPORTS) {
2146
+ if (!usage.exports.has(passthrough)) {
2147
+ continue;
2148
+ }
2149
+ lines.push(
2150
+ `export function ${passthrough}(options) {
2151
+ return passthroughFrozen(options);
2152
+ }`
2153
+ );
2154
+ }
2155
+ }
1873
2156
  return lines.join("\n\n");
1874
2157
  }
1875
2158
  function sdkAliasPlugin() {
@@ -2208,64 +2491,513 @@ async function generateManifest(input) {
2208
2491
  );
2209
2492
  return m;
2210
2493
  }
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
- });
2494
+
2495
+ // src/build/stable-json.ts
2496
+ function stableStringify(value, indent) {
2497
+ return JSON.stringify(canonicalize(value), null, indent);
2226
2498
  }
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
- );
2499
+ function canonicalize(value) {
2500
+ if (value === null) return null;
2501
+ if (Array.isArray(value)) {
2502
+ return value.map(canonicalize);
2235
2503
  }
2236
- const body = ast.body;
2237
- const stmt = body.find(
2238
- (candidate) => candidate.start === statementStart
2239
- );
2240
- if (stmt) {
2241
- return stmt;
2504
+ if (typeof value === "object") {
2505
+ const obj = value;
2506
+ const sorted = {};
2507
+ for (const key of Object.keys(obj).sort()) {
2508
+ sorted[key] = canonicalize(obj[key]);
2509
+ }
2510
+ return sorted;
2242
2511
  }
2243
- const fallback = body.find((candidate) => {
2244
- if (candidate.type !== "ExpressionStatement") {
2245
- return false;
2512
+ return value;
2513
+ }
2514
+
2515
+ // src/build/catalog.ts
2516
+ var CATALOG_CONTRACT_VERSION = "io.catalog.v1";
2517
+ var CATALOG_SNAPSHOT_VERSION = "io_runtime.snapshot.v2";
2518
+ var WEBHOOK_RESOURCE_KIND = "webhook";
2519
+ var PLATFORM_TOOL_KINDS = /* @__PURE__ */ new Set(["email", "calendar", "contacts"]);
2520
+ var STREAM_PUBLISH_KINDS = /* @__PURE__ */ new Set([
2521
+ "kafka",
2522
+ "rabbitmq",
2523
+ "redis",
2524
+ "pubsub",
2525
+ "sqs",
2526
+ "websocket"
2527
+ ]);
2528
+ function sha256Hex(value) {
2529
+ return `sha256:${createHash("sha256").update(stableStringify(value)).digest("hex")}`;
2530
+ }
2531
+ function isRecord3(value) {
2532
+ return !!value && typeof value === "object" && !Array.isArray(value);
2533
+ }
2534
+ function getString(record, key) {
2535
+ const value = record[key];
2536
+ return typeof value === "string" ? value : void 0;
2537
+ }
2538
+ function getRecord(record, key) {
2539
+ const value = record[key];
2540
+ return isRecord3(value) ? value : void 0;
2541
+ }
2542
+ function getArray(record, key) {
2543
+ const value = record[key];
2544
+ return Array.isArray(value) ? value : [];
2545
+ }
2546
+ function getNumber(record, key) {
2547
+ const value = record[key];
2548
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2549
+ }
2550
+ function countPlaceholders2(format) {
2551
+ let count = 0;
2552
+ let idx = 0;
2553
+ while ((idx = format.indexOf("{}", idx)) !== -1) {
2554
+ count++;
2555
+ idx += 2;
2556
+ }
2557
+ return count;
2558
+ }
2559
+ function deriveAuditRequirement(effect) {
2560
+ return effect === "write" ? "pre_call" : "finish_only";
2561
+ }
2562
+ function deriveEffect(toolMode) {
2563
+ return toolMode === "read" ? "read" : "write";
2564
+ }
2565
+ function normalizeCredentialHeaders(declared, resourceId, context, warnings) {
2566
+ if (!Array.isArray(declared)) return [];
2567
+ const out = [];
2568
+ for (const entry of declared) {
2569
+ if (!isRecord3(entry)) {
2570
+ warnings.push(`${context}: skipped non-object credential header entry`);
2571
+ continue;
2246
2572
  }
2247
- const expression = candidate.expression;
2248
- if (expression.type !== "CallExpression") {
2249
- return false;
2573
+ const name = getString(entry, "name");
2574
+ if (!name) {
2575
+ warnings.push(`${context}: credential header missing name`);
2576
+ continue;
2250
2577
  }
2251
- const args = expression.arguments;
2252
- if (args.length < 2 || args[0]?.type !== "Literal" || args[1]?.type !== "ObjectExpression") {
2253
- return false;
2578
+ const source = getRecord(entry, "source");
2579
+ if (!source) {
2580
+ warnings.push(`${context}: credential header "${name}" missing source`);
2581
+ continue;
2254
2582
  }
2255
- const callee = expression.callee;
2256
- if (callee.type !== "Identifier") {
2257
- return false;
2583
+ const sourceKind = getString(source, "kind");
2584
+ let normalizedSource;
2585
+ if (sourceKind === "credential") {
2586
+ const field = getString(source, "field");
2587
+ if (!field) {
2588
+ warnings.push(
2589
+ `${context}: credential header "${name}" credential source missing field`
2590
+ );
2591
+ continue;
2592
+ }
2593
+ normalizedSource = { kind: "credential", resource_id: resourceId, field };
2594
+ } else if (sourceKind === "rawSecret" || sourceKind === "raw_secret") {
2595
+ const secretName = getString(source, "name");
2596
+ if (!secretName) {
2597
+ warnings.push(
2598
+ `${context}: credential header "${name}" raw_secret source missing name`
2599
+ );
2600
+ continue;
2601
+ }
2602
+ normalizedSource = { kind: "raw_secret", name: secretName };
2603
+ } else {
2604
+ warnings.push(
2605
+ `${context}: credential header "${name}" has unsupported source kind "${sourceKind}"`
2606
+ );
2607
+ continue;
2258
2608
  }
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
- );
2609
+ const format = getString(entry, "format") ?? "{}";
2610
+ if (countPlaceholders2(format) !== 1) {
2611
+ warnings.push(
2612
+ `${context}: credential header "${name}" format must contain exactly one {} placeholder`
2613
+ );
2614
+ continue;
2615
+ }
2616
+ out.push({ name, source: normalizedSource, format });
2265
2617
  }
2266
- return fallback;
2618
+ return out;
2267
2619
  }
2268
- function rootSpecNode(stmt) {
2620
+ function normalizeRequestMapping(declared) {
2621
+ if (!isRecord3(declared)) {
2622
+ return { path_params: [], query: [] };
2623
+ }
2624
+ const pathParams = collectMappingValues(declared["pathParams"]);
2625
+ const query = collectMappingValues(declared["query"]);
2626
+ const body = declared["body"];
2627
+ const bodyValue = typeof body === "string" ? body : body === null ? null : void 0;
2628
+ return {
2629
+ path_params: pathParams,
2630
+ query,
2631
+ ...bodyValue !== void 0 ? { body: bodyValue } : {}
2632
+ };
2633
+ }
2634
+ function collectMappingValues(value) {
2635
+ if (!Array.isArray(value)) return [];
2636
+ const out = [];
2637
+ for (const entry of value) {
2638
+ if (typeof entry === "string" && entry.trim()) {
2639
+ out.push(entry);
2640
+ } else if (isRecord3(entry)) {
2641
+ const from = getString(entry, "from");
2642
+ if (!from?.trim()) continue;
2643
+ const alias = getString(entry, "as");
2644
+ out.push(alias?.trim() ? { from, as: alias } : from);
2645
+ }
2646
+ }
2647
+ return out;
2648
+ }
2649
+ function buildExecutor(ctx) {
2650
+ if (PLATFORM_TOOL_KINDS.has(ctx.resourceKind)) {
2651
+ return {
2652
+ kind: "platform_tool",
2653
+ provider_kind: ctx.providerKind,
2654
+ backend_kind: ctx.backendKind,
2655
+ operation: ctx.toolName
2656
+ };
2657
+ }
2658
+ if (ctx.resourceKind === WEBHOOK_RESOURCE_KIND && ctx.toolName === "respond") {
2659
+ return { kind: "webhook_response" };
2660
+ }
2661
+ if (STREAM_PUBLISH_KINDS.has(ctx.resourceKind)) {
2662
+ return {
2663
+ kind: "stream_publish",
2664
+ provider_kind: ctx.providerKind,
2665
+ backend_kind: ctx.backendKind,
2666
+ destination_ref: ctx.configHash
2667
+ };
2668
+ }
2669
+ if (ctx.resourceKind === "http") {
2670
+ return buildHttpRequestExecutor(ctx);
2671
+ }
2672
+ if (ctx.resourceKind === "graphql") {
2673
+ return buildGraphQlRequestExecutor(ctx);
2674
+ }
2675
+ if (ctx.resourceKind === "httpApi") {
2676
+ return buildHttpOperationExecutor(ctx);
2677
+ }
2678
+ if (ctx.resourceKind === "graphqlApi") {
2679
+ return buildGraphQlOperationExecutor(ctx);
2680
+ }
2681
+ return {
2682
+ kind: "unsupported",
2683
+ reason: `unknown_resource_kind:${ctx.resourceKind}`
2684
+ };
2685
+ }
2686
+ function buildHttpRequestExecutor(ctx) {
2687
+ const config = ctx.resourceConfig;
2688
+ const url = getString(config, "url") ?? "";
2689
+ const method = (getString(config, "method") ?? "GET").toUpperCase();
2690
+ const headers = getRecord(config, "headers") ?? {};
2691
+ const queryParams = getRecord(config, "queryParams") ?? {};
2692
+ const body = config["body"];
2693
+ const credentialHeaders = normalizeCredentialHeaders(
2694
+ config["credentialHeaders"],
2695
+ ctx.resourceId,
2696
+ `tool "${ctx.resourceId}.${ctx.toolName}"`,
2697
+ ctx.warnings
2698
+ );
2699
+ return {
2700
+ kind: "http_request",
2701
+ url,
2702
+ method,
2703
+ headers_ref: ctx.registerBlob(headers),
2704
+ credential_headers: credentialHeaders,
2705
+ query_params_ref: ctx.registerBlob(queryParams),
2706
+ body_ref: ctx.registerBlob(body ?? null),
2707
+ timeout_seconds: getNumber(config, "timeoutSeconds") ?? null
2708
+ };
2709
+ }
2710
+ function buildGraphQlRequestExecutor(ctx) {
2711
+ const config = ctx.resourceConfig;
2712
+ const endpoint = getString(config, "endpoint") ?? "";
2713
+ const query = getString(config, "query") ?? "";
2714
+ const headers = getRecord(config, "headers") ?? {};
2715
+ const variables = getRecord(config, "variables") ?? {};
2716
+ const operationName = getString(config, "operationName") ?? null;
2717
+ const credentialHeaders = normalizeCredentialHeaders(
2718
+ config["credentialHeaders"],
2719
+ ctx.resourceId,
2720
+ `tool "${ctx.resourceId}.${ctx.toolName}"`,
2721
+ ctx.warnings
2722
+ );
2723
+ return {
2724
+ kind: "graphql_request",
2725
+ endpoint,
2726
+ query_ref: ctx.registerBlob(query),
2727
+ operation_name: operationName,
2728
+ variables_ref: ctx.registerBlob(variables),
2729
+ headers_ref: ctx.registerBlob(headers),
2730
+ credential_headers: credentialHeaders,
2731
+ timeout_seconds: getNumber(config, "timeoutSeconds") ?? null
2732
+ };
2733
+ }
2734
+ function buildHttpOperationExecutor(ctx) {
2735
+ const config = ctx.resourceConfig;
2736
+ const ops = getRecord(config, "operations") ?? {};
2737
+ const opSpec = getRecord(ops, ctx.toolName);
2738
+ if (!opSpec) {
2739
+ ctx.warnings.push(
2740
+ `tool "${ctx.resourceId}.${ctx.toolName}" has no matching operation in resource config`
2741
+ );
2742
+ return {
2743
+ kind: "unsupported",
2744
+ reason: `httpApi_missing_op:${ctx.toolName}`
2745
+ };
2746
+ }
2747
+ const baseUrl = getString(config, "baseUrl") ?? "";
2748
+ const resourceHeaders = getRecord(config, "headers") ?? {};
2749
+ const opHeaders = getRecord(opSpec, "headers") ?? {};
2750
+ const mergedHeaders = { ...resourceHeaders, ...opHeaders };
2751
+ const opCredentialHeaders = opSpec["credentialHeaders"];
2752
+ const resourceCredentialHeaders = config["credentialHeaders"];
2753
+ const credentialHeaders = normalizeCredentialHeaders(
2754
+ opCredentialHeaders ?? resourceCredentialHeaders,
2755
+ ctx.resourceId,
2756
+ `tool "${ctx.resourceId}.${ctx.toolName}"`,
2757
+ ctx.warnings
2758
+ );
2759
+ const method = (getString(opSpec, "method") ?? "GET").toUpperCase();
2760
+ const path = getString(opSpec, "path") ?? "";
2761
+ const timeoutSeconds = getNumber(opSpec, "timeoutSeconds") ?? getNumber(config, "timeoutSeconds") ?? null;
2762
+ return {
2763
+ kind: "http_operation",
2764
+ base_url: baseUrl,
2765
+ method,
2766
+ path,
2767
+ headers_ref: ctx.registerBlob(mergedHeaders),
2768
+ credential_headers: credentialHeaders,
2769
+ timeout_seconds: timeoutSeconds,
2770
+ request_mapping: normalizeRequestMapping(opSpec["requestMapping"])
2771
+ };
2772
+ }
2773
+ function buildGraphQlOperationExecutor(ctx) {
2774
+ const config = ctx.resourceConfig;
2775
+ const ops = getRecord(config, "operations") ?? {};
2776
+ const opSpec = getRecord(ops, ctx.toolName);
2777
+ if (!opSpec) {
2778
+ ctx.warnings.push(
2779
+ `tool "${ctx.resourceId}.${ctx.toolName}" has no matching operation in resource config`
2780
+ );
2781
+ return {
2782
+ kind: "unsupported",
2783
+ reason: `graphqlApi_missing_op:${ctx.toolName}`
2784
+ };
2785
+ }
2786
+ const endpoint = getString(config, "endpoint") ?? "";
2787
+ const resourceHeaders = getRecord(config, "headers") ?? {};
2788
+ const opHeaders = getRecord(opSpec, "headers") ?? {};
2789
+ const mergedHeaders = { ...resourceHeaders, ...opHeaders };
2790
+ const opCredentialHeaders = opSpec["credentialHeaders"];
2791
+ const resourceCredentialHeaders = config["credentialHeaders"];
2792
+ const credentialHeaders = normalizeCredentialHeaders(
2793
+ opCredentialHeaders ?? resourceCredentialHeaders,
2794
+ ctx.resourceId,
2795
+ `tool "${ctx.resourceId}.${ctx.toolName}"`,
2796
+ ctx.warnings
2797
+ );
2798
+ const query = getString(opSpec, "query") ?? "";
2799
+ const operationName = getString(opSpec, "operationName") ?? null;
2800
+ const variablesMapping = getString(opSpec, "variablesMapping") ?? "input";
2801
+ const timeoutSeconds = getNumber(opSpec, "timeoutSeconds") ?? getNumber(config, "timeoutSeconds") ?? null;
2802
+ return {
2803
+ kind: "graphql_operation",
2804
+ endpoint,
2805
+ query_ref: ctx.registerBlob(query),
2806
+ operation_name: operationName,
2807
+ headers_ref: ctx.registerBlob(mergedHeaders),
2808
+ credential_headers: credentialHeaders,
2809
+ timeout_seconds: timeoutSeconds,
2810
+ variables_mapping: variablesMapping
2811
+ };
2812
+ }
2813
+ function generateCatalog(manifest) {
2814
+ const warnings = [];
2815
+ const resources = {};
2816
+ const signals2 = {};
2817
+ const tools = {};
2818
+ const schemas = {};
2819
+ const registerBlob = (value) => {
2820
+ const hash = sha256Hex(value ?? {});
2821
+ if (!(hash in schemas)) {
2822
+ schemas[hash] = value ?? {};
2823
+ }
2824
+ return hash;
2825
+ };
2826
+ const apps = getArray(manifest, "apps").filter(isRecord3);
2827
+ for (const app of apps) {
2828
+ const appName = getString(app, "name");
2829
+ if (!appName) continue;
2830
+ const appResources2 = getArray(app, "resources").filter(isRecord3);
2831
+ for (const resource of appResources2) {
2832
+ const resourceName = getString(resource, "name");
2833
+ const resourceKind = getString(resource, "kind") ?? "";
2834
+ const backend = getString(resource, "backend") ?? "";
2835
+ const providerKind = getString(resource, "provider_kind") ?? resourceKind;
2836
+ if (!resourceName) continue;
2837
+ const resourceId = `${appName}.${resourceName}`;
2838
+ const config = getRecord(resource, "config") ?? {};
2839
+ const configHash = registerBlob(config);
2840
+ const capability = getRecord(resource, "capability") ?? {};
2841
+ const bindingScope = getString(capability, "binding_scope") ?? "project";
2842
+ const credentialSource = getString(capability, "credential_source") ?? "none";
2843
+ const requiresBindingValue = capability.requires_binding;
2844
+ const credentialExposure = requiresBindingValue === true ? "stdlib" : "none";
2845
+ const resourceSignals = getArray(resource, "signals").filter(isRecord3);
2846
+ const resourceTools2 = getArray(resource, "tools").filter(isRecord3);
2847
+ const emittedSignalIds = [];
2848
+ for (const signal of resourceSignals) {
2849
+ const signalName = getString(signal, "name");
2850
+ if (!signalName) continue;
2851
+ const signalId = `${resourceId}.${signalName}`;
2852
+ const signalScope = getString(signal, "scope") === "principal" ? "principal" : "project";
2853
+ const signalSchema = getRecord(signal, "schema") ?? {};
2854
+ const schemaRef = registerBlob(signalSchema);
2855
+ signals2[signalId] = {
2856
+ id: signalId,
2857
+ resource_id: resourceId,
2858
+ name: signalName,
2859
+ event_name: getString(signal, "event_name") ?? "",
2860
+ scope: signalScope,
2861
+ mode: getString(signal, "mode") ?? "",
2862
+ schema_ref: schemaRef
2863
+ };
2864
+ emittedSignalIds.push(signalId);
2865
+ }
2866
+ const emittedToolIds = [];
2867
+ for (const tool of resourceTools2) {
2868
+ const toolName = getString(tool, "name");
2869
+ if (!toolName) continue;
2870
+ const toolId = `${resourceId}.${toolName}`;
2871
+ const effect = deriveEffect(tool.mode);
2872
+ const toolCapability = getRecord(tool, "capability") ?? {};
2873
+ const toolBackendKind = getString(toolCapability, "backend_kind") ?? backend;
2874
+ const toolProviderKind = getString(toolCapability, "provider_kind") ?? providerKind;
2875
+ const toolCredentialExposure = getString(toolCapability, "credential_exposure") ?? "none";
2876
+ const argsSchemaRef = registerBlob(
2877
+ getRecord(tool, "args_schema") ?? {}
2878
+ );
2879
+ const resultSchemaRef = registerBlob(
2880
+ getRecord(tool, "result_schema") ?? {}
2881
+ );
2882
+ const executor = buildExecutor({
2883
+ resourceId,
2884
+ resourceKind,
2885
+ providerKind: toolProviderKind,
2886
+ backendKind: toolBackendKind,
2887
+ toolName,
2888
+ resourceConfig: config,
2889
+ configHash,
2890
+ registerBlob,
2891
+ warnings
2892
+ });
2893
+ if (executor.kind === "unsupported") {
2894
+ warnings.push(
2895
+ `tool "${toolId}" executor deferred: ${executor.reason}`
2896
+ );
2897
+ }
2898
+ tools[toolId] = {
2899
+ id: toolId,
2900
+ resource_id: resourceId,
2901
+ name: toolName,
2902
+ effect,
2903
+ audit_requirement: deriveAuditRequirement(effect),
2904
+ credential_exposure: toolCredentialExposure,
2905
+ args_schema_ref: argsSchemaRef,
2906
+ result_schema_ref: resultSchemaRef,
2907
+ executor
2908
+ };
2909
+ emittedToolIds.push(toolId);
2910
+ }
2911
+ resources[resourceId] = {
2912
+ id: resourceId,
2913
+ application_name: appName,
2914
+ resource_name: resourceName,
2915
+ resource_kind: resourceKind,
2916
+ provider_kind: providerKind,
2917
+ backend_kind: backend,
2918
+ binding_scope: bindingScope,
2919
+ credential_source: credentialSource,
2920
+ credential_exposure: credentialExposure,
2921
+ config_ref: configHash,
2922
+ signals: emittedSignalIds,
2923
+ tools: emittedToolIds
2924
+ };
2925
+ }
2926
+ }
2927
+ const catalogBody = {
2928
+ contract_version: CATALOG_CONTRACT_VERSION,
2929
+ snapshot_version: CATALOG_SNAPSHOT_VERSION,
2930
+ resources,
2931
+ signals: signals2,
2932
+ tools,
2933
+ schemas
2934
+ };
2935
+ const catalogHash = sha256Hex(catalogBody);
2936
+ return {
2937
+ catalog: {
2938
+ ...catalogBody,
2939
+ catalog_hash: catalogHash
2940
+ },
2941
+ warnings
2942
+ };
2943
+ }
2944
+ var INTERNAL_CONFIG = "__io_cli_compiled_config";
2945
+ var INTERNAL_RUN = "__io_cli_compiled_run";
2946
+ var INTERNAL_ON_START = "__io_cli_compiled_on_start";
2947
+ var INTERNAL_ON_MESSAGE = "__io_cli_compiled_on_message";
2948
+ var INTERNAL_ON_END = "__io_cli_compiled_on_end";
2949
+ var INTERNAL_APPLICATIONS = "__io_cli_compiled_applications";
2950
+ function conversationChannel(name) {
2951
+ return `session:${name}`;
2952
+ }
2953
+ function parseModule2(source) {
2954
+ return acorn.parse(source, {
2955
+ ecmaVersion: "latest",
2956
+ sourceType: "module"
2957
+ });
2958
+ }
2959
+ function rootStatementFor(source, statementStart, kind, name) {
2960
+ let ast;
2961
+ try {
2962
+ ast = parseModule2(source);
2963
+ } catch (error) {
2964
+ throw new BuildError(
2965
+ `failed to parse compiled target source: ${error instanceof Error ? error.message : String(error)}`
2966
+ );
2967
+ }
2968
+ const body = ast.body;
2969
+ const stmt = body.find(
2970
+ (candidate) => candidate.start === statementStart
2971
+ );
2972
+ if (stmt) {
2973
+ return stmt;
2974
+ }
2975
+ const fallback = body.find((candidate) => {
2976
+ if (candidate.type !== "ExpressionStatement") {
2977
+ return false;
2978
+ }
2979
+ const expression = candidate.expression;
2980
+ if (expression.type !== "CallExpression") {
2981
+ return false;
2982
+ }
2983
+ const args = expression.arguments;
2984
+ if (args.length < 2 || args[0]?.type !== "Literal" || args[1]?.type !== "ObjectExpression") {
2985
+ return false;
2986
+ }
2987
+ const callee = expression.callee;
2988
+ if (callee.type !== "Identifier") {
2989
+ return false;
2990
+ }
2991
+ return callee.name === kind && args[0].value === name;
2992
+ });
2993
+ if (!fallback) {
2994
+ throw new BuildError(
2995
+ `failed to locate compiled ${kind} root '${name}' at byte offset ${statementStart}`
2996
+ );
2997
+ }
2998
+ return fallback;
2999
+ }
3000
+ function rootSpecNode(stmt) {
2269
3001
  if (stmt.type !== "ExpressionStatement") {
2270
3002
  throw new BuildError("compiled target root must be an expression statement");
2271
3003
  }
@@ -2567,13 +3299,90 @@ function pruneUnusedTopLevel(source, seeds) {
2567
3299
  }
2568
3300
  return applyEdits2(source, edits);
2569
3301
  }
3302
+ function parseDurationMs(value) {
3303
+ if (value === void 0) return void 0;
3304
+ if (typeof value === "number") {
3305
+ if (Number.isInteger(value) && value > 0) return value;
3306
+ throw new BuildError("execution.timeout must be a positive integer number of milliseconds");
3307
+ }
3308
+ if (typeof value !== "string") {
3309
+ throw new BuildError("execution.timeout must be a duration string or milliseconds");
3310
+ }
3311
+ const match = value.trim().toLowerCase().match(/^(\d+)\s*(ms|s|m|h)$/);
3312
+ if (!match) {
3313
+ throw new BuildError("execution.timeout must use ms, s, m, or h units");
3314
+ }
3315
+ const amount = Number(match[1]);
3316
+ const unit = match[2];
3317
+ const multiplier = unit === "ms" ? 1 : unit === "s" ? 1e3 : unit === "m" ? 6e4 : 36e5;
3318
+ return amount * multiplier;
3319
+ }
3320
+ function parseMemoryMb(value) {
3321
+ if (value === void 0) return void 0;
3322
+ if (typeof value === "number") {
3323
+ if (Number.isInteger(value) && value > 0) return value;
3324
+ throw new BuildError("execution.memory must be a positive integer number of megabytes");
3325
+ }
3326
+ if (typeof value !== "string") {
3327
+ throw new BuildError("execution.memory must be a size string or megabytes");
3328
+ }
3329
+ const match = value.trim().toLowerCase().match(/^(\d+)\s*(mb|m|gb|g)$/);
3330
+ if (!match) {
3331
+ throw new BuildError("execution.memory must use mb or gb units");
3332
+ }
3333
+ const amount = Number(match[1]);
3334
+ const unit = match[2];
3335
+ return unit === "gb" || unit === "g" ? amount * 1024 : amount;
3336
+ }
3337
+ function nonNegativeInteger(value, field) {
3338
+ if (value === void 0) return void 0;
3339
+ if (Number.isInteger(value) && value >= 0) return value;
3340
+ throw new BuildError(`${field} must be a non-negative integer`);
3341
+ }
3342
+ function normalizeBudgetMap(value, field) {
3343
+ if (value === void 0) return void 0;
3344
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3345
+ throw new BuildError(`${field} must be an object`);
3346
+ }
3347
+ const out = {};
3348
+ for (const [key, budget] of Object.entries(value)) {
3349
+ if (!key.trim()) {
3350
+ throw new BuildError(`${field} keys must be non-empty strings`);
3351
+ }
3352
+ out[key] = nonNegativeInteger(budget, `${field}.${key}`);
3353
+ }
3354
+ return out;
3355
+ }
3356
+ function normalizeRateBudget(rateBudget) {
3357
+ if (rateBudget === void 0) return void 0;
3358
+ const perApplication = normalizeBudgetMap(
3359
+ rateBudget.perApplication,
3360
+ "execution.rateBudget.perApplication"
3361
+ );
3362
+ const perCapability = normalizeBudgetMap(
3363
+ rateBudget.perCapability,
3364
+ "execution.rateBudget.perCapability"
3365
+ );
3366
+ const out = {
3367
+ ...perApplication ? { per_application: perApplication } : {},
3368
+ ...perCapability ? { per_capability: perCapability } : {}
3369
+ };
3370
+ return Object.keys(out).length > 0 ? out : void 0;
3371
+ }
2570
3372
  function compactExecution(execution) {
2571
3373
  if (!execution) {
2572
3374
  return void 0;
2573
3375
  }
2574
- const compact = Object.fromEntries(
2575
- Object.entries(execution).filter(([, value]) => value !== void 0)
2576
- );
3376
+ const timeoutMs = parseDurationMs(execution.timeout);
3377
+ const memoryMb = parseMemoryMb(execution.memory);
3378
+ const maxToolCalls = nonNegativeInteger(execution.maxToolCalls, "execution.maxToolCalls");
3379
+ const rateBudget = normalizeRateBudget(execution.rateBudget);
3380
+ const compact = {
3381
+ ...timeoutMs !== void 0 ? { timeout_ms: timeoutMs } : {},
3382
+ ...memoryMb !== void 0 ? { memory_mb: memoryMb } : {},
3383
+ ...maxToolCalls !== void 0 ? { max_tool_calls: maxToolCalls } : {},
3384
+ ...rateBudget !== void 0 ? { rate_budget: rateBudget } : {}
3385
+ };
2577
3386
  return Object.keys(compact).length > 0 ? compact : void 0;
2578
3387
  }
2579
3388
  function buildBehaviorReplacement(source, spec, behavior) {
@@ -2583,8 +3392,6 @@ function buildBehaviorReplacement(source, spec, behavior) {
2583
3392
  }
2584
3393
  const execution = compactExecution(behavior.execution);
2585
3394
  const runSource = functionSourceFromProperty(source, run, "run");
2586
- const trackingId = findProperty(spec, "trackingId");
2587
- const trackingIdSource = trackingId ? functionSourceFromProperty(source, trackingId, "trackingId") : void 0;
2588
3395
  const applications = findProperty(spec, "applications");
2589
3396
  const applicationsSource = applications ? expressionSourceFromProperty(source, applications, "applications") : void 0;
2590
3397
  const configEntries = [
@@ -2595,9 +3402,6 @@ function buildBehaviorReplacement(source, spec, behavior) {
2595
3402
  if (execution) {
2596
3403
  configEntries.push(`execution: ${JSON.stringify(execution)}`);
2597
3404
  }
2598
- if (behavior.messageHistory !== void 0) {
2599
- configEntries.push(`messageHistory: ${JSON.stringify(behavior.messageHistory)}`);
2600
- }
2601
3405
  if (applicationsSource) {
2602
3406
  configEntries.push(`applications: ${INTERNAL_APPLICATIONS}`);
2603
3407
  }
@@ -2606,17 +3410,14 @@ function buildBehaviorReplacement(source, spec, behavior) {
2606
3410
  replacement: [
2607
3411
  ...applicationsSource ? [`const ${INTERNAL_APPLICATIONS} = ${applicationsSource};`] : [],
2608
3412
  `const ${INTERNAL_CONFIG} = ${configSource};`,
2609
- `const ${INTERNAL_TRACKING_ID} = ${trackingIdSource ?? "undefined"};`,
2610
3413
  `const ${INTERNAL_RUN} = ${runSource};`,
2611
- `export { ${INTERNAL_CONFIG} as config, ${INTERNAL_TRACKING_ID} as trackingId, ${INTERNAL_RUN} as run };`
3414
+ `export { ${INTERNAL_CONFIG} as config, ${INTERNAL_RUN} as run };`
2612
3415
  ].join("\n"),
2613
3416
  refs: /* @__PURE__ */ new Set([
2614
3417
  INTERNAL_CONFIG,
2615
- INTERNAL_TRACKING_ID,
2616
3418
  INTERNAL_RUN,
2617
3419
  ...applicationsSource ? [INTERNAL_APPLICATIONS] : [],
2618
3420
  ...refsFromExpressionSource(runSource),
2619
- ...trackingIdSource ? refsFromExpressionSource(trackingIdSource) : [],
2620
3421
  ...applicationsSource ? refsFromExpressionSource(applicationsSource) : []
2621
3422
  ])
2622
3423
  };
@@ -2756,7 +3557,15 @@ async function buildPipeline(config, cwd = process.cwd(), minify = false, buildD
2756
3557
  conversationBundles
2757
3558
  });
2758
3559
  validateManifest(manifest);
2759
- return { manifest, outDir };
3560
+ const { catalog: catalog2, warnings } = generateCatalog(manifest);
3561
+ if (warnings.length > 0) {
3562
+ throw new BuildError(
3563
+ `Catalog generation produced ${warnings.length} unrecoverable issue${warnings.length === 1 ? "" : "s"} \u2014 refusing to ship a partial catalog:
3564
+ ${warnings.map((w) => ` - ${w}`).join("\n")}`
3565
+ );
3566
+ }
3567
+ await writeFile(join(outDir, "catalog.json"), stableStringify(catalog2, 2), "utf-8");
3568
+ return { manifest, catalog: catalog2, outDir };
2760
3569
  }
2761
3570
  async function createStagingDir() {
2762
3571
  return mkdtemp(join(tmpdir(), "io-cli-"));
@@ -2820,8 +3629,8 @@ async function buildCommand(options) {
2820
3629
  async function zipBuildOutput(buildDir, outPath) {
2821
3630
  const output = createWriteStream(outPath);
2822
3631
  const archive = archiver("zip", { zlib: { level: 9 } });
2823
- const done = new Promise((resolve4, reject) => {
2824
- output.on("close", resolve4);
3632
+ const done = new Promise((resolve5, reject) => {
3633
+ output.on("close", resolve5);
2825
3634
  archive.on("error", reject);
2826
3635
  });
2827
3636
  archive.pipe(output);
@@ -2850,13 +3659,13 @@ async function addDirectory(archive, baseDir, prefix, skipRelativePath) {
2850
3659
  }
2851
3660
 
2852
3661
  // src/deploy/diff.ts
2853
- function stableStringify(value) {
3662
+ function stableStringify2(value) {
2854
3663
  if (Array.isArray(value)) {
2855
- return `[${value.map(stableStringify).join(",")}]`;
3664
+ return `[${value.map(stableStringify2).join(",")}]`;
2856
3665
  }
2857
3666
  if (value && typeof value === "object") {
2858
3667
  const record = value;
2859
- return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
3668
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(record[key])}`).join(",")}}`;
2860
3669
  }
2861
3670
  return JSON.stringify(value);
2862
3671
  }
@@ -2882,7 +3691,7 @@ function diffEntries(local, remote) {
2882
3691
  for (const { name, value } of local) {
2883
3692
  if (!remoteMap.has(name)) {
2884
3693
  entries.push({ name, kind: "added" });
2885
- } else if (stableStringify(value) !== stableStringify(remoteMap.get(name))) {
3694
+ } else if (stableStringify2(value) !== stableStringify2(remoteMap.get(name))) {
2886
3695
  entries.push({ name, kind: "changed" });
2887
3696
  }
2888
3697
  }
@@ -2995,6 +3804,77 @@ function diffManifests(local, remote) {
2995
3804
  hasChanges
2996
3805
  };
2997
3806
  }
3807
+
3808
+ // src/deploy/catalog-diff.ts
3809
+ function stableStringify3(value) {
3810
+ if (Array.isArray(value)) {
3811
+ return `[${value.map(stableStringify3).join(",")}]`;
3812
+ }
3813
+ if (value && typeof value === "object") {
3814
+ const record = value;
3815
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify3(record[key])}`).join(",")}}`;
3816
+ }
3817
+ return JSON.stringify(value);
3818
+ }
3819
+ function diffSection(local, remote) {
3820
+ const localMap = local ?? {};
3821
+ const remoteMap = remote ?? {};
3822
+ const entries = [];
3823
+ const seen = /* @__PURE__ */ new Set();
3824
+ for (const id of Object.keys(localMap).sort()) {
3825
+ seen.add(id);
3826
+ if (!(id in remoteMap)) {
3827
+ entries.push({ id, kind: "added" });
3828
+ } else if (stableStringify3(localMap[id]) !== stableStringify3(remoteMap[id])) {
3829
+ entries.push({ id, kind: "changed" });
3830
+ }
3831
+ }
3832
+ for (const id of Object.keys(remoteMap).sort()) {
3833
+ if (!seen.has(id) && !(id in localMap)) {
3834
+ entries.push({ id, kind: "removed" });
3835
+ }
3836
+ }
3837
+ return entries;
3838
+ }
3839
+ function schemaDiffCounts(local, remote) {
3840
+ const localKeys = new Set(Object.keys(local ?? {}));
3841
+ const remoteKeys = new Set(Object.keys(remote ?? {}));
3842
+ let added = 0;
3843
+ let removed = 0;
3844
+ for (const k of localKeys) {
3845
+ if (!remoteKeys.has(k)) added++;
3846
+ }
3847
+ for (const k of remoteKeys) {
3848
+ if (!localKeys.has(k)) removed++;
3849
+ }
3850
+ return { added, removed };
3851
+ }
3852
+ function diffCatalogs(local, remote) {
3853
+ const localResources = local?.resources ?? {};
3854
+ const remoteResources = remote?.resources ?? {};
3855
+ const localSignals = local?.signals ?? {};
3856
+ const remoteSignals = remote?.signals ?? {};
3857
+ const localTools = local?.tools ?? {};
3858
+ const remoteTools = remote?.tools ?? {};
3859
+ const localSchemas = local?.schemas ?? {};
3860
+ const remoteSchemas = remote?.schemas ?? {};
3861
+ const resources = diffSection(localResources, remoteResources);
3862
+ const signals2 = diffSection(localSignals, remoteSignals);
3863
+ const tools = diffSection(localTools, remoteTools);
3864
+ const schemas = schemaDiffCounts(localSchemas, remoteSchemas);
3865
+ const localHash = local?.catalog_hash ?? null;
3866
+ const remoteHash = remote?.catalog_hash ?? null;
3867
+ const hashChanged = (localHash ?? remoteHash) !== null && localHash !== remoteHash;
3868
+ const hasChanges = resources.length > 0 || signals2.length > 0 || tools.length > 0 || schemas.added > 0 || schemas.removed > 0 || hashChanged;
3869
+ return {
3870
+ hashChanged,
3871
+ resources,
3872
+ signals: signals2,
3873
+ tools,
3874
+ schemas,
3875
+ hasChanges
3876
+ };
3877
+ }
2998
3878
  var SYMBOLS = {
2999
3879
  added: chalk12.green("+ "),
3000
3880
  changed: chalk12.yellow("~ "),
@@ -3014,7 +3894,12 @@ var SECTIONS = [
3014
3894
  { key: "targets", label: "Targets" },
3015
3895
  { key: "subscriptions", label: "Subscriptions" }
3016
3896
  ];
3017
- function formatPlan(diff) {
3897
+ var CATALOG_SECTIONS = [
3898
+ { key: "resources", label: "Catalog resources" },
3899
+ { key: "signals", label: "Catalog signals" },
3900
+ { key: "tools", label: "Catalog tools" }
3901
+ ];
3902
+ function formatPlan(diff, catalogDiff) {
3018
3903
  const lines = [];
3019
3904
  for (const { key, label } of SECTIONS) {
3020
3905
  const entries = diff[key];
@@ -3029,15 +3914,44 @@ function formatPlan(diff) {
3029
3914
  }
3030
3915
  }
3031
3916
  }
3917
+ if (catalogDiff) {
3918
+ lines.push("");
3919
+ lines.push(
3920
+ chalk12.bold(
3921
+ catalogDiff.hashChanged ? " Catalog (hash changed):" : " Catalog:"
3922
+ )
3923
+ );
3924
+ for (const { key, label } of CATALOG_SECTIONS) {
3925
+ const entries = catalogDiff[key];
3926
+ lines.push(` ${label}:`);
3927
+ if (entries.length === 0) {
3928
+ lines.push(chalk12.gray(" (no changes)"));
3929
+ } else {
3930
+ for (const e of entries) {
3931
+ lines.push(
3932
+ ` ${SYMBOLS[e.kind]}${e.id} ${chalk12.gray(`(${LABELS[e.kind]})`)}`
3933
+ );
3934
+ }
3935
+ }
3936
+ }
3937
+ const schemaLine = catalogDiff.schemas;
3938
+ if (schemaLine.added > 0 || schemaLine.removed > 0) {
3939
+ lines.push(
3940
+ ` ${chalk12.gray("Schemas:")} ${chalk12.green(`+${schemaLine.added}`)} ${chalk12.red(`-${schemaLine.removed}`)}`
3941
+ );
3942
+ } else {
3943
+ lines.push(` ${chalk12.gray("Schemas: (no changes)")}`);
3944
+ }
3945
+ }
3032
3946
  return lines.join("\n");
3033
3947
  }
3034
3948
  async function confirmPlan(skip) {
3035
3949
  if (skip) return true;
3036
3950
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3037
- return new Promise((resolve4) => {
3951
+ return new Promise((resolve5) => {
3038
3952
  rl.question("\nApply these changes? (y/n) ", (answer) => {
3039
3953
  rl.close();
3040
- resolve4(answer.trim().toLowerCase() === "y");
3954
+ resolve5(answer.trim().toLowerCase() === "y");
3041
3955
  });
3042
3956
  });
3043
3957
  }
@@ -3111,40 +4025,6 @@ function socketAuthParams(creds) {
3111
4025
  return { authorization: `Bearer ${creds.token}` };
3112
4026
  }
3113
4027
 
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
4028
  // src/api/client.ts
3149
4029
  var REFRESH_MUTATION = `
3150
4030
  mutation Refresh($input: RefreshAuthInput!) {
@@ -3245,8 +4125,32 @@ function graphqlErrorMessage(errors) {
3245
4125
  }
3246
4126
  return null;
3247
4127
  }
3248
- function isMissingSnapshotJsonFieldError(error) {
3249
- return error instanceof ApiError && error.message.includes("snapshotJson") && error.message.includes("Cannot query field");
4128
+ function isPlainObject(value) {
4129
+ return !!value && typeof value === "object" && !Array.isArray(value);
4130
+ }
4131
+ function isCatalogShape(value) {
4132
+ if (!isPlainObject(value)) return false;
4133
+ return value.contract_version === "io.catalog.v1" && value.snapshot_version === "io_runtime.snapshot.v2" && typeof value.catalog_hash === "string" && isPlainObject(value.resources) && isPlainObject(value.signals) && isPlainObject(value.tools) && isPlainObject(value.schemas);
4134
+ }
4135
+ function isManifestShape(value) {
4136
+ if (!isPlainObject(value)) return false;
4137
+ 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);
4138
+ }
4139
+ function parseRemoteCatalog(catalogJson) {
4140
+ let parsed;
4141
+ try {
4142
+ parsed = JSON.parse(catalogJson);
4143
+ } catch (err) {
4144
+ throw new ConfigError(
4145
+ `Remote deployment catalog was not valid JSON: ${err instanceof Error ? err.message : String(err)}`
4146
+ );
4147
+ }
4148
+ if (!isCatalogShape(parsed)) {
4149
+ throw new ConfigError(
4150
+ "Remote deployment catalog does not match the io.catalog.v1 catalog shape."
4151
+ );
4152
+ }
4153
+ return parsed;
3250
4154
  }
3251
4155
  function parseRemoteManifest(snapshotJson) {
3252
4156
  let parsed;
@@ -3257,11 +4161,22 @@ function parseRemoteManifest(snapshotJson) {
3257
4161
  `Remote deployment snapshot was not valid JSON: ${err instanceof Error ? err.message : String(err)}`
3258
4162
  );
3259
4163
  }
3260
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3261
- throw new ConfigError("Remote deployment snapshot was not a JSON object.");
4164
+ if (!isManifestShape(parsed)) {
4165
+ throw new ConfigError(
4166
+ "Remote deployment snapshot does not match the io_runtime.snapshot.v2 manifest shape."
4167
+ );
3262
4168
  }
3263
4169
  return parsed;
3264
4170
  }
4171
+ var EMPTY_MANIFEST = {
4172
+ contract_version: "io.invocation.v2",
4173
+ snapshot_version: "io_runtime.snapshot.v2",
4174
+ secret_bundles: [],
4175
+ apps: [],
4176
+ endpoints: [],
4177
+ targets: [],
4178
+ subscriptions: []
4179
+ };
3265
4180
  async function executeGraphQLUnauth(apiUrl, query, variables, headers = {}) {
3266
4181
  const response = await fetch(`${trimBaseUrl(apiUrl)}/graphql`, {
3267
4182
  method: "POST",
@@ -3280,6 +4195,15 @@ async function executeGraphQLUnauth(apiUrl, query, variables, headers = {}) {
3280
4195
  }
3281
4196
  return payload.data;
3282
4197
  }
4198
+ function serializePrincipalInput(input) {
4199
+ return {
4200
+ ...input.externalId !== void 0 ? { externalId: input.externalId } : {},
4201
+ ...input.state !== void 0 ? {
4202
+ state: input.state.toUpperCase()
4203
+ } : {},
4204
+ ...input.metadata !== void 0 ? { metadata: input.metadata } : {}
4205
+ };
4206
+ }
3283
4207
  async function refreshAndSave(apiUrl, refreshToken) {
3284
4208
  let data;
3285
4209
  try {
@@ -3478,6 +4402,23 @@ function createClient(config) {
3478
4402
  );
3479
4403
  return data.createProject;
3480
4404
  }
4405
+ async function deleteProject(organizationId, projectIdValue) {
4406
+ const data = await graphql(
4407
+ `
4408
+ mutation DeleteProject($organizationId: ID!, $id: ID!) {
4409
+ deleteProject(organizationId: $organizationId, id: $id) {
4410
+ id
4411
+ name
4412
+ slug
4413
+ description
4414
+ organizationId
4415
+ }
4416
+ }
4417
+ `,
4418
+ { organizationId, id: projectIdValue }
4419
+ );
4420
+ return data.deleteProject;
4421
+ }
3481
4422
  async function principals2(projectIdValue) {
3482
4423
  const data = await graphql(
3483
4424
  `
@@ -3512,7 +4453,10 @@ function createClient(config) {
3512
4453
  }
3513
4454
  }
3514
4455
  `,
3515
- { projectId: projectIdValue, input }
4456
+ {
4457
+ projectId: projectIdValue,
4458
+ input: serializePrincipalInput(input)
4459
+ }
3516
4460
  );
3517
4461
  return data.createPrincipal;
3518
4462
  }
@@ -3531,7 +4475,11 @@ function createClient(config) {
3531
4475
  }
3532
4476
  }
3533
4477
  `,
3534
- { projectId: projectIdValue, id, input }
4478
+ {
4479
+ projectId: projectIdValue,
4480
+ id,
4481
+ input: serializePrincipalInput(input)
4482
+ }
3535
4483
  );
3536
4484
  return data.updatePrincipal ?? null;
3537
4485
  }
@@ -3607,28 +4555,34 @@ function createClient(config) {
3607
4555
  return data.deleteSecret === true;
3608
4556
  }
3609
4557
  async function fetchRemoteManifest(projectIdValue) {
3610
- let data;
3611
- try {
3612
- data = await graphql(
3613
- `
4558
+ const data = await graphql(
4559
+ `
3614
4560
  query ActiveDeploymentSnapshot($projectId: ID!) {
3615
4561
  activeDeployment(projectId: $projectId) {
3616
4562
  snapshotJson
3617
4563
  }
3618
4564
  }
3619
4565
  `,
3620
- { projectId: projectIdValue }
3621
- );
3622
- } catch (err) {
3623
- if (isMissingSnapshotJsonFieldError(err)) {
3624
- return {};
3625
- }
3626
- throw err;
3627
- }
4566
+ { projectId: projectIdValue }
4567
+ );
3628
4568
  const snapshotJson = data.activeDeployment?.snapshotJson;
3629
- return typeof snapshotJson === "string" && snapshotJson.trim() !== "" ? parseRemoteManifest(snapshotJson) : {};
4569
+ return typeof snapshotJson === "string" && snapshotJson.trim() !== "" ? parseRemoteManifest(snapshotJson) : EMPTY_MANIFEST;
3630
4570
  }
3631
- async function deployBundle(bundlePath, manifest) {
4571
+ async function fetchRemoteCatalog(projectIdValue) {
4572
+ const data = await graphql(
4573
+ `
4574
+ query ActiveDeploymentCatalog($projectId: ID!) {
4575
+ activeDeployment(projectId: $projectId) {
4576
+ catalogJson
4577
+ }
4578
+ }
4579
+ `,
4580
+ { projectId: projectIdValue }
4581
+ );
4582
+ const catalogJson = data.activeDeployment?.catalogJson;
4583
+ return typeof catalogJson === "string" && catalogJson.trim() !== "" ? parseRemoteCatalog(catalogJson) : null;
4584
+ }
4585
+ async function deployBundle(bundlePath, manifest, catalog2) {
3632
4586
  const MAX_BUNDLE_BYTES = 100 * 1024 * 1024;
3633
4587
  const fileStat = await stat(bundlePath);
3634
4588
  if (fileStat.size > MAX_BUNDLE_BYTES) {
@@ -3666,7 +4620,8 @@ function createClient(config) {
3666
4620
  projectId: projectIdValue,
3667
4621
  bundleHash: upload.bundleHash,
3668
4622
  revision: nextRevision,
3669
- snapshotJson: stableStringify2(manifest)
4623
+ snapshotJson: stableStringify(manifest),
4624
+ catalogJson: stableStringify(catalog2)
3670
4625
  }
3671
4626
  }
3672
4627
  );
@@ -3677,7 +4632,7 @@ function createClient(config) {
3677
4632
  if (!input.principalId || input.principalId.trim() === "") {
3678
4633
  throw new ConfigError("principalId is required to emit a signal");
3679
4634
  }
3680
- const eventName = normalizeRuntimeSignalName2(input.signal);
4635
+ const eventName = normalizeRuntimeSignalName(input.signal);
3681
4636
  if (!eventName) {
3682
4637
  throw new ConfigError(
3683
4638
  "signal name must be a bare event name like smoke.start or use the signal: namespace"
@@ -3708,7 +4663,6 @@ function createClient(config) {
3708
4663
  eventName,
3709
4664
  payloadJson: JSON.stringify(input.payload ?? {}),
3710
4665
  metadataJson: JSON.stringify(input.metadata ?? {}),
3711
- correlationId: input.correlationId,
3712
4666
  causationId: input.causationId,
3713
4667
  idempotencyKey: input.idempotencyKey,
3714
4668
  sourceName: input.sourceName
@@ -3730,6 +4684,37 @@ function createClient(config) {
3730
4684
  );
3731
4685
  return data.activeRevision;
3732
4686
  }
4687
+ async function reconcileProject(projectIdValue) {
4688
+ const data = await graphql(
4689
+ `
4690
+ mutation ReconcileProject($projectId: ID!) {
4691
+ reconcileProject(projectId: $projectId)
4692
+ }
4693
+ `,
4694
+ { projectId: projectIdValue }
4695
+ );
4696
+ return data.reconcileProject === true;
4697
+ }
4698
+ async function deploymentStatus(projectIdValue, revision) {
4699
+ const data = await graphql(
4700
+ `
4701
+ query DeploymentStatus($projectId: ID!, $revision: Int!) {
4702
+ deploymentStatus(projectId: $projectId, revision: $revision) {
4703
+ id
4704
+ projectId
4705
+ revision
4706
+ bundleHash
4707
+ status
4708
+ activatedAt
4709
+ insertedAt
4710
+ updatedAt
4711
+ }
4712
+ }
4713
+ `,
4714
+ { projectId: projectIdValue, revision }
4715
+ );
4716
+ return data.deploymentStatus;
4717
+ }
3733
4718
  async function quotaStatuses() {
3734
4719
  const data = await graphql(
3735
4720
  `
@@ -3781,6 +4766,7 @@ function createClient(config) {
3781
4766
  organizations,
3782
4767
  projects: projects2,
3783
4768
  createProject,
4769
+ deleteProject,
3784
4770
  principals: principals2,
3785
4771
  createPrincipal,
3786
4772
  updatePrincipal,
@@ -3789,14 +4775,245 @@ function createClient(config) {
3789
4775
  putSecret,
3790
4776
  deleteSecret,
3791
4777
  fetchRemoteManifest,
4778
+ fetchRemoteCatalog,
3792
4779
  deployBundle,
4780
+ deploymentStatus,
4781
+ reconcileProject,
3793
4782
  emitSignal,
3794
4783
  quotaStatuses,
3795
4784
  projectEvents
3796
4785
  };
3797
4786
  }
4787
+ Object.assign(globalThis, { WebSocket });
4788
+ var SessionTokenNotSupportedError = class extends Error {
4789
+ constructor(message) {
4790
+ super(
4791
+ message ?? "WebSocket subscriptions require API key authentication on the current io_runtime socket contract."
4792
+ );
4793
+ this.name = "SessionTokenNotSupportedError";
4794
+ }
4795
+ };
4796
+ function buildSocketUrl(apiUrl) {
4797
+ const url = new URL(apiUrl);
4798
+ const basePath = url.pathname.replace(/\/+$/, "");
4799
+ const socketBase = basePath.endsWith("/graphql") ? basePath.slice(0, -"/graphql".length) : basePath;
4800
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
4801
+ url.pathname = `${socketBase === "/" ? "" : socketBase}/socket`;
4802
+ url.search = "";
4803
+ url.hash = "";
4804
+ return url.toString();
4805
+ }
4806
+ function createSocketClient(options) {
4807
+ let phoenixSocket = null;
4808
+ let absintheSocket = null;
4809
+ const notifiers = /* @__PURE__ */ new Set();
4810
+ function ensureConnection() {
4811
+ if (!options.credentials.apiKey) {
4812
+ throw new SessionTokenNotSupportedError();
4813
+ }
4814
+ if (!phoenixSocket || !absintheSocket) {
4815
+ phoenixSocket = new Socket(buildSocketUrl(options.apiUrl), {
4816
+ params: socketAuthParams(options.credentials),
4817
+ timeout: options.timeoutMs ?? 1e4
4818
+ });
4819
+ absintheSocket = AbsintheSocket.create(phoenixSocket);
4820
+ phoenixSocket.connect();
4821
+ }
4822
+ return { absintheSocket };
4823
+ }
4824
+ function tearDownIfIdle() {
4825
+ if (notifiers.size === 0 && phoenixSocket) {
4826
+ phoenixSocket.disconnect();
4827
+ phoenixSocket = null;
4828
+ absintheSocket = null;
4829
+ }
4830
+ }
4831
+ return {
4832
+ subscribe(operation, variables, observer) {
4833
+ const conn = ensureConnection();
4834
+ const notifier = AbsintheSocket.send(conn.absintheSocket, {
4835
+ operation,
4836
+ variables
4837
+ });
4838
+ notifiers.add(notifier);
4839
+ AbsintheSocket.observe(conn.absintheSocket, notifier, {
4840
+ onAbort: (err) => {
4841
+ notifiers.delete(notifier);
4842
+ if (observer.onAbort) {
4843
+ observer.onAbort(err);
4844
+ } else {
4845
+ observer.onError?.(err);
4846
+ }
4847
+ tearDownIfIdle();
4848
+ },
4849
+ onCancel: () => {
4850
+ notifiers.delete(notifier);
4851
+ tearDownIfIdle();
4852
+ },
4853
+ onError: (err) => {
4854
+ observer.onError?.(err);
4855
+ },
4856
+ onResult: (result) => {
4857
+ observer.onResult?.(result);
4858
+ },
4859
+ onStart: () => {
4860
+ observer.onStart?.();
4861
+ }
4862
+ });
4863
+ return () => {
4864
+ if (!notifiers.has(notifier)) return;
4865
+ notifiers.delete(notifier);
4866
+ try {
4867
+ AbsintheSocket.cancel(conn.absintheSocket, notifier);
4868
+ } catch {
4869
+ }
4870
+ tearDownIfIdle();
4871
+ };
4872
+ },
4873
+ close() {
4874
+ notifiers.clear();
4875
+ if (phoenixSocket) {
4876
+ phoenixSocket.disconnect();
4877
+ }
4878
+ phoenixSocket = null;
4879
+ absintheSocket = null;
4880
+ }
4881
+ };
4882
+ }
4883
+
4884
+ // src/api/watcher.ts
4885
+ var DEFAULT_POLL_INTERVAL_MS = 2e3;
4886
+ var DEFAULT_SEEN_ID_LIMIT = 1e3;
4887
+ var DEFAULT_POLL_SAFETY_LOOPS = 10;
4888
+ function installSigintHandler(onTerminate) {
4889
+ const handler = () => {
4890
+ onTerminate();
4891
+ };
4892
+ process.once("SIGINT", handler);
4893
+ return () => {
4894
+ process.removeListener("SIGINT", handler);
4895
+ };
4896
+ }
4897
+ async function watch(plan, handlers) {
4898
+ const seenIds = /* @__PURE__ */ new Set();
4899
+ const seenOrder = [];
4900
+ const seenLimit = plan.seenIdLimit ?? DEFAULT_SEEN_ID_LIMIT;
4901
+ function rememberId(id) {
4902
+ if (seenIds.has(id)) return;
4903
+ seenIds.add(id);
4904
+ seenOrder.push(id);
4905
+ while (seenOrder.length > seenLimit) {
4906
+ const evicted = seenOrder.shift();
4907
+ if (evicted) seenIds.delete(evicted);
4908
+ }
4909
+ }
4910
+ function handle(event) {
4911
+ const id = plan.getId(event);
4912
+ if (seenIds.has(id)) return false;
4913
+ rememberId(id);
4914
+ handlers.onEvent(event);
4915
+ return true;
4916
+ }
4917
+ let cursor = plan.initialSince ?? (/* @__PURE__ */ new Date()).toISOString();
4918
+ function advanceCursor(event) {
4919
+ if (!plan.getCursor) return;
4920
+ const next = plan.getCursor(event);
4921
+ if (next > cursor) cursor = next;
4922
+ }
4923
+ if (plan.snapshot) {
4924
+ const initial = await plan.snapshot();
4925
+ for (const event of initial) {
4926
+ handle(event);
4927
+ advanceCursor(event);
4928
+ if (plan.isTerminal?.(event)) return;
4929
+ }
4930
+ }
4931
+ return new Promise((resolve5, reject) => {
4932
+ let cleanupSigint;
4933
+ let subscribeHandle;
4934
+ let pollTimer;
4935
+ let terminated = false;
4936
+ function finalize(action) {
4937
+ if (terminated) return;
4938
+ terminated = true;
4939
+ subscribeHandle?.unsubscribe();
4940
+ if (pollTimer) clearTimeout(pollTimer);
4941
+ cleanupSigint?.();
4942
+ action();
4943
+ }
4944
+ cleanupSigint = installSigintHandler(() => {
4945
+ finalize(() => resolve5());
4946
+ });
4947
+ function processLiveEvent(event) {
4948
+ const accepted = handle(event);
4949
+ if (accepted) advanceCursor(event);
4950
+ if (plan.isTerminal?.(event)) finalize(() => resolve5());
4951
+ }
4952
+ async function startPolling() {
4953
+ if (!plan.poll) {
4954
+ finalize(() => reject(new Error("No polling fallback configured for this watch.")));
4955
+ return;
4956
+ }
4957
+ handlers.onConnected?.("polling");
4958
+ const interval = plan.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
4959
+ const safetyLoops = plan.pollBatchSafetyLoops ?? DEFAULT_POLL_SAFETY_LOOPS;
4960
+ const loop = async () => {
4961
+ if (terminated) return;
4962
+ try {
4963
+ for (let i = 0; i < safetyLoops; i += 1) {
4964
+ const batch = await plan.poll(cursor);
4965
+ let anyNew = false;
4966
+ for (const event of batch) {
4967
+ if (handle(event)) anyNew = true;
4968
+ advanceCursor(event);
4969
+ if (plan.isTerminal?.(event)) {
4970
+ finalize(() => resolve5());
4971
+ return;
4972
+ }
4973
+ }
4974
+ if (!anyNew) break;
4975
+ }
4976
+ } catch (err) {
4977
+ finalize(
4978
+ () => reject(err instanceof Error ? err : new Error(String(err)))
4979
+ );
4980
+ return;
4981
+ }
4982
+ pollTimer = setTimeout(loop, interval);
4983
+ };
4984
+ void loop();
4985
+ }
4986
+ if (!plan.subscribe) {
4987
+ void startPolling();
4988
+ return;
4989
+ }
4990
+ try {
4991
+ subscribeHandle = plan.subscribe(
4992
+ processLiveEvent,
4993
+ (err) => {
4994
+ if (err instanceof SessionTokenNotSupportedError && plan.poll) {
4995
+ subscribeHandle = void 0;
4996
+ void startPolling();
4997
+ return;
4998
+ }
4999
+ finalize(() => reject(err));
5000
+ },
5001
+ () => handlers.onConnected?.("subscription")
5002
+ );
5003
+ } catch (err) {
5004
+ if (err instanceof SessionTokenNotSupportedError && plan.poll) {
5005
+ void startPolling();
5006
+ return;
5007
+ }
5008
+ finalize(() => reject(err instanceof Error ? err : new Error(String(err))));
5009
+ }
5010
+ });
5011
+ }
3798
5012
 
3799
5013
  // src/commands/deploy.ts
5014
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["active", "failed", "superseded"]);
5015
+ var WATCH_POLL_INTERVAL_MS = 2e3;
5016
+ var WATCH_DEADLINE_MS = 5 * 6e4;
3800
5017
  async function deployCommand(options) {
3801
5018
  let stagingDir;
3802
5019
  try {
@@ -3805,7 +5022,7 @@ async function deployCommand(options) {
3805
5022
  });
3806
5023
  console.log(chalk12.gray("Planning deployment..."));
3807
5024
  stagingDir = await createStagingDir();
3808
- const { manifest } = await buildPipeline(
5025
+ const { manifest, catalog: catalog2 } = await buildPipeline(
3809
5026
  config,
3810
5027
  process.cwd(),
3811
5028
  options.minify ?? true,
@@ -3824,13 +5041,17 @@ async function deployCommand(options) {
3824
5041
  projectId: config.projectId
3825
5042
  });
3826
5043
  console.log(chalk12.gray("\nComparing with remote..."));
3827
- const remoteManifest = await client.fetchRemoteManifest(client.projectId());
5044
+ const remoteManifest = await client.fetchRemoteManifest(
5045
+ client.projectId()
5046
+ );
5047
+ const remoteCatalog = await client.fetchRemoteCatalog(client.projectId());
3828
5048
  const diff = diffManifests(manifest, remoteManifest);
3829
- if (!diff.hasChanges) {
5049
+ const catalogDiff = diffCatalogs(catalog2, remoteCatalog);
5050
+ if (!diff.hasChanges && !catalogDiff.hasChanges) {
3830
5051
  console.log(chalk12.green("\n\u2713 No changes to deploy."));
3831
5052
  return;
3832
5053
  }
3833
- console.log("\n" + formatPlan(diff));
5054
+ console.log("\n" + formatPlan(diff, catalogDiff));
3834
5055
  const confirmed = await confirmPlan(options.yes ?? false);
3835
5056
  if (!confirmed) {
3836
5057
  console.log(chalk12.yellow("Deploy cancelled."));
@@ -3839,11 +5060,14 @@ async function deployCommand(options) {
3839
5060
  console.log(chalk12.gray("\nApplying deployment..."));
3840
5061
  const zipPath = join(stagingDir, "deploy.zip");
3841
5062
  await zipBuildOutput(stagingDir, zipPath);
3842
- const revision = await client.deployBundle(zipPath, manifest);
5063
+ const revision = await client.deployBundle(zipPath, manifest, catalog2);
3843
5064
  console.log(
3844
5065
  chalk12.green(`
3845
- \u2713 Deployment complete (revision ${revision.revision}, ${revision.status})`)
5066
+ \u2713 Deployment submitted (revision ${revision.revision}, ${revision.status})`)
3846
5067
  );
5068
+ if (options.watch) {
5069
+ await watchDeployment(client, revision);
5070
+ }
3847
5071
  } catch (err) {
3848
5072
  if (err instanceof CliError) {
3849
5073
  console.error(chalk12.red(err.message));
@@ -3854,6 +5078,72 @@ async function deployCommand(options) {
3854
5078
  await cleanupStagingDir(stagingDir);
3855
5079
  }
3856
5080
  }
5081
+ function colorStatus(status) {
5082
+ switch (status) {
5083
+ case "active":
5084
+ return chalk12.green(status);
5085
+ case "failed":
5086
+ return chalk12.red(status);
5087
+ case "superseded":
5088
+ return chalk12.yellow(status);
5089
+ case "staged":
5090
+ return chalk12.cyan(status);
5091
+ case "draft":
5092
+ default:
5093
+ return chalk12.gray(status);
5094
+ }
5095
+ }
5096
+ async function watchDeployment(client, initial) {
5097
+ if (TERMINAL_STATUSES.has(initial.status)) {
5098
+ return;
5099
+ }
5100
+ console.log(chalk12.gray(`
5101
+ Watching revision ${initial.revision}\u2026`));
5102
+ let lastStatus = initial.status;
5103
+ const startedAt = Date.now();
5104
+ await watch(
5105
+ {
5106
+ poll: async () => {
5107
+ if (Date.now() - startedAt > WATCH_DEADLINE_MS) {
5108
+ throw new Error(
5109
+ `Deployment did not reach a terminal status within ${WATCH_DEADLINE_MS / 1e3}s.`
5110
+ );
5111
+ }
5112
+ const next = await client.deploymentStatus(
5113
+ client.projectId(),
5114
+ initial.revision
5115
+ );
5116
+ return next ? [next] : [];
5117
+ },
5118
+ pollIntervalMs: WATCH_POLL_INTERVAL_MS,
5119
+ getId: (rev) => `${rev.revision}:${rev.status}:${rev.updatedAt}`,
5120
+ isTerminal: (rev) => TERMINAL_STATUSES.has(rev.status)
5121
+ },
5122
+ {
5123
+ onEvent: (rev) => {
5124
+ if (rev.status !== lastStatus) {
5125
+ console.log(
5126
+ ` revision ${rev.revision} \u2192 ${colorStatus(rev.status)}`
5127
+ );
5128
+ lastStatus = rev.status;
5129
+ }
5130
+ }
5131
+ }
5132
+ );
5133
+ if (lastStatus === "active") {
5134
+ console.log(chalk12.green(`
5135
+ \u2713 Deployment active (revision ${initial.revision}).`));
5136
+ } else if (lastStatus === "failed") {
5137
+ console.log(chalk12.red(`
5138
+ \u2717 Deployment failed (revision ${initial.revision}).`));
5139
+ process.exit(1);
5140
+ } else if (lastStatus === "superseded") {
5141
+ console.log(
5142
+ chalk12.yellow(`
5143
+ ! Revision ${initial.revision} was superseded by a later deploy.`)
5144
+ );
5145
+ }
5146
+ }
3857
5147
  var SUCCESS_HTML = `<!DOCTYPE html>
3858
5148
  <html>
3859
5149
  <head><title>IO CLI</title><style>
@@ -4092,10 +5382,10 @@ async function whoamiCommand() {
4092
5382
  var CONFIG_FILE = "io.config.json";
4093
5383
  async function prompt(question) {
4094
5384
  const rl = createInterface({ input: process.stdin, output: process.stdout });
4095
- return new Promise((resolve4) => {
5385
+ return new Promise((resolve5) => {
4096
5386
  rl.question(question, (answer) => {
4097
5387
  rl.close();
4098
- resolve4(answer.trim());
5388
+ resolve5(answer.trim());
4099
5389
  });
4100
5390
  });
4101
5391
  }
@@ -4271,6 +5561,55 @@ async function projectsListCommand() {
4271
5561
  throw err;
4272
5562
  }
4273
5563
  }
5564
+ async function confirmDelete(projectName, projectId) {
5565
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
5566
+ return new Promise((resolve5) => {
5567
+ rl.question(
5568
+ `Delete project "${projectName}" (${projectId})? Type the project name to confirm: `,
5569
+ (answer) => {
5570
+ rl.close();
5571
+ resolve5(answer.trim() === projectName);
5572
+ }
5573
+ );
5574
+ });
5575
+ }
5576
+ async function projectsDeleteCommand(id, options = {}) {
5577
+ try {
5578
+ const config = await loadConfig();
5579
+ const client = createClient(config);
5580
+ const organizationId = options.organizationId ?? await resolveOrganizationId(client);
5581
+ const projects2 = await client.projects(organizationId);
5582
+ const target = projects2.find((p) => p.id === id);
5583
+ if (!target) {
5584
+ throw new ConfigError(
5585
+ `Project "${id}" not found under organization ${organizationId}.`
5586
+ );
5587
+ }
5588
+ if (!options.yes) {
5589
+ const confirmed = await confirmDelete(target.name, target.id);
5590
+ if (!confirmed) {
5591
+ console.log(chalk12.yellow("Delete cancelled (name did not match)."));
5592
+ return;
5593
+ }
5594
+ }
5595
+ const deleted = await client.deleteProject(organizationId, target.id);
5596
+ console.log(chalk12.green(`\u2713 Deleted project: ${deleted.name}`));
5597
+ console.log(chalk12.gray(` ID: ${deleted.id}`));
5598
+ if (config.projectId === deleted.id) {
5599
+ console.log(
5600
+ chalk12.yellow(
5601
+ "\n io.config.json still references this project \u2014 clear the projectId field before redeploying."
5602
+ )
5603
+ );
5604
+ }
5605
+ } catch (err) {
5606
+ if (err instanceof CliError) {
5607
+ console.error(chalk12.red(err.message));
5608
+ process.exit(1);
5609
+ }
5610
+ throw err;
5611
+ }
5612
+ }
4274
5613
  async function projectsCreateCommand(name) {
4275
5614
  try {
4276
5615
  const config = await loadConfig();
@@ -4495,8 +5834,9 @@ async function quotaCommand() {
4495
5834
  throw err;
4496
5835
  }
4497
5836
  }
4498
- Object.assign(globalThis, { WebSocket });
4499
- var ACTIVITY_SUBSCRIPTION = `
5837
+
5838
+ // src/api/subscription.ts
5839
+ var ACTIVITY_EVENT_SUBSCRIPTION = `
4500
5840
  subscription ActivityEventReceived($projectId: ID!, $input: ActivityEventFilterInput) {
4501
5841
  activityEventReceived(projectId: $projectId, input: $input) {
4502
5842
  id
@@ -4517,56 +5857,6 @@ var ACTIVITY_SUBSCRIPTION = `
4517
5857
  }
4518
5858
  }
4519
5859
  `;
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
5860
 
4571
5861
  // src/activity/presentation.ts
4572
5862
  var DISPLAY_NAME_NAMESPACES = /* @__PURE__ */ new Set([
@@ -4892,7 +6182,6 @@ var LEVEL_COLORS = {
4892
6182
  scheduled: chalk12.magenta
4893
6183
  };
4894
6184
  var FOLLOW_POLL_INTERVAL_MS = 2e3;
4895
- var FOLLOW_SEEN_ID_LIMIT = 1e3;
4896
6185
  var FOLLOW_BATCH_LIMIT = 200;
4897
6186
  function colorLevel(level) {
4898
6187
  if (!level) return chalk12.gray("\u2014");
@@ -4943,74 +6232,13 @@ function formatEvent(event) {
4943
6232
  detail ? chalk12.gray(detail) : ""
4944
6233
  ].join(" ").trimEnd();
4945
6234
  }
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) {
6235
+ function followPollInput(input, since) {
4973
6236
  return {
4974
6237
  ...input,
4975
6238
  since,
4976
6239
  limit: Math.max(input?.limit ?? 0, FOLLOW_BATCH_LIMIT)
4977
6240
  };
4978
6241
  }
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
6242
  async function logsCommand(options) {
5015
6243
  try {
5016
6244
  const config = await loadConfig();
@@ -5026,18 +6254,18 @@ async function logsCommand(options) {
5026
6254
  const since = parseSince(options.since);
5027
6255
  if (since) input.since = since;
5028
6256
  }
5029
- const events = await client.projectEvents(pid, input);
5030
- if (events.length === 0 && !options.follow) {
6257
+ const initialEvents = await client.projectEvents(pid, input);
6258
+ if (initialEvents.length === 0 && !options.follow) {
5031
6259
  console.log(chalk12.gray("No events found."));
5032
6260
  return;
5033
6261
  }
5034
- if (events.length > 0) {
6262
+ if (initialEvents.length > 0) {
5035
6263
  console.log(chalk12.bold(`
5036
- Activity Log \u2014 ${events.length} events
6264
+ Activity Log \u2014 ${initialEvents.length} events
5037
6265
  `));
5038
6266
  console.log(formatHeader());
5039
6267
  console.log(chalk12.gray(` ${"\u2500".repeat(92)}`));
5040
- for (const event of events) {
6268
+ for (const event of initialEvents) {
5041
6269
  console.log(formatEvent(event));
5042
6270
  }
5043
6271
  }
@@ -5047,7 +6275,7 @@ Activity Log \u2014 ${events.length} events
5047
6275
  client,
5048
6276
  pid,
5049
6277
  input,
5050
- events,
6278
+ initialEvents,
5051
6279
  followStartedAt ?? (/* @__PURE__ */ new Date()).toISOString()
5052
6280
  );
5053
6281
  } else {
@@ -5067,88 +6295,68 @@ async function tailEvents(apiUrl, client, projectId, input, initialEvents, follo
5067
6295
  throw new ConfigError("Not authenticated. Run `io login` first.");
5068
6296
  }
5069
6297
  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,
6298
+ const initialSince = initialEvents.reduce(
6299
+ (latest, event) => event.occurredAt > latest ? event.occurredAt : latest,
6300
+ followStartedAt
6301
+ );
6302
+ const socketClient = createSocketClient({ apiUrl, credentials: creds });
6303
+ const subscriptionVars = input;
6304
+ let liveStarted = false;
6305
+ try {
6306
+ await watch(
6307
+ {
6308
+ subscribe: (onEvent, onError, onConnected) => {
6309
+ const unsubscribe = socketClient.subscribe(
6310
+ ACTIVITY_EVENT_SUBSCRIPTION,
6311
+ { projectId, input: subscriptionVars },
6312
+ {
6313
+ onStart: onConnected,
6314
+ onResult: (result) => {
6315
+ const event = result.data?.activityEventReceived;
6316
+ if (event) onEvent(event);
6317
+ },
6318
+ onError,
6319
+ onAbort: onError
6320
+ }
6321
+ );
6322
+ return { unsubscribe };
6323
+ },
6324
+ poll: async (since) => {
6325
+ return client.projectEvents(projectId, followPollInput(input, since));
6326
+ },
6327
+ pollIntervalMs: FOLLOW_POLL_INTERVAL_MS,
6328
+ initialSince,
6329
+ getId: (event) => event.id,
6330
+ getCursor: (event) => event.occurredAt,
6331
+ seenIdLimit: 1e3
6332
+ },
6333
+ {
5083
6334
  onEvent: (event) => {
5084
- if (seenIds2.has(event.id)) {
5085
- return;
6335
+ if (!liveStarted) {
6336
+ liveStarted = true;
5086
6337
  }
5087
6338
  console.log(formatEvent(event));
5088
- seenIds2.add(event.id);
5089
- seenOrder2.push(event.id);
5090
- trimSeenIds(seenIds2, seenOrder2);
5091
6339
  },
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
- });
6340
+ onConnected: (mode) => {
6341
+ if (mode === "subscription") {
6342
+ console.log(chalk12.green(" \u25CF Connected \u2014 tailing live events\n"));
6343
+ } else {
6344
+ console.log(
6345
+ chalk12.green(
6346
+ " \u25CF Connected \u2014 polling for live events with your authenticated session\n"
6347
+ )
6348
+ );
6349
+ }
5112
6350
  },
5113
- onError: (error) => {
6351
+ onError: (err) => {
5114
6352
  console.error(chalk12.red(`
5115
- Connection error: ${error.message}`));
5116
- removeSigintHandler2();
5117
- disconnect?.();
5118
- reject(error);
6353
+ Connection error: ${err.message}`));
5119
6354
  }
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
6355
  }
5148
- await wait(FOLLOW_POLL_INTERVAL_MS);
5149
- }
6356
+ );
6357
+ console.log(chalk12.gray("\n Disconnected."));
5150
6358
  } finally {
5151
- removeSigintHandler();
6359
+ socketClient.close();
5152
6360
  }
5153
6361
  }
5154
6362
  function parseSince(value) {
@@ -5181,7 +6389,7 @@ async function signalEmitCommand(name, options) {
5181
6389
  try {
5182
6390
  const config = await loadConfig({ projectId: options.projectId });
5183
6391
  const client = createClient(config);
5184
- const signal = normalizeRuntimeSignalName2(name);
6392
+ const signal = normalizeRuntimeSignalName(name);
5185
6393
  if (!signal) {
5186
6394
  throw new ConfigError(
5187
6395
  "signal name must be a bare event name like smoke.start or use the signal: namespace"
@@ -5197,7 +6405,6 @@ async function signalEmitCommand(name, options) {
5197
6405
  principalId: options.principalId,
5198
6406
  payload,
5199
6407
  metadata,
5200
- correlationId: options.correlationId,
5201
6408
  causationId: options.causationId,
5202
6409
  idempotencyKey: options.idempotencyKey,
5203
6410
  sourceName: options.sourceName
@@ -5223,19 +6430,268 @@ async function signalEmitCommand(name, options) {
5223
6430
  throw err;
5224
6431
  }
5225
6432
  }
6433
+ var POLL_INTERVAL_MS = 2e3;
6434
+ var POLL_BATCH_LIMIT = 200;
6435
+ function formatEvent2(event) {
6436
+ const time = new Date(event.occurredAt).toLocaleTimeString("en-GB", {
6437
+ hour12: false
6438
+ });
6439
+ const status = inferActivityEventStatus(event);
6440
+ const category = inferActivityEventCategory(event);
6441
+ const name = inferActivityEventName(event);
6442
+ const detail = inferActivityEventDetail(event) ?? "";
6443
+ return ` ${chalk12.gray(time)} ${chalk12.cyan(category)} ${name}${detail ? ` ${chalk12.gray(detail)}` : ""}${status ? ` ${chalk12.gray(`(${status})`)}` : ""}`;
6444
+ }
6445
+ async function reconcileCommand(options) {
6446
+ try {
6447
+ const config = await loadConfig();
6448
+ const client = createClient(config);
6449
+ const projectId = options.projectId ?? client.projectId();
6450
+ console.log(chalk12.gray(`Requesting reconciliation for project ${projectId}\u2026`));
6451
+ const accepted = await client.reconcileProject(projectId);
6452
+ if (!accepted) {
6453
+ throw new CliError(
6454
+ "Runtime rejected the reconcile request.",
6455
+ "RECONCILE_REJECTED"
6456
+ );
6457
+ }
6458
+ console.log(chalk12.green("\u2713 Reconcile requested."));
6459
+ if (!options.watch) {
6460
+ return;
6461
+ }
6462
+ const creds = await loadCredentials();
6463
+ if (!creds?.token) {
6464
+ throw new ConfigError("Not authenticated. Run `io login` first.");
6465
+ }
6466
+ console.log(
6467
+ chalk12.gray("\nTailing activity for this project (Ctrl+C to stop)\u2026\n")
6468
+ );
6469
+ const socketClient = createSocketClient({
6470
+ apiUrl: config.apiUrl,
6471
+ credentials: creds
6472
+ });
6473
+ const initialSince = (/* @__PURE__ */ new Date()).toISOString();
6474
+ try {
6475
+ await watch(
6476
+ {
6477
+ subscribe: (onEvent, onError, onConnected) => {
6478
+ const unsubscribe = socketClient.subscribe(
6479
+ ACTIVITY_EVENT_SUBSCRIPTION,
6480
+ { projectId, input: { limit: POLL_BATCH_LIMIT } },
6481
+ {
6482
+ onStart: onConnected,
6483
+ onResult: (result) => {
6484
+ const event = result.data?.activityEventReceived;
6485
+ if (event) onEvent(event);
6486
+ },
6487
+ onError,
6488
+ onAbort: onError
6489
+ }
6490
+ );
6491
+ return { unsubscribe };
6492
+ },
6493
+ poll: async (since) => {
6494
+ return client.projectEvents(projectId, {
6495
+ since,
6496
+ limit: POLL_BATCH_LIMIT
6497
+ });
6498
+ },
6499
+ pollIntervalMs: POLL_INTERVAL_MS,
6500
+ initialSince,
6501
+ getId: (event) => event.id,
6502
+ getCursor: (event) => event.occurredAt
6503
+ },
6504
+ {
6505
+ onEvent: (event) => console.log(formatEvent2(event)),
6506
+ onConnected: (mode) => {
6507
+ if (mode === "subscription") {
6508
+ console.log(chalk12.green(" \u25CF Connected \u2014 tailing live events\n"));
6509
+ } else {
6510
+ console.log(
6511
+ chalk12.green(
6512
+ " \u25CF Connected \u2014 polling for live events with your authenticated session\n"
6513
+ )
6514
+ );
6515
+ }
6516
+ }
6517
+ }
6518
+ );
6519
+ console.log(chalk12.gray("\nDisconnected."));
6520
+ } finally {
6521
+ socketClient.close();
6522
+ }
6523
+ } catch (err) {
6524
+ if (err instanceof CliError) {
6525
+ console.error(chalk12.red(err.message));
6526
+ process.exit(1);
6527
+ }
6528
+ throw err;
6529
+ }
6530
+ }
6531
+ function isRecord4(value) {
6532
+ return !!value && typeof value === "object" && !Array.isArray(value);
6533
+ }
6534
+ function isCatalogShape2(value) {
6535
+ if (!isRecord4(value)) return false;
6536
+ return value.contract_version === "io.catalog.v1" && value.snapshot_version === "io_runtime.snapshot.v2" && typeof value.catalog_hash === "string" && isRecord4(value.resources) && isRecord4(value.signals) && isRecord4(value.tools) && isRecord4(value.schemas);
6537
+ }
6538
+ async function readLocalCatalog(buildDir) {
6539
+ const path = resolve(process.cwd(), buildDir, "catalog.json");
6540
+ let raw;
6541
+ try {
6542
+ raw = await readFile(path, "utf-8");
6543
+ } catch (err) {
6544
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
6545
+ throw new CliError(
6546
+ `Local catalog not found at ${path}. Run \`io build\` or \`io deploy\` first.`,
6547
+ "CATALOG_NOT_FOUND"
6548
+ );
6549
+ }
6550
+ throw err;
6551
+ }
6552
+ let parsed;
6553
+ try {
6554
+ parsed = JSON.parse(raw);
6555
+ } catch (err) {
6556
+ throw new CliError(
6557
+ `Local catalog at ${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
6558
+ "CATALOG_INVALID_JSON"
6559
+ );
6560
+ }
6561
+ if (!isCatalogShape2(parsed)) {
6562
+ throw new CliError(
6563
+ `Local catalog at ${path} does not match the io.catalog.v1 catalog shape.`,
6564
+ "CATALOG_SHAPE_MISMATCH"
6565
+ );
6566
+ }
6567
+ return parsed;
6568
+ }
6569
+ function describeExecutor(executor) {
6570
+ switch (executor.kind) {
6571
+ case "platform_tool":
6572
+ return `${executor.provider_kind}.${executor.backend_kind}.${executor.operation}`;
6573
+ case "stream_publish":
6574
+ return `${executor.provider_kind}.${executor.backend_kind} \u2192 ${executor.destination_ref.slice(0, 19)}\u2026`;
6575
+ case "webhook_response":
6576
+ return "webhook response";
6577
+ case "http_request":
6578
+ return `${executor.method} ${executor.url}`;
6579
+ case "graphql_request":
6580
+ return `POST ${executor.endpoint}${executor.operation_name ? ` (${executor.operation_name})` : ""}`;
6581
+ case "http_operation":
6582
+ return `${executor.method} ${executor.base_url}${executor.path}`;
6583
+ case "graphql_operation":
6584
+ return `POST ${executor.endpoint}${executor.operation_name ? ` (${executor.operation_name})` : ""}`;
6585
+ case "unsupported":
6586
+ return chalk12.red(`unsupported (${executor.reason})`);
6587
+ }
6588
+ }
6589
+ function formatCatalog(catalog2, label) {
6590
+ const lines = [];
6591
+ lines.push(chalk12.bold(`
6592
+ Catalog (${label})`));
6593
+ lines.push(
6594
+ ` ${chalk12.gray("contract_version:")} ${catalog2.contract_version}`
6595
+ );
6596
+ lines.push(
6597
+ ` ${chalk12.gray("snapshot_version:")} ${catalog2.snapshot_version}`
6598
+ );
6599
+ lines.push(` ${chalk12.gray("catalog_hash: ")} ${catalog2.catalog_hash}`);
6600
+ lines.push("");
6601
+ lines.push(chalk12.bold(" Counts"));
6602
+ lines.push(` resources: ${Object.keys(catalog2.resources).length}`);
6603
+ lines.push(` signals: ${Object.keys(catalog2.signals).length}`);
6604
+ lines.push(` tools: ${Object.keys(catalog2.tools).length}`);
6605
+ lines.push(` schemas: ${Object.keys(catalog2.schemas).length}`);
6606
+ const resourceIds = Object.keys(catalog2.resources).sort();
6607
+ if (resourceIds.length > 0) {
6608
+ lines.push("");
6609
+ lines.push(chalk12.bold(" Resources"));
6610
+ for (const id of resourceIds) {
6611
+ const resource = catalog2.resources[id];
6612
+ lines.push(
6613
+ ` ${chalk12.cyan(resource.id)} ${chalk12.gray(
6614
+ `${resource.resource_kind}/${resource.backend_kind} scope=${resource.binding_scope} cred=${resource.credential_source}`
6615
+ )}`
6616
+ );
6617
+ for (const signalId of resource.signals) {
6618
+ const signal = catalog2.signals[signalId];
6619
+ if (!signal) continue;
6620
+ lines.push(
6621
+ ` ${chalk12.magenta("signal")} ${signal.name} ${chalk12.gray(
6622
+ `(${signal.mode}, scope=${signal.scope})`
6623
+ )}`
6624
+ );
6625
+ }
6626
+ for (const toolId of resource.tools) {
6627
+ const tool = catalog2.tools[toolId];
6628
+ if (!tool) continue;
6629
+ lines.push(
6630
+ ` ${chalk12.yellow("tool ")} ${tool.name} ${chalk12.gray(
6631
+ `(${tool.effect}, audit=${tool.audit_requirement}) ${describeExecutor(tool.executor)}`
6632
+ )}`
6633
+ );
6634
+ }
6635
+ }
6636
+ }
6637
+ lines.push("");
6638
+ return lines.join("\n");
6639
+ }
6640
+ async function catalogShowCommand(options) {
6641
+ try {
6642
+ let catalog2;
6643
+ let label;
6644
+ if (options.remote) {
6645
+ const config = await loadConfig();
6646
+ if (!config.apiUrl || !config.projectId) {
6647
+ throw new CliError(
6648
+ "No apiUrl or projectId configured. Run `io login` and set a project first.",
6649
+ "CONFIG_MISSING"
6650
+ );
6651
+ }
6652
+ const client = createClient(config);
6653
+ const remote = await client.fetchRemoteCatalog(client.projectId());
6654
+ if (!remote) {
6655
+ console.log(
6656
+ chalk12.gray("No active deployment catalog for this project yet.")
6657
+ );
6658
+ return;
6659
+ }
6660
+ catalog2 = remote;
6661
+ label = `remote: project ${client.projectId()}`;
6662
+ } else {
6663
+ const buildDir = options.buildDir ?? ".io";
6664
+ catalog2 = await readLocalCatalog(buildDir);
6665
+ label = `local: ${join(buildDir, "catalog.json")}`;
6666
+ }
6667
+ if (options.json) {
6668
+ process.stdout.write(stableStringify(catalog2, 2));
6669
+ process.stdout.write("\n");
6670
+ return;
6671
+ }
6672
+ process.stdout.write(formatCatalog(catalog2, label));
6673
+ } catch (err) {
6674
+ if (err instanceof CliError) {
6675
+ console.error(chalk12.red(err.message));
6676
+ process.exit(1);
6677
+ }
6678
+ throw err;
6679
+ }
6680
+ }
5226
6681
 
5227
6682
  // src/index.ts
5228
6683
  var program = new Command();
5229
- program.name("io").description("IO CLI \u2014 build and deploy behaviors").version("0.1.15");
6684
+ program.name("io").description("IO CLI \u2014 build and deploy behaviors").version("0.1.17");
5230
6685
  program.command("init").description("Initialize io.config.json for the current project").option("--yes", "Overwrite existing config without prompting").action(initCommand);
5231
6686
  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);
6687
+ 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
6688
  program.command("login").description("Authenticate with the IO platform").option("--token <value>", "API token for CI/CD environments").action(loginCommand);
5234
6689
  program.command("logout").description("Remove stored credentials").action(logoutCommand);
5235
6690
  program.command("whoami").description("Show current authentication status").action(whoamiCommand);
5236
6691
  var projects = program.command("projects").description("Manage projects");
5237
6692
  projects.command("list").description("List all projects").action(projectsListCommand);
5238
6693
  projects.command("create <name>").description("Create a new project").action(projectsCreateCommand);
6694
+ 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
6695
  var secrets = program.command("secrets").description("Manage project secrets");
5240
6696
  secrets.command("set <name> <value>").description("Set a secret value").option("--target <targets...>", "Restrict to specific targets").action(secretsSetCommand);
5241
6697
  secrets.command("list").description("List all secrets").action(secretsListCommand);
@@ -5246,9 +6702,12 @@ principals.command("create <external-id>").description("Create a principal in th
5246
6702
  principals.command("update <id>").description("Update a principal").option("--project-id <id>", "Override the configured project ID").option("--external-id <external-id>", "Replace the external ID").option("--state <state>", "Principal state: active, inactive, or archived").option("--metadata <json>", "Replace principal metadata with a JSON object").action(principalsUpdateCommand);
5247
6703
  principals.command("delete <id>").description("Delete a principal").option("--project-id <id>", "Override the configured project ID").action(principalsDeleteCommand);
5248
6704
  var signals = program.command("signals").description("Emit runtime signals");
5249
- 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);
6705
+ 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("--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
6706
  program.command("quota").description("Show current quota usage for the active organization").action(quotaCommand);
5251
6707
  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);
6708
+ 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);
6709
+ var catalog = program.command("catalog").description("Inspect the v2 deployment catalog");
6710
+ 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
6711
  program.parse();
5253
6712
  //# sourceMappingURL=index.js.map
5254
6713
  //# sourceMappingURL=index.js.map