@kaelen-ai/cli 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import chalk11 from 'chalk';
3
+ import chalk12 from 'chalk';
4
4
  import { writeFile, readFile, mkdtemp, rm, mkdir, stat, readdir } from 'fs/promises';
5
5
  import { join, resolve, dirname, relative, basename, extname, isAbsolute } from 'path';
6
6
  import * as acorn from 'acorn';
@@ -9,16 +9,16 @@ import { transform, build } from 'esbuild';
9
9
  import { spawn } from 'child_process';
10
10
  import { createRequire } from 'module';
11
11
  import { URL as URL$1, pathToFileURL } from 'url';
12
+ import { randomBytes, createHash } from 'crypto';
12
13
  import { tmpdir, homedir } from 'os';
13
14
  import { createWriteStream } from 'fs';
14
15
  import archiver from 'archiver';
15
16
  import { createInterface } from 'readline';
16
- import open from 'open';
17
- import { createServer } from 'http';
18
- import { randomBytes, createHash } from 'crypto';
19
17
  import WebSocket from 'ws';
20
18
  import { Socket } from 'phoenix';
21
19
  import * as AbsintheSocket from '@absinthe/socket';
20
+ import open from 'open';
21
+ import { createServer } from 'http';
22
22
 
23
23
  // src/errors.ts
24
24
  var CliError = class extends Error {
@@ -159,7 +159,14 @@ function validChannelPattern(value) {
159
159
 
160
160
  // src/build/parser.ts
161
161
  var SDK_PACKAGES = /* @__PURE__ */ new Set(["@io/sdk", "@kaelen-ai/sdk", "@eleven-am/sdk"]);
162
- var EXECUTION_KEYS = /* @__PURE__ */ new Set(["memory", "timeout", "retries", "concurrency"]);
162
+ var EXECUTION_KEYS = /* @__PURE__ */ new Set([
163
+ "memory",
164
+ "timeout",
165
+ "maxToolCalls",
166
+ "rateBudget",
167
+ "retries",
168
+ "concurrency"
169
+ ]);
163
170
  function loc(node, file) {
164
171
  const position = node.loc?.start;
165
172
  return {
@@ -311,7 +318,7 @@ function extractOnField(propValue, file, sdkImports) {
311
318
  loc(propValue, file)
312
319
  );
313
320
  }
314
- function extractExecutionConfig(properties) {
321
+ function extractExecutionConfig(properties, file) {
315
322
  const config = {};
316
323
  for (const prop of properties) {
317
324
  if (prop.type === "SpreadElement") {
@@ -321,10 +328,7 @@ function extractExecutionConfig(properties) {
321
328
  if (!keyName || !EXECUTION_KEYS.has(keyName)) {
322
329
  continue;
323
330
  }
324
- const value = prop.value;
325
- if (value.type === "Literal") {
326
- config[keyName] = value.value;
327
- }
331
+ config[keyName] = extractStaticValue(prop.value, file);
328
332
  }
329
333
  return Object.keys(config).length > 0 ? config : void 0;
330
334
  }
@@ -364,7 +368,6 @@ function parseBehaviorCall(node, args, ancestors, file, out, sdkImports) {
364
368
  const properties = spec.properties;
365
369
  let onRaw;
366
370
  let hasRun = false;
367
- let messageHistory;
368
371
  for (const prop of properties) {
369
372
  if (prop.type === "SpreadElement") {
370
373
  continue;
@@ -377,23 +380,6 @@ function parseBehaviorCall(node, args, ancestors, file, out, sdkImports) {
377
380
  onRaw = extractOnField(prop.value, file, sdkImports);
378
381
  } else if (keyName === "run") {
379
382
  hasRun = true;
380
- } else if (keyName === "trackingId") {
381
- const value = prop.value;
382
- if (value.type !== "ArrowFunctionExpression" && value.type !== "FunctionExpression") {
383
- throw new BuildError(
384
- `behavior '${name}' trackingId must be a function in ${file}:${location.line}`,
385
- location
386
- );
387
- }
388
- } else if (keyName === "messageHistory") {
389
- const value = prop.value;
390
- if (value.type !== "Literal" || typeof value.value !== "number" || !Number.isInteger(value.value) || value.value <= 0) {
391
- throw new BuildError(
392
- `behavior '${name}' messageHistory must be a positive integer literal in ${file}:${location.line}`,
393
- location
394
- );
395
- }
396
- messageHistory = value.value;
397
383
  }
398
384
  }
399
385
  if (!onRaw) {
@@ -402,14 +388,13 @@ function parseBehaviorCall(node, args, ancestors, file, out, sdkImports) {
402
388
  if (!hasRun) {
403
389
  throw new BuildError(`behavior '${name}' is missing a run function`, location);
404
390
  }
405
- const execution = extractExecutionConfig(properties);
391
+ const execution = extractExecutionConfig(properties, file);
406
392
  const bounds = findStatementBounds(ancestors);
407
393
  out.push({
408
394
  name,
409
395
  onRaw,
410
396
  on: "",
411
397
  execution,
412
- messageHistory,
413
398
  location,
414
399
  statementStart: bounds.start,
415
400
  statementEnd: bounds.end
@@ -490,7 +475,7 @@ function parseConversationCall(node, args, ancestors, file, out) {
490
475
  location
491
476
  );
492
477
  }
493
- const execution = extractExecutionConfig(properties);
478
+ const execution = extractExecutionConfig(properties, file);
494
479
  const bounds = findStatementBounds(ancestors);
495
480
  out.push({
496
481
  name,
@@ -565,11 +550,7 @@ var MEMORY_EVENT_MAP = {
565
550
  onTensionEmerged: "knowledge:tension.emerged",
566
551
  onTensionEscalated: "knowledge:tension.escalated",
567
552
  onTrajectoryDeteriorated: "knowledge:trajectory.deteriorated",
568
- onRoutineBroken: "knowledge:routine.broken",
569
- onContradiction: "knowledge:contradiction",
570
- onDeadlinePassed: "knowledge:deadline_passed",
571
- onIntentionExpired: "knowledge:intention_expired",
572
- onTemporalDueSoon: "knowledge:temporal_due_soon"
553
+ onRoutineBroken: "knowledge:routine.broken"
573
554
  };
574
555
  function resolveAll(files) {
575
556
  const signalVarMap = /* @__PURE__ */ new Map();
@@ -854,6 +835,228 @@ function validateEndpointsSection(endpoints, errors) {
854
835
  var RESERVED_BUNDLE_PREFIX = "__kaelen.";
855
836
  var BINDING_SCOPES = /* @__PURE__ */ new Set(["project", "principal"]);
856
837
  var CREDENTIAL_SOURCES = /* @__PURE__ */ new Set(["none", "platform", "secret_bundle"]);
838
+ var TARGET_EXECUTION_KEYS = /* @__PURE__ */ new Set([
839
+ "applications",
840
+ "endpoints",
841
+ "secret_names",
842
+ "timeout_ms",
843
+ "memory_mb",
844
+ "max_tool_calls",
845
+ "rate_budget"
846
+ ]);
847
+ var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
848
+ "authorization",
849
+ "proxy-authorization",
850
+ "proxy-authenticate",
851
+ "www-authenticate",
852
+ "cookie",
853
+ "set-cookie",
854
+ "x-api-key",
855
+ "x-auth-token",
856
+ "x-access-token",
857
+ "x-secret",
858
+ "x-secret-key",
859
+ "x-amz-security-token"
860
+ ]);
861
+ var HTTP_LIKE_KINDS = /* @__PURE__ */ new Set(["http", "graphql", "httpApi", "graphqlApi"]);
862
+ var OP_RESOURCE_KINDS = /* @__PURE__ */ new Set(["httpApi", "graphqlApi"]);
863
+ function countPlaceholders(format) {
864
+ let count = 0;
865
+ let idx = 0;
866
+ while ((idx = format.indexOf("{}", idx)) !== -1) {
867
+ count++;
868
+ idx += 2;
869
+ }
870
+ return count;
871
+ }
872
+ function validateStaticHeaders(owner, headers, errors) {
873
+ if (headers === void 0) return;
874
+ if (!isRecord2(headers)) {
875
+ errors.push(`${owner} headers must be an object`);
876
+ return;
877
+ }
878
+ for (const name of Object.keys(headers)) {
879
+ if (SECRET_HEADER_NAMES.has(name.toLowerCase())) {
880
+ errors.push(
881
+ `${owner} headers entry "${name}" is secret-bearing; declare it via credentialHeaders instead`
882
+ );
883
+ }
884
+ }
885
+ }
886
+ function validateCredentialHeadersBlock(owner, declared, errors, secretBundleNames, resourceSecretBundle, secretBundleSlots) {
887
+ if (declared === void 0) return;
888
+ if (!Array.isArray(declared)) {
889
+ errors.push(`${owner} credentialHeaders must be an array`);
890
+ return;
891
+ }
892
+ for (const entry of declared) {
893
+ if (!isRecord2(entry)) {
894
+ errors.push(`${owner} credentialHeaders entry must be an object`);
895
+ continue;
896
+ }
897
+ const name = stringField2(entry, "name");
898
+ if (!name) {
899
+ errors.push(`${owner} credentialHeaders entry is missing a string name`);
900
+ continue;
901
+ }
902
+ if (SECRET_HEADER_NAMES.has(name.toLowerCase())) ;
903
+ const source = entry.source;
904
+ if (!isRecord2(source)) {
905
+ errors.push(
906
+ `${owner} credentialHeaders "${name}" source must be an object`
907
+ );
908
+ continue;
909
+ }
910
+ const kind = stringField2(source, "kind");
911
+ if (kind === "credential") {
912
+ const field = stringField2(source, "field");
913
+ if (!field) {
914
+ errors.push(
915
+ `${owner} credentialHeaders "${name}" credential source missing field`
916
+ );
917
+ continue;
918
+ }
919
+ if (resourceSecretBundle === void 0) {
920
+ errors.push(
921
+ `${owner} credentialHeaders "${name}" uses credential source but resource declares no auth/secret_bundle`
922
+ );
923
+ } else if (!resourceSecretBundle.startsWith(RESERVED_BUNDLE_PREFIX)) {
924
+ const slots = secretBundleSlots.get(resourceSecretBundle);
925
+ if (slots && !slots.has(field)) {
926
+ errors.push(
927
+ `${owner} credentialHeaders "${name}" credential field "${field}" is not declared in secret bundle "${resourceSecretBundle}"`
928
+ );
929
+ }
930
+ }
931
+ } else if (kind === "rawSecret" || kind === "raw_secret") {
932
+ const secretName = stringField2(source, "name");
933
+ if (!secretName) {
934
+ errors.push(
935
+ `${owner} credentialHeaders "${name}" raw_secret source missing name`
936
+ );
937
+ continue;
938
+ }
939
+ if (!secretName.startsWith(RESERVED_BUNDLE_PREFIX) && !secretBundleNames.has(secretName)) {
940
+ errors.push(
941
+ `${owner} credentialHeaders "${name}" references unknown raw_secret "${secretName}"`
942
+ );
943
+ }
944
+ } else {
945
+ errors.push(
946
+ `${owner} credentialHeaders "${name}" source.kind must be credential or rawSecret`
947
+ );
948
+ continue;
949
+ }
950
+ const format = entry.format;
951
+ if (format !== void 0) {
952
+ if (typeof format !== "string" || countPlaceholders(format) !== 1) {
953
+ errors.push(
954
+ `${owner} credentialHeaders "${name}" format must contain exactly one {} placeholder`
955
+ );
956
+ }
957
+ }
958
+ }
959
+ }
960
+ function validateRequestMappingBlock(owner, declared, errors) {
961
+ if (declared === void 0) return;
962
+ if (!isRecord2(declared)) {
963
+ errors.push(`${owner} requestMapping must be an object`);
964
+ return;
965
+ }
966
+ for (const key of ["pathParams", "query"]) {
967
+ const value = declared[key];
968
+ if (value === void 0) continue;
969
+ if (!Array.isArray(value)) {
970
+ errors.push(`${owner} requestMapping.${key} must be an array`);
971
+ continue;
972
+ }
973
+ for (const entry of value) {
974
+ if (typeof entry === "string") {
975
+ if (!entry.trim()) {
976
+ errors.push(
977
+ `${owner} requestMapping.${key} entries must be non-empty strings or {from, as?} objects`
978
+ );
979
+ }
980
+ continue;
981
+ }
982
+ if (isRecord2(entry)) {
983
+ if (!stringField2(entry, "from")) {
984
+ errors.push(
985
+ `${owner} requestMapping.${key} entry.from must be a non-empty string`
986
+ );
987
+ continue;
988
+ }
989
+ if (entry.as !== void 0 && (typeof entry.as !== "string" || !entry.as.trim())) {
990
+ errors.push(
991
+ `${owner} requestMapping.${key} entry.as must be a non-empty string when provided`
992
+ );
993
+ }
994
+ continue;
995
+ }
996
+ errors.push(
997
+ `${owner} requestMapping.${key} entries must be strings or {from, as?} objects`
998
+ );
999
+ }
1000
+ }
1001
+ if (declared.body !== void 0 && typeof declared.body !== "string" && declared.body !== null) {
1002
+ errors.push(`${owner} requestMapping.body must be a string or null`);
1003
+ }
1004
+ }
1005
+ function validateHttpLikeConfig(appName, resourceName, resourceKind, config, secretBundleNames, resourceSecretBundle, secretBundleSlots, errors) {
1006
+ if (!HTTP_LIKE_KINDS.has(resourceKind)) return;
1007
+ if (!isRecord2(config)) return;
1008
+ const base = `resource "${appName}/${resourceName}"`;
1009
+ validateStaticHeaders(base, config.headers, errors);
1010
+ validateCredentialHeadersBlock(
1011
+ base,
1012
+ config.credentialHeaders,
1013
+ errors,
1014
+ secretBundleNames,
1015
+ resourceSecretBundle,
1016
+ secretBundleSlots
1017
+ );
1018
+ if (resourceKind === "http") {
1019
+ validateRequestMappingBlock(base, config.requestMapping, errors);
1020
+ }
1021
+ if (resourceKind === "graphql") {
1022
+ if (config.variablesMapping !== void 0 && typeof config.variablesMapping !== "string") {
1023
+ errors.push(`${base} variablesMapping must be a string`);
1024
+ }
1025
+ }
1026
+ if (OP_RESOURCE_KINDS.has(resourceKind)) {
1027
+ const ops = config.operations;
1028
+ if (ops !== void 0 && !isRecord2(ops)) {
1029
+ errors.push(`${base} operations must be an object`);
1030
+ return;
1031
+ }
1032
+ if (isRecord2(ops)) {
1033
+ for (const [opName, opSpec] of Object.entries(ops)) {
1034
+ const owner = `${base} operation "${opName}"`;
1035
+ if (!isRecord2(opSpec)) {
1036
+ errors.push(`${owner} must be an object`);
1037
+ continue;
1038
+ }
1039
+ validateStaticHeaders(owner, opSpec.headers, errors);
1040
+ validateCredentialHeadersBlock(
1041
+ owner,
1042
+ opSpec.credentialHeaders,
1043
+ errors,
1044
+ secretBundleNames,
1045
+ resourceSecretBundle,
1046
+ secretBundleSlots
1047
+ );
1048
+ if (resourceKind === "httpApi") {
1049
+ validateRequestMappingBlock(owner, opSpec.requestMapping, errors);
1050
+ }
1051
+ if (resourceKind === "graphqlApi") {
1052
+ if (opSpec.variablesMapping !== void 0 && typeof opSpec.variablesMapping !== "string") {
1053
+ errors.push(`${owner} variablesMapping must be a string`);
1054
+ }
1055
+ }
1056
+ }
1057
+ }
1058
+ }
1059
+ }
857
1060
  function isRecord2(value) {
858
1061
  return !!value && typeof value === "object" && !Array.isArray(value);
859
1062
  }
@@ -865,12 +1068,6 @@ function stringField2(record, key) {
865
1068
  const value = record[key];
866
1069
  return typeof value === "string" && value.trim() ? value : void 0;
867
1070
  }
868
- function scopeListField(record, key) {
869
- const value = record[key];
870
- if (value === void 0) return void 0;
871
- if (!Array.isArray(value)) return [];
872
- return value.flatMap((entry) => scopeEntryName(entry) ?? []);
873
- }
874
1071
  function scopeEntryName(entry) {
875
1072
  if (typeof entry === "string" && entry.trim()) return entry;
876
1073
  if (isRecord2(entry)) return stringField2(entry, "name");
@@ -916,6 +1113,11 @@ function validateTargetExecution(target, declaredApps, declaredEndpoints, errors
916
1113
  return;
917
1114
  }
918
1115
  const executionRecord = isRecord2(execution) ? execution : {};
1116
+ for (const key of Object.keys(executionRecord)) {
1117
+ if (!TARGET_EXECUTION_KEYS.has(key)) {
1118
+ errors.push(`${owner} execution contains unsupported key "${key}"`);
1119
+ }
1120
+ }
919
1121
  const applicationRefs = validateScopeList(
920
1122
  owner,
921
1123
  "execution.applications",
@@ -938,31 +1140,60 @@ function validateTargetExecution(target, declaredApps, declaredEndpoints, errors
938
1140
  errors.push(`${owner} references undeclared endpoint "${ref}"`);
939
1141
  }
940
1142
  }
941
- for (const field of ["secret_names", "raw_secrets"]) {
942
- const refs = validateScopeList(owner, `execution.${field}`, executionRecord[field], errors);
943
- for (const ref of refs) {
944
- if (ref.startsWith(RESERVED_BUNDLE_PREFIX)) {
945
- errors.push(
946
- `${owner} execution.${field} entry "${ref}" uses the reserved "${RESERVED_BUNDLE_PREFIX}" prefix`
947
- );
948
- }
1143
+ const secretRefs = validateScopeList(
1144
+ owner,
1145
+ "execution.secret_names",
1146
+ executionRecord.secret_names,
1147
+ errors
1148
+ );
1149
+ for (const ref of secretRefs) {
1150
+ if (ref.startsWith(RESERVED_BUNDLE_PREFIX)) {
1151
+ errors.push(
1152
+ `${owner} execution.secret_names entry "${ref}" uses the reserved "${RESERVED_BUNDLE_PREFIX}" prefix`
1153
+ );
949
1154
  }
950
1155
  }
951
- const legacyEndpointRefs = scopeListField(target, "endpoints");
952
- if (target.endpoints !== void 0 && legacyEndpointRefs === void 0) {
953
- errors.push(`${owner} endpoints must be an array of strings`);
954
- } else if (target.endpoints !== void 0) {
955
- if (endpointRefs.length === 0) {
956
- errors.push(`${owner} must declare endpoint scope under execution.endpoints`);
957
- }
958
- for (const ref of legacyEndpointRefs ?? []) {
959
- if (!declaredEndpoints.has(ref)) {
960
- errors.push(`${owner} references undeclared endpoint "${ref}"`);
961
- }
962
- if (endpointRefs.length > 0 && !endpointRefs.includes(ref)) {
963
- errors.push(`${owner} legacy endpoints entry "${ref}" is missing from execution.endpoints`);
964
- }
1156
+ validatePositiveInteger(owner, "execution.timeout_ms", executionRecord.timeout_ms, errors);
1157
+ validatePositiveInteger(owner, "execution.memory_mb", executionRecord.memory_mb, errors);
1158
+ validateNonNegativeInteger(owner, "execution.max_tool_calls", executionRecord.max_tool_calls, errors);
1159
+ validateRateBudget(owner, executionRecord.rate_budget, errors);
1160
+ if (target.endpoints !== void 0) {
1161
+ errors.push(`${owner} endpoints is no longer supported; use execution.endpoints`);
1162
+ }
1163
+ }
1164
+ function validateRateBudget(owner, value, errors) {
1165
+ if (value === void 0) return;
1166
+ if (!isRecord2(value)) {
1167
+ errors.push(`${owner} execution.rate_budget must be an object`);
1168
+ return;
1169
+ }
1170
+ validateBudgetMap(owner, "execution.rate_budget.per_application", value.per_application, errors);
1171
+ validateBudgetMap(owner, "execution.rate_budget.per_capability", value.per_capability, errors);
1172
+ }
1173
+ function validateBudgetMap(owner, field, value, errors) {
1174
+ if (value === void 0) return;
1175
+ if (!isRecord2(value)) {
1176
+ errors.push(`${owner} ${field} must be an object`);
1177
+ return;
1178
+ }
1179
+ for (const [key, budget] of Object.entries(value)) {
1180
+ if (!key.trim()) {
1181
+ errors.push(`${owner} ${field} keys must be non-empty strings`);
1182
+ continue;
965
1183
  }
1184
+ validateNonNegativeInteger(owner, `${field}.${key}`, budget, errors);
1185
+ }
1186
+ }
1187
+ function validatePositiveInteger(owner, field, value, errors) {
1188
+ if (value === void 0) return;
1189
+ if (!Number.isInteger(value) || value <= 0) {
1190
+ errors.push(`${owner} ${field} must be a positive integer`);
1191
+ }
1192
+ }
1193
+ function validateNonNegativeInteger(owner, field, value, errors) {
1194
+ if (value === void 0) return;
1195
+ if (!Number.isInteger(value) || value < 0) {
1196
+ errors.push(`${owner} ${field} must be a non-negative integer`);
966
1197
  }
967
1198
  }
968
1199
  function capabilityId(applicationName, resourceName, toolName) {
@@ -1056,11 +1287,11 @@ function validateManifest(manifest) {
1056
1287
  const root = isRecord2(manifest) ? manifest : {};
1057
1288
  const errors = [];
1058
1289
  const seenCapabilities = /* @__PURE__ */ new Set();
1059
- if (root.contract_version !== "io.invocation.v1") {
1060
- errors.push("contract_version must be io.invocation.v1");
1290
+ if (root.contract_version !== "io.invocation.v2") {
1291
+ errors.push("contract_version must be io.invocation.v2");
1061
1292
  }
1062
- if (root.snapshot_version !== "io_runtime.snapshot.v1") {
1063
- errors.push("snapshot_version must be io_runtime.snapshot.v1");
1293
+ if (root.snapshot_version !== "io_runtime.snapshot.v2") {
1294
+ errors.push("snapshot_version must be io_runtime.snapshot.v2");
1064
1295
  }
1065
1296
  const secretBundles = arrayField(root, "secret_bundles").filter(isRecord2);
1066
1297
  duplicateNames(secretBundles, "secret bundle", errors);
@@ -1075,6 +1306,14 @@ function validateManifest(manifest) {
1075
1306
  const secretNames = new Set(
1076
1307
  secretBundles.map((entry) => stringField2(entry, "name")).filter((name) => !!name)
1077
1308
  );
1309
+ const secretBundleSlots = /* @__PURE__ */ new Map();
1310
+ for (const bundle of secretBundles) {
1311
+ const bundleName = stringField2(bundle, "name");
1312
+ if (!bundleName) continue;
1313
+ const slots = bundle.slots;
1314
+ if (!isRecord2(slots)) continue;
1315
+ secretBundleSlots.set(bundleName, new Set(Object.keys(slots)));
1316
+ }
1078
1317
  const declaredEndpoints = validateEndpointsSection(
1079
1318
  arrayField(root, "endpoints"),
1080
1319
  errors
@@ -1121,6 +1360,17 @@ function validateManifest(manifest) {
1121
1360
  } else {
1122
1361
  validateResourceCapability(appName, resourceName, capability, errors);
1123
1362
  }
1363
+ const resourceKindForConfig = stringField2(resource, "kind") ?? "";
1364
+ validateHttpLikeConfig(
1365
+ appName,
1366
+ resourceName,
1367
+ resourceKindForConfig,
1368
+ resource.config,
1369
+ secretNames,
1370
+ secretBundle,
1371
+ secretBundleSlots,
1372
+ errors
1373
+ );
1124
1374
  const tools = toolEntries.filter(isRecord2);
1125
1375
  duplicateNames(tools, `resource "${appName}/${resourceName}" tool`, errors);
1126
1376
  for (const tool of tools) {
@@ -1588,6 +1838,12 @@ var RUNTIME_MEMORY_SIGNALS = {
1588
1838
  onTrajectoryDeteriorated: "knowledge:trajectory.deteriorated",
1589
1839
  onRoutineBroken: "knowledge:routine.broken"
1590
1840
  };
1841
+ var RUNTIME_SDK_PASSTHROUGH_EXPORTS = /* @__PURE__ */ new Set([
1842
+ "google",
1843
+ "microsoft",
1844
+ "httpApi",
1845
+ "graphqlApi"
1846
+ ]);
1591
1847
  var RUNTIME_SDK_EXPORTS = /* @__PURE__ */ new Set([
1592
1848
  "ioSchema",
1593
1849
  "zodSchema",
@@ -1608,7 +1864,8 @@ var RUNTIME_SDK_EXPORTS = /* @__PURE__ */ new Set([
1608
1864
  "graphql",
1609
1865
  "http",
1610
1866
  "calendar",
1611
- "contacts"
1867
+ "contacts",
1868
+ ...RUNTIME_SDK_PASSTHROUGH_EXPORTS
1612
1869
  ]);
1613
1870
  var COMPILED_AWAY_EXPORTS = /* @__PURE__ */ new Set(["behavior", "conversation"]);
1614
1871
  function normalizeSdkSpecifier(specifier) {
@@ -1870,6 +2127,29 @@ ${entries.map(([key, value]) => ` ${key}: Object.freeze({ name: ${JSON.stringif
1870
2127
  }`
1871
2128
  );
1872
2129
  }
2130
+ const needsPassthroughHelper = [...RUNTIME_SDK_PASSTHROUGH_EXPORTS].some(
2131
+ (name) => usage.exports.has(name)
2132
+ );
2133
+ if (needsPassthroughHelper) {
2134
+ lines.push(
2135
+ `function passthroughFrozen(options) {
2136
+ if (options && typeof options === "object" && !Array.isArray(options)) {
2137
+ return Object.freeze({ ...options });
2138
+ }
2139
+ return Object.freeze({});
2140
+ }`
2141
+ );
2142
+ for (const passthrough of RUNTIME_SDK_PASSTHROUGH_EXPORTS) {
2143
+ if (!usage.exports.has(passthrough)) {
2144
+ continue;
2145
+ }
2146
+ lines.push(
2147
+ `export function ${passthrough}(options) {
2148
+ return passthroughFrozen(options);
2149
+ }`
2150
+ );
2151
+ }
2152
+ }
1873
2153
  return lines.join("\n\n");
1874
2154
  }
1875
2155
  function sdkAliasPlugin() {
@@ -2208,65 +2488,514 @@ async function generateManifest(input) {
2208
2488
  );
2209
2489
  return m;
2210
2490
  }
2211
- var INTERNAL_CONFIG = "__io_cli_compiled_config";
2212
- var INTERNAL_RUN = "__io_cli_compiled_run";
2213
- var INTERNAL_TRACKING_ID = "__io_cli_compiled_tracking_id";
2214
- var INTERNAL_ON_START = "__io_cli_compiled_on_start";
2215
- var INTERNAL_ON_MESSAGE = "__io_cli_compiled_on_message";
2216
- var INTERNAL_ON_END = "__io_cli_compiled_on_end";
2217
- var INTERNAL_APPLICATIONS = "__io_cli_compiled_applications";
2218
- function conversationChannel(name) {
2219
- return `session:${name}`;
2220
- }
2221
- function parseModule2(source) {
2222
- return acorn.parse(source, {
2223
- ecmaVersion: "latest",
2224
- sourceType: "module"
2225
- });
2491
+
2492
+ // src/build/stable-json.ts
2493
+ function stableStringify(value, indent) {
2494
+ return JSON.stringify(canonicalize(value), null, indent);
2226
2495
  }
2227
- function rootStatementFor(source, statementStart, kind, name) {
2228
- let ast;
2229
- try {
2230
- ast = parseModule2(source);
2231
- } catch (error) {
2232
- throw new BuildError(
2233
- `failed to parse compiled target source: ${error instanceof Error ? error.message : String(error)}`
2234
- );
2496
+ function canonicalize(value) {
2497
+ if (value === null) return null;
2498
+ if (Array.isArray(value)) {
2499
+ return value.map(canonicalize);
2235
2500
  }
2236
- const body = ast.body;
2237
- const stmt = body.find(
2238
- (candidate) => candidate.start === statementStart
2239
- );
2240
- if (stmt) {
2241
- return stmt;
2501
+ if (typeof value === "object") {
2502
+ const obj = value;
2503
+ const sorted = {};
2504
+ for (const key of Object.keys(obj).sort()) {
2505
+ sorted[key] = canonicalize(obj[key]);
2506
+ }
2507
+ return sorted;
2242
2508
  }
2243
- const fallback = body.find((candidate) => {
2244
- if (candidate.type !== "ExpressionStatement") {
2245
- return false;
2509
+ return value;
2510
+ }
2511
+
2512
+ // src/build/catalog.ts
2513
+ var CATALOG_CONTRACT_VERSION = "io.invocation.v2";
2514
+ var CATALOG_SNAPSHOT_VERSION = "io_runtime.snapshot.v2";
2515
+ var WEBHOOK_RESOURCE_KIND = "webhook";
2516
+ var PLATFORM_TOOL_KINDS = /* @__PURE__ */ new Set(["email", "calendar", "contacts"]);
2517
+ var STREAM_PUBLISH_KINDS = /* @__PURE__ */ new Set([
2518
+ "kafka",
2519
+ "rabbitmq",
2520
+ "redis",
2521
+ "pubsub",
2522
+ "sqs",
2523
+ "websocket"
2524
+ ]);
2525
+ function sha256Hex(value) {
2526
+ return `sha256:${createHash("sha256").update(stableStringify(value)).digest("hex")}`;
2527
+ }
2528
+ function isRecord3(value) {
2529
+ return !!value && typeof value === "object" && !Array.isArray(value);
2530
+ }
2531
+ function getString(record, key) {
2532
+ const value = record[key];
2533
+ return typeof value === "string" ? value : void 0;
2534
+ }
2535
+ function getRecord(record, key) {
2536
+ const value = record[key];
2537
+ return isRecord3(value) ? value : void 0;
2538
+ }
2539
+ function getArray(record, key) {
2540
+ const value = record[key];
2541
+ return Array.isArray(value) ? value : [];
2542
+ }
2543
+ function getNumber(record, key) {
2544
+ const value = record[key];
2545
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2546
+ }
2547
+ function countPlaceholders2(format) {
2548
+ let count = 0;
2549
+ let idx = 0;
2550
+ while ((idx = format.indexOf("{}", idx)) !== -1) {
2551
+ count++;
2552
+ idx += 2;
2553
+ }
2554
+ return count;
2555
+ }
2556
+ function deriveAuditRequirement(effect) {
2557
+ return effect === "write" ? "pre_call" : "finish_only";
2558
+ }
2559
+ function deriveEffect(toolMode) {
2560
+ return toolMode === "read" ? "read" : "write";
2561
+ }
2562
+ function normalizeCredentialHeaders(declared, resourceId, context, warnings) {
2563
+ if (!Array.isArray(declared)) return [];
2564
+ const out = [];
2565
+ for (const entry of declared) {
2566
+ if (!isRecord3(entry)) {
2567
+ warnings.push(`${context}: skipped non-object credential header entry`);
2568
+ continue;
2246
2569
  }
2247
- const expression = candidate.expression;
2248
- if (expression.type !== "CallExpression") {
2249
- return false;
2570
+ const name = getString(entry, "name");
2571
+ if (!name) {
2572
+ warnings.push(`${context}: credential header missing name`);
2573
+ continue;
2250
2574
  }
2251
- const args = expression.arguments;
2252
- if (args.length < 2 || args[0]?.type !== "Literal" || args[1]?.type !== "ObjectExpression") {
2253
- return false;
2575
+ const source = getRecord(entry, "source");
2576
+ if (!source) {
2577
+ warnings.push(`${context}: credential header "${name}" missing source`);
2578
+ continue;
2254
2579
  }
2255
- const callee = expression.callee;
2256
- if (callee.type !== "Identifier") {
2257
- return false;
2580
+ const sourceKind = getString(source, "kind");
2581
+ let normalizedSource;
2582
+ if (sourceKind === "credential") {
2583
+ const field = getString(source, "field");
2584
+ if (!field) {
2585
+ warnings.push(
2586
+ `${context}: credential header "${name}" credential source missing field`
2587
+ );
2588
+ continue;
2589
+ }
2590
+ normalizedSource = { kind: "credential", resource_id: resourceId, field };
2591
+ } else if (sourceKind === "rawSecret" || sourceKind === "raw_secret") {
2592
+ const secretName = getString(source, "name");
2593
+ if (!secretName) {
2594
+ warnings.push(
2595
+ `${context}: credential header "${name}" raw_secret source missing name`
2596
+ );
2597
+ continue;
2598
+ }
2599
+ normalizedSource = { kind: "raw_secret", name: secretName };
2600
+ } else {
2601
+ warnings.push(
2602
+ `${context}: credential header "${name}" has unsupported source kind "${sourceKind}"`
2603
+ );
2604
+ continue;
2258
2605
  }
2259
- return callee.name === kind && args[0].value === name;
2260
- });
2261
- if (!fallback) {
2262
- throw new BuildError(
2263
- `failed to locate compiled ${kind} root '${name}' at byte offset ${statementStart}`
2264
- );
2606
+ const format = getString(entry, "format") ?? "{}";
2607
+ if (countPlaceholders2(format) !== 1) {
2608
+ warnings.push(
2609
+ `${context}: credential header "${name}" format must contain exactly one {} placeholder`
2610
+ );
2611
+ continue;
2612
+ }
2613
+ out.push({ name, source: normalizedSource, format });
2265
2614
  }
2266
- return fallback;
2615
+ return out;
2267
2616
  }
2268
- function rootSpecNode(stmt) {
2269
- if (stmt.type !== "ExpressionStatement") {
2617
+ function normalizeRequestMapping(declared) {
2618
+ if (!isRecord3(declared)) {
2619
+ return { path_params: [], query: [] };
2620
+ }
2621
+ const pathParams = collectMappingValues(declared["pathParams"]);
2622
+ const query = collectMappingValues(declared["query"]);
2623
+ const body = declared["body"];
2624
+ const bodyValue = typeof body === "string" ? body : body === null ? null : void 0;
2625
+ return {
2626
+ path_params: pathParams,
2627
+ query,
2628
+ ...bodyValue !== void 0 ? { body: bodyValue } : {}
2629
+ };
2630
+ }
2631
+ function collectMappingValues(value) {
2632
+ if (!Array.isArray(value)) return [];
2633
+ const out = [];
2634
+ for (const entry of value) {
2635
+ if (typeof entry === "string" && entry.trim()) {
2636
+ out.push(entry);
2637
+ } else if (isRecord3(entry)) {
2638
+ const from = getString(entry, "from");
2639
+ if (!from?.trim()) continue;
2640
+ const alias = getString(entry, "as");
2641
+ out.push(alias?.trim() ? { from, as: alias } : from);
2642
+ }
2643
+ }
2644
+ return out;
2645
+ }
2646
+ function buildExecutor(ctx) {
2647
+ if (PLATFORM_TOOL_KINDS.has(ctx.resourceKind)) {
2648
+ return {
2649
+ kind: "platform_tool",
2650
+ provider_kind: ctx.providerKind,
2651
+ backend_kind: ctx.backendKind,
2652
+ operation: ctx.toolName
2653
+ };
2654
+ }
2655
+ if (ctx.resourceKind === WEBHOOK_RESOURCE_KIND && ctx.toolName === "respond") {
2656
+ return { kind: "webhook_response" };
2657
+ }
2658
+ if (STREAM_PUBLISH_KINDS.has(ctx.resourceKind)) {
2659
+ return {
2660
+ kind: "stream_publish",
2661
+ provider_kind: ctx.providerKind,
2662
+ backend_kind: ctx.backendKind,
2663
+ destination_ref: ctx.configHash
2664
+ };
2665
+ }
2666
+ if (ctx.resourceKind === "http") {
2667
+ return buildHttpRequestExecutor(ctx);
2668
+ }
2669
+ if (ctx.resourceKind === "graphql") {
2670
+ return buildGraphQlRequestExecutor(ctx);
2671
+ }
2672
+ if (ctx.resourceKind === "httpApi") {
2673
+ return buildHttpOperationExecutor(ctx);
2674
+ }
2675
+ if (ctx.resourceKind === "graphqlApi") {
2676
+ return buildGraphQlOperationExecutor(ctx);
2677
+ }
2678
+ return {
2679
+ kind: "unsupported",
2680
+ reason: `unknown_resource_kind:${ctx.resourceKind}`
2681
+ };
2682
+ }
2683
+ function buildHttpRequestExecutor(ctx) {
2684
+ const config = ctx.resourceConfig;
2685
+ const url = getString(config, "url") ?? "";
2686
+ const method = (getString(config, "method") ?? "GET").toUpperCase();
2687
+ const headers = getRecord(config, "headers") ?? {};
2688
+ const queryParams = getRecord(config, "queryParams") ?? {};
2689
+ const body = config["body"];
2690
+ const credentialHeaders = normalizeCredentialHeaders(
2691
+ config["credentialHeaders"],
2692
+ ctx.resourceId,
2693
+ `tool "${ctx.resourceId}.${ctx.toolName}"`,
2694
+ ctx.warnings
2695
+ );
2696
+ return {
2697
+ kind: "http_request",
2698
+ url,
2699
+ method,
2700
+ headers_ref: ctx.registerBlob(headers),
2701
+ credential_headers: credentialHeaders,
2702
+ query_params_ref: ctx.registerBlob(queryParams),
2703
+ body_ref: ctx.registerBlob(body ?? null),
2704
+ timeout_seconds: getNumber(config, "timeoutSeconds") ?? null
2705
+ };
2706
+ }
2707
+ function buildGraphQlRequestExecutor(ctx) {
2708
+ const config = ctx.resourceConfig;
2709
+ const endpoint = getString(config, "endpoint") ?? "";
2710
+ const query = getString(config, "query") ?? "";
2711
+ const headers = getRecord(config, "headers") ?? {};
2712
+ const variables = getRecord(config, "variables") ?? {};
2713
+ const operationName = getString(config, "operationName") ?? null;
2714
+ const credentialHeaders = normalizeCredentialHeaders(
2715
+ config["credentialHeaders"],
2716
+ ctx.resourceId,
2717
+ `tool "${ctx.resourceId}.${ctx.toolName}"`,
2718
+ ctx.warnings
2719
+ );
2720
+ return {
2721
+ kind: "graphql_request",
2722
+ endpoint,
2723
+ query_ref: ctx.registerBlob(query),
2724
+ operation_name: operationName,
2725
+ variables_ref: ctx.registerBlob(variables),
2726
+ headers_ref: ctx.registerBlob(headers),
2727
+ credential_headers: credentialHeaders,
2728
+ timeout_seconds: getNumber(config, "timeoutSeconds") ?? null
2729
+ };
2730
+ }
2731
+ function buildHttpOperationExecutor(ctx) {
2732
+ const config = ctx.resourceConfig;
2733
+ const ops = getRecord(config, "operations") ?? {};
2734
+ const opSpec = getRecord(ops, ctx.toolName);
2735
+ if (!opSpec) {
2736
+ ctx.warnings.push(
2737
+ `tool "${ctx.resourceId}.${ctx.toolName}" has no matching operation in resource config`
2738
+ );
2739
+ return {
2740
+ kind: "unsupported",
2741
+ reason: `httpApi_missing_op:${ctx.toolName}`
2742
+ };
2743
+ }
2744
+ const baseUrl = getString(config, "baseUrl") ?? "";
2745
+ const resourceHeaders = getRecord(config, "headers") ?? {};
2746
+ const opHeaders = getRecord(opSpec, "headers") ?? {};
2747
+ const mergedHeaders = { ...resourceHeaders, ...opHeaders };
2748
+ const opCredentialHeaders = opSpec["credentialHeaders"];
2749
+ const resourceCredentialHeaders = config["credentialHeaders"];
2750
+ const credentialHeaders = normalizeCredentialHeaders(
2751
+ opCredentialHeaders ?? resourceCredentialHeaders,
2752
+ ctx.resourceId,
2753
+ `tool "${ctx.resourceId}.${ctx.toolName}"`,
2754
+ ctx.warnings
2755
+ );
2756
+ const method = (getString(opSpec, "method") ?? "GET").toUpperCase();
2757
+ const path = getString(opSpec, "path") ?? "";
2758
+ const timeoutSeconds = getNumber(opSpec, "timeoutSeconds") ?? getNumber(config, "timeoutSeconds") ?? null;
2759
+ return {
2760
+ kind: "http_operation",
2761
+ base_url: baseUrl,
2762
+ method,
2763
+ path,
2764
+ headers_ref: ctx.registerBlob(mergedHeaders),
2765
+ credential_headers: credentialHeaders,
2766
+ timeout_seconds: timeoutSeconds,
2767
+ request_mapping: normalizeRequestMapping(opSpec["requestMapping"])
2768
+ };
2769
+ }
2770
+ function buildGraphQlOperationExecutor(ctx) {
2771
+ const config = ctx.resourceConfig;
2772
+ const ops = getRecord(config, "operations") ?? {};
2773
+ const opSpec = getRecord(ops, ctx.toolName);
2774
+ if (!opSpec) {
2775
+ ctx.warnings.push(
2776
+ `tool "${ctx.resourceId}.${ctx.toolName}" has no matching operation in resource config`
2777
+ );
2778
+ return {
2779
+ kind: "unsupported",
2780
+ reason: `graphqlApi_missing_op:${ctx.toolName}`
2781
+ };
2782
+ }
2783
+ const endpoint = getString(config, "endpoint") ?? "";
2784
+ const resourceHeaders = getRecord(config, "headers") ?? {};
2785
+ const opHeaders = getRecord(opSpec, "headers") ?? {};
2786
+ const mergedHeaders = { ...resourceHeaders, ...opHeaders };
2787
+ const opCredentialHeaders = opSpec["credentialHeaders"];
2788
+ const resourceCredentialHeaders = config["credentialHeaders"];
2789
+ const credentialHeaders = normalizeCredentialHeaders(
2790
+ opCredentialHeaders ?? resourceCredentialHeaders,
2791
+ ctx.resourceId,
2792
+ `tool "${ctx.resourceId}.${ctx.toolName}"`,
2793
+ ctx.warnings
2794
+ );
2795
+ const query = getString(opSpec, "query") ?? "";
2796
+ const operationName = getString(opSpec, "operationName") ?? null;
2797
+ const variablesMapping = getString(opSpec, "variablesMapping") ?? "input";
2798
+ const timeoutSeconds = getNumber(opSpec, "timeoutSeconds") ?? getNumber(config, "timeoutSeconds") ?? null;
2799
+ return {
2800
+ kind: "graphql_operation",
2801
+ endpoint,
2802
+ query_ref: ctx.registerBlob(query),
2803
+ operation_name: operationName,
2804
+ headers_ref: ctx.registerBlob(mergedHeaders),
2805
+ credential_headers: credentialHeaders,
2806
+ timeout_seconds: timeoutSeconds,
2807
+ variables_mapping: variablesMapping
2808
+ };
2809
+ }
2810
+ function generateCatalog(manifest) {
2811
+ const warnings = [];
2812
+ const resources = {};
2813
+ const signals2 = {};
2814
+ const tools = {};
2815
+ const schemas = {};
2816
+ const registerBlob = (value) => {
2817
+ const hash = sha256Hex(value ?? {});
2818
+ if (!(hash in schemas)) {
2819
+ schemas[hash] = value ?? {};
2820
+ }
2821
+ return hash;
2822
+ };
2823
+ const apps = getArray(manifest, "apps").filter(isRecord3);
2824
+ for (const app of apps) {
2825
+ const appName = getString(app, "name");
2826
+ if (!appName) continue;
2827
+ const appResources2 = getArray(app, "resources").filter(isRecord3);
2828
+ for (const resource of appResources2) {
2829
+ const resourceName = getString(resource, "name");
2830
+ const resourceKind = getString(resource, "kind") ?? "";
2831
+ const backend = getString(resource, "backend") ?? "";
2832
+ const providerKind = getString(resource, "provider_kind") ?? resourceKind;
2833
+ if (!resourceName) continue;
2834
+ const resourceId = `${appName}.${resourceName}`;
2835
+ const config = getRecord(resource, "config") ?? {};
2836
+ const configHash = registerBlob(config);
2837
+ const capability = getRecord(resource, "capability") ?? {};
2838
+ const bindingScope = getString(capability, "binding_scope") ?? "project";
2839
+ const credentialSource = getString(capability, "credential_source") ?? "none";
2840
+ const requiresBindingValue = capability.requires_binding;
2841
+ const credentialExposure = requiresBindingValue === true ? "stdlib" : "none";
2842
+ const resourceSignals = getArray(resource, "signals").filter(isRecord3);
2843
+ const resourceTools2 = getArray(resource, "tools").filter(isRecord3);
2844
+ const emittedSignalIds = [];
2845
+ for (const signal of resourceSignals) {
2846
+ const signalName = getString(signal, "name");
2847
+ if (!signalName) continue;
2848
+ const signalId = `${resourceId}.${signalName}`;
2849
+ const signalScope = getString(signal, "scope") === "principal" ? "principal" : "project";
2850
+ const signalSchema = getRecord(signal, "schema") ?? {};
2851
+ const schemaRef = registerBlob(signalSchema);
2852
+ signals2[signalId] = {
2853
+ id: signalId,
2854
+ resource_id: resourceId,
2855
+ name: signalName,
2856
+ event_name: getString(signal, "event_name") ?? "",
2857
+ scope: signalScope,
2858
+ mode: getString(signal, "mode") ?? "",
2859
+ schema_ref: schemaRef
2860
+ };
2861
+ emittedSignalIds.push(signalId);
2862
+ }
2863
+ const emittedToolIds = [];
2864
+ for (const tool of resourceTools2) {
2865
+ const toolName = getString(tool, "name");
2866
+ if (!toolName) continue;
2867
+ const toolId = `${resourceId}.${toolName}`;
2868
+ const effect = deriveEffect(tool.mode);
2869
+ const toolCapability = getRecord(tool, "capability") ?? {};
2870
+ const toolBackendKind = getString(toolCapability, "backend_kind") ?? backend;
2871
+ const toolProviderKind = getString(toolCapability, "provider_kind") ?? providerKind;
2872
+ const toolCredentialExposure = getString(toolCapability, "credential_exposure") ?? "none";
2873
+ const argsSchemaRef = registerBlob(
2874
+ getRecord(tool, "args_schema") ?? {}
2875
+ );
2876
+ const resultSchemaRef = registerBlob(
2877
+ getRecord(tool, "result_schema") ?? {}
2878
+ );
2879
+ const executor = buildExecutor({
2880
+ resourceId,
2881
+ resourceKind,
2882
+ providerKind: toolProviderKind,
2883
+ backendKind: toolBackendKind,
2884
+ toolName,
2885
+ resourceConfig: config,
2886
+ configHash,
2887
+ registerBlob,
2888
+ warnings
2889
+ });
2890
+ if (executor.kind === "unsupported") {
2891
+ warnings.push(
2892
+ `tool "${toolId}" executor deferred: ${executor.reason}`
2893
+ );
2894
+ }
2895
+ tools[toolId] = {
2896
+ id: toolId,
2897
+ resource_id: resourceId,
2898
+ name: toolName,
2899
+ effect,
2900
+ audit_requirement: deriveAuditRequirement(effect),
2901
+ credential_exposure: toolCredentialExposure,
2902
+ args_schema_ref: argsSchemaRef,
2903
+ result_schema_ref: resultSchemaRef,
2904
+ executor
2905
+ };
2906
+ emittedToolIds.push(toolId);
2907
+ }
2908
+ resources[resourceId] = {
2909
+ id: resourceId,
2910
+ application_name: appName,
2911
+ resource_name: resourceName,
2912
+ resource_kind: resourceKind,
2913
+ provider_kind: providerKind,
2914
+ backend_kind: backend,
2915
+ binding_scope: bindingScope,
2916
+ credential_source: credentialSource,
2917
+ credential_exposure: credentialExposure,
2918
+ config_ref: configHash,
2919
+ signals: emittedSignalIds,
2920
+ tools: emittedToolIds
2921
+ };
2922
+ }
2923
+ }
2924
+ const catalogBody = {
2925
+ contract_version: CATALOG_CONTRACT_VERSION,
2926
+ snapshot_version: CATALOG_SNAPSHOT_VERSION,
2927
+ resources,
2928
+ signals: signals2,
2929
+ tools,
2930
+ schemas
2931
+ };
2932
+ const catalogHash = sha256Hex(catalogBody);
2933
+ return {
2934
+ catalog: {
2935
+ ...catalogBody,
2936
+ catalog_hash: catalogHash
2937
+ },
2938
+ warnings
2939
+ };
2940
+ }
2941
+ var INTERNAL_CONFIG = "__io_cli_compiled_config";
2942
+ var INTERNAL_RUN = "__io_cli_compiled_run";
2943
+ var INTERNAL_ON_START = "__io_cli_compiled_on_start";
2944
+ var INTERNAL_ON_MESSAGE = "__io_cli_compiled_on_message";
2945
+ var INTERNAL_ON_END = "__io_cli_compiled_on_end";
2946
+ var INTERNAL_APPLICATIONS = "__io_cli_compiled_applications";
2947
+ function conversationChannel(name) {
2948
+ return `session:${name}`;
2949
+ }
2950
+ function parseModule2(source) {
2951
+ return acorn.parse(source, {
2952
+ ecmaVersion: "latest",
2953
+ sourceType: "module"
2954
+ });
2955
+ }
2956
+ function rootStatementFor(source, statementStart, kind, name) {
2957
+ let ast;
2958
+ try {
2959
+ ast = parseModule2(source);
2960
+ } catch (error) {
2961
+ throw new BuildError(
2962
+ `failed to parse compiled target source: ${error instanceof Error ? error.message : String(error)}`
2963
+ );
2964
+ }
2965
+ const body = ast.body;
2966
+ const stmt = body.find(
2967
+ (candidate) => candidate.start === statementStart
2968
+ );
2969
+ if (stmt) {
2970
+ return stmt;
2971
+ }
2972
+ const fallback = body.find((candidate) => {
2973
+ if (candidate.type !== "ExpressionStatement") {
2974
+ return false;
2975
+ }
2976
+ const expression = candidate.expression;
2977
+ if (expression.type !== "CallExpression") {
2978
+ return false;
2979
+ }
2980
+ const args = expression.arguments;
2981
+ if (args.length < 2 || args[0]?.type !== "Literal" || args[1]?.type !== "ObjectExpression") {
2982
+ return false;
2983
+ }
2984
+ const callee = expression.callee;
2985
+ if (callee.type !== "Identifier") {
2986
+ return false;
2987
+ }
2988
+ return callee.name === kind && args[0].value === name;
2989
+ });
2990
+ if (!fallback) {
2991
+ throw new BuildError(
2992
+ `failed to locate compiled ${kind} root '${name}' at byte offset ${statementStart}`
2993
+ );
2994
+ }
2995
+ return fallback;
2996
+ }
2997
+ function rootSpecNode(stmt) {
2998
+ if (stmt.type !== "ExpressionStatement") {
2270
2999
  throw new BuildError("compiled target root must be an expression statement");
2271
3000
  }
2272
3001
  const expression = stmt.expression;
@@ -2567,13 +3296,90 @@ function pruneUnusedTopLevel(source, seeds) {
2567
3296
  }
2568
3297
  return applyEdits2(source, edits);
2569
3298
  }
3299
+ function parseDurationMs(value) {
3300
+ if (value === void 0) return void 0;
3301
+ if (typeof value === "number") {
3302
+ if (Number.isInteger(value) && value > 0) return value;
3303
+ throw new BuildError("execution.timeout must be a positive integer number of milliseconds");
3304
+ }
3305
+ if (typeof value !== "string") {
3306
+ throw new BuildError("execution.timeout must be a duration string or milliseconds");
3307
+ }
3308
+ const match = value.trim().toLowerCase().match(/^(\d+)\s*(ms|s|m|h)$/);
3309
+ if (!match) {
3310
+ throw new BuildError("execution.timeout must use ms, s, m, or h units");
3311
+ }
3312
+ const amount = Number(match[1]);
3313
+ const unit = match[2];
3314
+ const multiplier = unit === "ms" ? 1 : unit === "s" ? 1e3 : unit === "m" ? 6e4 : 36e5;
3315
+ return amount * multiplier;
3316
+ }
3317
+ function parseMemoryMb(value) {
3318
+ if (value === void 0) return void 0;
3319
+ if (typeof value === "number") {
3320
+ if (Number.isInteger(value) && value > 0) return value;
3321
+ throw new BuildError("execution.memory must be a positive integer number of megabytes");
3322
+ }
3323
+ if (typeof value !== "string") {
3324
+ throw new BuildError("execution.memory must be a size string or megabytes");
3325
+ }
3326
+ const match = value.trim().toLowerCase().match(/^(\d+)\s*(mb|m|gb|g)$/);
3327
+ if (!match) {
3328
+ throw new BuildError("execution.memory must use mb or gb units");
3329
+ }
3330
+ const amount = Number(match[1]);
3331
+ const unit = match[2];
3332
+ return unit === "gb" || unit === "g" ? amount * 1024 : amount;
3333
+ }
3334
+ function nonNegativeInteger(value, field) {
3335
+ if (value === void 0) return void 0;
3336
+ if (Number.isInteger(value) && value >= 0) return value;
3337
+ throw new BuildError(`${field} must be a non-negative integer`);
3338
+ }
3339
+ function normalizeBudgetMap(value, field) {
3340
+ if (value === void 0) return void 0;
3341
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3342
+ throw new BuildError(`${field} must be an object`);
3343
+ }
3344
+ const out = {};
3345
+ for (const [key, budget] of Object.entries(value)) {
3346
+ if (!key.trim()) {
3347
+ throw new BuildError(`${field} keys must be non-empty strings`);
3348
+ }
3349
+ out[key] = nonNegativeInteger(budget, `${field}.${key}`);
3350
+ }
3351
+ return out;
3352
+ }
3353
+ function normalizeRateBudget(rateBudget) {
3354
+ if (rateBudget === void 0) return void 0;
3355
+ const perApplication = normalizeBudgetMap(
3356
+ rateBudget.perApplication,
3357
+ "execution.rateBudget.perApplication"
3358
+ );
3359
+ const perCapability = normalizeBudgetMap(
3360
+ rateBudget.perCapability,
3361
+ "execution.rateBudget.perCapability"
3362
+ );
3363
+ const out = {
3364
+ ...perApplication ? { per_application: perApplication } : {},
3365
+ ...perCapability ? { per_capability: perCapability } : {}
3366
+ };
3367
+ return Object.keys(out).length > 0 ? out : void 0;
3368
+ }
2570
3369
  function compactExecution(execution) {
2571
3370
  if (!execution) {
2572
3371
  return void 0;
2573
3372
  }
2574
- const compact = Object.fromEntries(
2575
- Object.entries(execution).filter(([, value]) => value !== void 0)
2576
- );
3373
+ const timeoutMs = parseDurationMs(execution.timeout);
3374
+ const memoryMb = parseMemoryMb(execution.memory);
3375
+ const maxToolCalls = nonNegativeInteger(execution.maxToolCalls, "execution.maxToolCalls");
3376
+ const rateBudget = normalizeRateBudget(execution.rateBudget);
3377
+ const compact = {
3378
+ ...timeoutMs !== void 0 ? { timeout_ms: timeoutMs } : {},
3379
+ ...memoryMb !== void 0 ? { memory_mb: memoryMb } : {},
3380
+ ...maxToolCalls !== void 0 ? { max_tool_calls: maxToolCalls } : {},
3381
+ ...rateBudget !== void 0 ? { rate_budget: rateBudget } : {}
3382
+ };
2577
3383
  return Object.keys(compact).length > 0 ? compact : void 0;
2578
3384
  }
2579
3385
  function buildBehaviorReplacement(source, spec, behavior) {
@@ -2583,8 +3389,6 @@ function buildBehaviorReplacement(source, spec, behavior) {
2583
3389
  }
2584
3390
  const execution = compactExecution(behavior.execution);
2585
3391
  const runSource = functionSourceFromProperty(source, run, "run");
2586
- const trackingId = findProperty(spec, "trackingId");
2587
- const trackingIdSource = trackingId ? functionSourceFromProperty(source, trackingId, "trackingId") : void 0;
2588
3392
  const applications = findProperty(spec, "applications");
2589
3393
  const applicationsSource = applications ? expressionSourceFromProperty(source, applications, "applications") : void 0;
2590
3394
  const configEntries = [
@@ -2595,9 +3399,6 @@ function buildBehaviorReplacement(source, spec, behavior) {
2595
3399
  if (execution) {
2596
3400
  configEntries.push(`execution: ${JSON.stringify(execution)}`);
2597
3401
  }
2598
- if (behavior.messageHistory !== void 0) {
2599
- configEntries.push(`messageHistory: ${JSON.stringify(behavior.messageHistory)}`);
2600
- }
2601
3402
  if (applicationsSource) {
2602
3403
  configEntries.push(`applications: ${INTERNAL_APPLICATIONS}`);
2603
3404
  }
@@ -2606,17 +3407,14 @@ function buildBehaviorReplacement(source, spec, behavior) {
2606
3407
  replacement: [
2607
3408
  ...applicationsSource ? [`const ${INTERNAL_APPLICATIONS} = ${applicationsSource};`] : [],
2608
3409
  `const ${INTERNAL_CONFIG} = ${configSource};`,
2609
- `const ${INTERNAL_TRACKING_ID} = ${trackingIdSource ?? "undefined"};`,
2610
3410
  `const ${INTERNAL_RUN} = ${runSource};`,
2611
- `export { ${INTERNAL_CONFIG} as config, ${INTERNAL_TRACKING_ID} as trackingId, ${INTERNAL_RUN} as run };`
3411
+ `export { ${INTERNAL_CONFIG} as config, ${INTERNAL_RUN} as run };`
2612
3412
  ].join("\n"),
2613
3413
  refs: /* @__PURE__ */ new Set([
2614
3414
  INTERNAL_CONFIG,
2615
- INTERNAL_TRACKING_ID,
2616
3415
  INTERNAL_RUN,
2617
3416
  ...applicationsSource ? [INTERNAL_APPLICATIONS] : [],
2618
3417
  ...refsFromExpressionSource(runSource),
2619
- ...trackingIdSource ? refsFromExpressionSource(trackingIdSource) : [],
2620
3418
  ...applicationsSource ? refsFromExpressionSource(applicationsSource) : []
2621
3419
  ])
2622
3420
  };
@@ -2756,7 +3554,15 @@ async function buildPipeline(config, cwd = process.cwd(), minify = false, buildD
2756
3554
  conversationBundles
2757
3555
  });
2758
3556
  validateManifest(manifest);
2759
- return { manifest, outDir };
3557
+ const { catalog: catalog2, warnings } = generateCatalog(manifest);
3558
+ if (warnings.length > 0) {
3559
+ throw new BuildError(
3560
+ `Catalog generation produced ${warnings.length} unrecoverable issue${warnings.length === 1 ? "" : "s"} \u2014 refusing to ship a partial catalog:
3561
+ ${warnings.map((w) => ` - ${w}`).join("\n")}`
3562
+ );
3563
+ }
3564
+ await writeFile(join(outDir, "catalog.json"), stableStringify(catalog2, 2), "utf-8");
3565
+ return { manifest, catalog: catalog2, outDir };
2760
3566
  }
2761
3567
  async function createStagingDir() {
2762
3568
  return mkdtemp(join(tmpdir(), "io-cli-"));
@@ -2776,7 +3582,7 @@ async function buildCommand(options) {
2776
3582
  const config = await loadConfig({
2777
3583
  dir: options.dir
2778
3584
  });
2779
- console.log(chalk11.gray("Validating deployment..."));
3585
+ console.log(chalk12.gray("Validating deployment..."));
2780
3586
  stagingDir = await createStagingDir();
2781
3587
  const { manifest } = await buildPipeline(
2782
3588
  config,
@@ -2806,10 +3612,10 @@ async function buildCommand(options) {
2806
3612
  `${subscriptions.length} subscription${subscriptions.length === 1 ? "" : "s"}`,
2807
3613
  `${secretBundles.length} secret bundle${secretBundles.length === 1 ? "" : "s"}`
2808
3614
  ];
2809
- console.log(chalk11.green(`\u2713 Build succeeded (${parts.join(", ")})`));
3615
+ console.log(chalk12.green(`\u2713 Build succeeded (${parts.join(", ")})`));
2810
3616
  } catch (err) {
2811
3617
  if (err instanceof CliError) {
2812
- console.error(chalk11.red(err.message));
3618
+ console.error(chalk12.red(err.message));
2813
3619
  process.exit(1);
2814
3620
  }
2815
3621
  throw err;
@@ -2820,8 +3626,8 @@ async function buildCommand(options) {
2820
3626
  async function zipBuildOutput(buildDir, outPath) {
2821
3627
  const output = createWriteStream(outPath);
2822
3628
  const archive = archiver("zip", { zlib: { level: 9 } });
2823
- const done = new Promise((resolve4, reject) => {
2824
- output.on("close", resolve4);
3629
+ const done = new Promise((resolve5, reject) => {
3630
+ output.on("close", resolve5);
2825
3631
  archive.on("error", reject);
2826
3632
  });
2827
3633
  archive.pipe(output);
@@ -2850,13 +3656,13 @@ async function addDirectory(archive, baseDir, prefix, skipRelativePath) {
2850
3656
  }
2851
3657
 
2852
3658
  // src/deploy/diff.ts
2853
- function stableStringify(value) {
3659
+ function stableStringify2(value) {
2854
3660
  if (Array.isArray(value)) {
2855
- return `[${value.map(stableStringify).join(",")}]`;
3661
+ return `[${value.map(stableStringify2).join(",")}]`;
2856
3662
  }
2857
3663
  if (value && typeof value === "object") {
2858
3664
  const record = value;
2859
- return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
3665
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(record[key])}`).join(",")}}`;
2860
3666
  }
2861
3667
  return JSON.stringify(value);
2862
3668
  }
@@ -2882,7 +3688,7 @@ function diffEntries(local, remote) {
2882
3688
  for (const { name, value } of local) {
2883
3689
  if (!remoteMap.has(name)) {
2884
3690
  entries.push({ name, kind: "added" });
2885
- } else if (stableStringify(value) !== stableStringify(remoteMap.get(name))) {
3691
+ } else if (stableStringify2(value) !== stableStringify2(remoteMap.get(name))) {
2886
3692
  entries.push({ name, kind: "changed" });
2887
3693
  }
2888
3694
  }
@@ -2995,10 +3801,81 @@ function diffManifests(local, remote) {
2995
3801
  hasChanges
2996
3802
  };
2997
3803
  }
3804
+
3805
+ // src/deploy/catalog-diff.ts
3806
+ function stableStringify3(value) {
3807
+ if (Array.isArray(value)) {
3808
+ return `[${value.map(stableStringify3).join(",")}]`;
3809
+ }
3810
+ if (value && typeof value === "object") {
3811
+ const record = value;
3812
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify3(record[key])}`).join(",")}}`;
3813
+ }
3814
+ return JSON.stringify(value);
3815
+ }
3816
+ function diffSection(local, remote) {
3817
+ const localMap = local ?? {};
3818
+ const remoteMap = remote ?? {};
3819
+ const entries = [];
3820
+ const seen = /* @__PURE__ */ new Set();
3821
+ for (const id of Object.keys(localMap).sort()) {
3822
+ seen.add(id);
3823
+ if (!(id in remoteMap)) {
3824
+ entries.push({ id, kind: "added" });
3825
+ } else if (stableStringify3(localMap[id]) !== stableStringify3(remoteMap[id])) {
3826
+ entries.push({ id, kind: "changed" });
3827
+ }
3828
+ }
3829
+ for (const id of Object.keys(remoteMap).sort()) {
3830
+ if (!seen.has(id) && !(id in localMap)) {
3831
+ entries.push({ id, kind: "removed" });
3832
+ }
3833
+ }
3834
+ return entries;
3835
+ }
3836
+ function schemaDiffCounts(local, remote) {
3837
+ const localKeys = new Set(Object.keys(local ?? {}));
3838
+ const remoteKeys = new Set(Object.keys(remote ?? {}));
3839
+ let added = 0;
3840
+ let removed = 0;
3841
+ for (const k of localKeys) {
3842
+ if (!remoteKeys.has(k)) added++;
3843
+ }
3844
+ for (const k of remoteKeys) {
3845
+ if (!localKeys.has(k)) removed++;
3846
+ }
3847
+ return { added, removed };
3848
+ }
3849
+ function diffCatalogs(local, remote) {
3850
+ const localResources = local?.resources ?? {};
3851
+ const remoteResources = remote?.resources ?? {};
3852
+ const localSignals = local?.signals ?? {};
3853
+ const remoteSignals = remote?.signals ?? {};
3854
+ const localTools = local?.tools ?? {};
3855
+ const remoteTools = remote?.tools ?? {};
3856
+ const localSchemas = local?.schemas ?? {};
3857
+ const remoteSchemas = remote?.schemas ?? {};
3858
+ const resources = diffSection(localResources, remoteResources);
3859
+ const signals2 = diffSection(localSignals, remoteSignals);
3860
+ const tools = diffSection(localTools, remoteTools);
3861
+ const schemas = schemaDiffCounts(localSchemas, remoteSchemas);
3862
+ const localHash = local?.catalog_hash ?? null;
3863
+ const remoteHash = remote?.catalog_hash ?? null;
3864
+ const hashChanged = (localHash ?? remoteHash) !== null && localHash !== remoteHash;
3865
+ const hasChanges = resources.length > 0 || signals2.length > 0 || tools.length > 0 || schemas.added > 0 || schemas.removed > 0 || hashChanged;
3866
+ return {
3867
+ hashChanged,
3868
+ resources,
3869
+ signals: signals2,
3870
+ tools,
3871
+ schemas,
3872
+ hasChanges
3873
+ };
3874
+ }
2998
3875
  var SYMBOLS = {
2999
- added: chalk11.green("+ "),
3000
- changed: chalk11.yellow("~ "),
3001
- removed: chalk11.red("- ")
3876
+ added: chalk12.green("+ "),
3877
+ changed: chalk12.yellow("~ "),
3878
+ removed: chalk12.red("- ")
3002
3879
  };
3003
3880
  var LABELS = {
3004
3881
  added: "new",
@@ -3014,30 +3891,64 @@ var SECTIONS = [
3014
3891
  { key: "targets", label: "Targets" },
3015
3892
  { key: "subscriptions", label: "Subscriptions" }
3016
3893
  ];
3017
- function formatPlan(diff) {
3894
+ var CATALOG_SECTIONS = [
3895
+ { key: "resources", label: "Catalog resources" },
3896
+ { key: "signals", label: "Catalog signals" },
3897
+ { key: "tools", label: "Catalog tools" }
3898
+ ];
3899
+ function formatPlan(diff, catalogDiff) {
3018
3900
  const lines = [];
3019
3901
  for (const { key, label } of SECTIONS) {
3020
3902
  const entries = diff[key];
3021
3903
  lines.push(` ${label}:`);
3022
3904
  if (entries.length === 0) {
3023
- lines.push(chalk11.gray(" (no changes)"));
3905
+ lines.push(chalk12.gray(" (no changes)"));
3024
3906
  } else {
3025
3907
  for (const e of entries) {
3026
3908
  lines.push(
3027
- ` ${SYMBOLS[e.kind]}${e.name} ${chalk11.gray(`(${LABELS[e.kind]})`)}`
3909
+ ` ${SYMBOLS[e.kind]}${e.name} ${chalk12.gray(`(${LABELS[e.kind]})`)}`
3028
3910
  );
3029
3911
  }
3030
3912
  }
3031
3913
  }
3914
+ if (catalogDiff) {
3915
+ lines.push("");
3916
+ lines.push(
3917
+ chalk12.bold(
3918
+ catalogDiff.hashChanged ? " Catalog (hash changed):" : " Catalog:"
3919
+ )
3920
+ );
3921
+ for (const { key, label } of CATALOG_SECTIONS) {
3922
+ const entries = catalogDiff[key];
3923
+ lines.push(` ${label}:`);
3924
+ if (entries.length === 0) {
3925
+ lines.push(chalk12.gray(" (no changes)"));
3926
+ } else {
3927
+ for (const e of entries) {
3928
+ lines.push(
3929
+ ` ${SYMBOLS[e.kind]}${e.id} ${chalk12.gray(`(${LABELS[e.kind]})`)}`
3930
+ );
3931
+ }
3932
+ }
3933
+ }
3934
+ const schemaLine = catalogDiff.schemas;
3935
+ if (schemaLine.added > 0 || schemaLine.removed > 0) {
3936
+ lines.push(
3937
+ ` ${chalk12.gray("Schemas:")} ${chalk12.green(`+${schemaLine.added}`)} ${chalk12.red(`-${schemaLine.removed}`)}`
3938
+ );
3939
+ } else {
3940
+ lines.push(` ${chalk12.gray("Schemas: (no changes)")}`);
3941
+ }
3942
+ }
3032
3943
  return lines.join("\n");
3033
3944
  }
3034
3945
  async function confirmPlan(skip) {
3035
3946
  if (skip) return true;
3036
3947
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3037
- return new Promise((resolve4) => {
3948
+ return new Promise((resolve5) => {
3038
3949
  rl.question("\nApply these changes? (y/n) ", (answer) => {
3039
3950
  rl.close();
3040
- resolve4(answer.trim().toLowerCase() === "y");
3951
+ resolve5(answer.trim().toLowerCase() === "y");
3041
3952
  });
3042
3953
  });
3043
3954
  }
@@ -3111,40 +4022,6 @@ function socketAuthParams(creds) {
3111
4022
  return { authorization: `Bearer ${creds.token}` };
3112
4023
  }
3113
4024
 
3114
- // src/build/stable-json.ts
3115
- function stableStringify2(value, indent) {
3116
- return JSON.stringify(canonicalize(value), null, indent);
3117
- }
3118
- function canonicalize(value) {
3119
- if (value === null) return null;
3120
- if (Array.isArray(value)) {
3121
- return value.map(canonicalize);
3122
- }
3123
- if (typeof value === "object") {
3124
- const obj = value;
3125
- const sorted = {};
3126
- for (const key of Object.keys(obj).sort()) {
3127
- sorted[key] = canonicalize(obj[key]);
3128
- }
3129
- return sorted;
3130
- }
3131
- return value;
3132
- }
3133
-
3134
- // src/runtime-signal-name.ts
3135
- var EVENT_NAME_REGEX2 = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
3136
- var SIGNAL_NAME_REGEX = /^signal:[A-Za-z0-9][A-Za-z0-9._-]*$/;
3137
- function normalizeRuntimeSignalName2(value) {
3138
- const trimmed = value.trim();
3139
- if (EVENT_NAME_REGEX2.test(trimmed)) {
3140
- return `signal:${trimmed}`;
3141
- }
3142
- if (SIGNAL_NAME_REGEX.test(trimmed)) {
3143
- return trimmed;
3144
- }
3145
- return null;
3146
- }
3147
-
3148
4025
  // src/api/client.ts
3149
4026
  var REFRESH_MUTATION = `
3150
4027
  mutation Refresh($input: RefreshAuthInput!) {
@@ -3245,8 +4122,32 @@ function graphqlErrorMessage(errors) {
3245
4122
  }
3246
4123
  return null;
3247
4124
  }
3248
- function isMissingSnapshotJsonFieldError(error) {
3249
- return error instanceof ApiError && error.message.includes("snapshotJson") && error.message.includes("Cannot query field");
4125
+ function isPlainObject(value) {
4126
+ return !!value && typeof value === "object" && !Array.isArray(value);
4127
+ }
4128
+ function isCatalogShape(value) {
4129
+ if (!isPlainObject(value)) return false;
4130
+ return value.contract_version === "io.invocation.v2" && value.snapshot_version === "io_runtime.snapshot.v2" && typeof value.catalog_hash === "string" && isPlainObject(value.resources) && isPlainObject(value.signals) && isPlainObject(value.tools) && isPlainObject(value.schemas);
4131
+ }
4132
+ function isManifestShape(value) {
4133
+ if (!isPlainObject(value)) return false;
4134
+ return value.contract_version === "io.invocation.v2" && value.snapshot_version === "io_runtime.snapshot.v2" && Array.isArray(value.secret_bundles) && Array.isArray(value.apps) && Array.isArray(value.endpoints) && Array.isArray(value.targets) && Array.isArray(value.subscriptions);
4135
+ }
4136
+ function parseRemoteCatalog(catalogJson) {
4137
+ let parsed;
4138
+ try {
4139
+ parsed = JSON.parse(catalogJson);
4140
+ } catch (err) {
4141
+ throw new ConfigError(
4142
+ `Remote deployment catalog was not valid JSON: ${err instanceof Error ? err.message : String(err)}`
4143
+ );
4144
+ }
4145
+ if (!isCatalogShape(parsed)) {
4146
+ throw new ConfigError(
4147
+ "Remote deployment catalog does not match the io.invocation.v2 catalog shape."
4148
+ );
4149
+ }
4150
+ return parsed;
3250
4151
  }
3251
4152
  function parseRemoteManifest(snapshotJson) {
3252
4153
  let parsed;
@@ -3257,11 +4158,22 @@ function parseRemoteManifest(snapshotJson) {
3257
4158
  `Remote deployment snapshot was not valid JSON: ${err instanceof Error ? err.message : String(err)}`
3258
4159
  );
3259
4160
  }
3260
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3261
- throw new ConfigError("Remote deployment snapshot was not a JSON object.");
4161
+ if (!isManifestShape(parsed)) {
4162
+ throw new ConfigError(
4163
+ "Remote deployment snapshot does not match the io_runtime.snapshot.v2 manifest shape."
4164
+ );
3262
4165
  }
3263
4166
  return parsed;
3264
4167
  }
4168
+ var EMPTY_MANIFEST = {
4169
+ contract_version: "io.invocation.v2",
4170
+ snapshot_version: "io_runtime.snapshot.v2",
4171
+ secret_bundles: [],
4172
+ apps: [],
4173
+ endpoints: [],
4174
+ targets: [],
4175
+ subscriptions: []
4176
+ };
3265
4177
  async function executeGraphQLUnauth(apiUrl, query, variables, headers = {}) {
3266
4178
  const response = await fetch(`${trimBaseUrl(apiUrl)}/graphql`, {
3267
4179
  method: "POST",
@@ -3478,14 +4390,33 @@ function createClient(config) {
3478
4390
  );
3479
4391
  return data.createProject;
3480
4392
  }
3481
- async function listSecrets(projectIdValue) {
4393
+ async function deleteProject(organizationId, projectIdValue) {
3482
4394
  const data = await graphql(
3483
4395
  `
3484
- query Secrets($projectId: ID!) {
3485
- secrets(projectId: $projectId) {
4396
+ mutation DeleteProject($organizationId: ID!, $id: ID!) {
4397
+ deleteProject(organizationId: $organizationId, id: $id) {
4398
+ id
3486
4399
  name
4400
+ slug
4401
+ description
4402
+ organizationId
4403
+ }
4404
+ }
4405
+ `,
4406
+ { organizationId, id: projectIdValue }
4407
+ );
4408
+ return data.deleteProject;
4409
+ }
4410
+ async function principals2(projectIdValue) {
4411
+ const data = await graphql(
4412
+ `
4413
+ query Principals($projectId: ID!) {
4414
+ principals(projectId: $projectId) {
4415
+ id
4416
+ externalId
4417
+ state
4418
+ metadata
3487
4419
  projectId
3488
- hasValue
3489
4420
  insertedAt
3490
4421
  updatedAt
3491
4422
  }
@@ -3493,9 +4424,83 @@ function createClient(config) {
3493
4424
  `,
3494
4425
  { projectId: projectIdValue }
3495
4426
  );
3496
- return data.secrets ?? [];
4427
+ return data.principals ?? [];
3497
4428
  }
3498
- async function putSecret(projectIdValue, input) {
4429
+ async function createPrincipal(projectIdValue, input) {
4430
+ const data = await graphql(
4431
+ `
4432
+ mutation CreatePrincipal($projectId: ID!, $input: CreatePrincipalInput!) {
4433
+ createPrincipal(projectId: $projectId, input: $input) {
4434
+ id
4435
+ externalId
4436
+ state
4437
+ metadata
4438
+ projectId
4439
+ insertedAt
4440
+ updatedAt
4441
+ }
4442
+ }
4443
+ `,
4444
+ { projectId: projectIdValue, input }
4445
+ );
4446
+ return data.createPrincipal;
4447
+ }
4448
+ async function updatePrincipal(projectIdValue, id, input) {
4449
+ const data = await graphql(
4450
+ `
4451
+ mutation UpdatePrincipal($projectId: ID!, $id: ID!, $input: UpdatePrincipalInput!) {
4452
+ updatePrincipal(projectId: $projectId, id: $id, input: $input) {
4453
+ id
4454
+ externalId
4455
+ state
4456
+ metadata
4457
+ projectId
4458
+ insertedAt
4459
+ updatedAt
4460
+ }
4461
+ }
4462
+ `,
4463
+ { projectId: projectIdValue, id, input }
4464
+ );
4465
+ return data.updatePrincipal ?? null;
4466
+ }
4467
+ async function deletePrincipal(projectIdValue, id) {
4468
+ const data = await graphql(
4469
+ `
4470
+ mutation DeletePrincipal($projectId: ID!, $id: ID!) {
4471
+ deletePrincipal(projectId: $projectId, id: $id) {
4472
+ id
4473
+ externalId
4474
+ state
4475
+ metadata
4476
+ projectId
4477
+ insertedAt
4478
+ updatedAt
4479
+ }
4480
+ }
4481
+ `,
4482
+ { projectId: projectIdValue, id }
4483
+ );
4484
+ return data.deletePrincipal ?? null;
4485
+ }
4486
+ async function listSecrets(projectIdValue) {
4487
+ const data = await graphql(
4488
+ `
4489
+ query Secrets($projectId: ID!) {
4490
+ secrets(projectId: $projectId) {
4491
+ name
4492
+ projectId
4493
+ hasValue
4494
+ insertedAt
4495
+ updatedAt
4496
+ }
4497
+ }
4498
+ `,
4499
+ { projectId: projectIdValue }
4500
+ );
4501
+ return data.secrets ?? [];
4502
+ }
4503
+ async function putSecret(projectIdValue, input) {
3499
4504
  const data = await graphql(
3500
4505
  `
3501
4506
  mutation PutSecret($input: PutSecretInput!) {
@@ -3531,28 +4536,34 @@ function createClient(config) {
3531
4536
  return data.deleteSecret === true;
3532
4537
  }
3533
4538
  async function fetchRemoteManifest(projectIdValue) {
3534
- let data;
3535
- try {
3536
- data = await graphql(
3537
- `
4539
+ const data = await graphql(
4540
+ `
3538
4541
  query ActiveDeploymentSnapshot($projectId: ID!) {
3539
4542
  activeDeployment(projectId: $projectId) {
3540
4543
  snapshotJson
3541
4544
  }
3542
4545
  }
3543
4546
  `,
3544
- { projectId: projectIdValue }
3545
- );
3546
- } catch (err) {
3547
- if (isMissingSnapshotJsonFieldError(err)) {
3548
- return {};
3549
- }
3550
- throw err;
3551
- }
4547
+ { projectId: projectIdValue }
4548
+ );
3552
4549
  const snapshotJson = data.activeDeployment?.snapshotJson;
3553
- return typeof snapshotJson === "string" && snapshotJson.trim() !== "" ? parseRemoteManifest(snapshotJson) : {};
4550
+ return typeof snapshotJson === "string" && snapshotJson.trim() !== "" ? parseRemoteManifest(snapshotJson) : EMPTY_MANIFEST;
3554
4551
  }
3555
- async function deployBundle(bundlePath, manifest) {
4552
+ async function fetchRemoteCatalog(projectIdValue) {
4553
+ const data = await graphql(
4554
+ `
4555
+ query ActiveDeploymentCatalog($projectId: ID!) {
4556
+ activeDeployment(projectId: $projectId) {
4557
+ catalogJson
4558
+ }
4559
+ }
4560
+ `,
4561
+ { projectId: projectIdValue }
4562
+ );
4563
+ const catalogJson = data.activeDeployment?.catalogJson;
4564
+ return typeof catalogJson === "string" && catalogJson.trim() !== "" ? parseRemoteCatalog(catalogJson) : null;
4565
+ }
4566
+ async function deployBundle(bundlePath, manifest, catalog2) {
3556
4567
  const MAX_BUNDLE_BYTES = 100 * 1024 * 1024;
3557
4568
  const fileStat = await stat(bundlePath);
3558
4569
  if (fileStat.size > MAX_BUNDLE_BYTES) {
@@ -3590,7 +4601,8 @@ function createClient(config) {
3590
4601
  projectId: projectIdValue,
3591
4602
  bundleHash: upload.bundleHash,
3592
4603
  revision: nextRevision,
3593
- snapshotJson: stableStringify2(manifest)
4604
+ snapshotJson: stableStringify(manifest),
4605
+ catalogJson: stableStringify(catalog2)
3594
4606
  }
3595
4607
  }
3596
4608
  );
@@ -3598,7 +4610,10 @@ function createClient(config) {
3598
4610
  }
3599
4611
  async function emitSignal(input) {
3600
4612
  const projectIdValue = input.projectId ?? projectId();
3601
- const eventName = normalizeRuntimeSignalName2(input.signal);
4613
+ if (!input.principalId || input.principalId.trim() === "") {
4614
+ throw new ConfigError("principalId is required to emit a signal");
4615
+ }
4616
+ const eventName = normalizeRuntimeSignalName(input.signal);
3602
4617
  if (!eventName) {
3603
4618
  throw new ConfigError(
3604
4619
  "signal name must be a bare event name like smoke.start or use the signal: namespace"
@@ -3625,6 +4640,7 @@ function createClient(config) {
3625
4640
  {
3626
4641
  input: {
3627
4642
  projectId: projectIdValue,
4643
+ principalId: input.principalId,
3628
4644
  eventName,
3629
4645
  payloadJson: JSON.stringify(input.payload ?? {}),
3630
4646
  metadataJson: JSON.stringify(input.metadata ?? {}),
@@ -3650,6 +4666,37 @@ function createClient(config) {
3650
4666
  );
3651
4667
  return data.activeRevision;
3652
4668
  }
4669
+ async function reconcileProject(projectIdValue) {
4670
+ const data = await graphql(
4671
+ `
4672
+ mutation ReconcileProject($projectId: ID!) {
4673
+ reconcileProject(projectId: $projectId)
4674
+ }
4675
+ `,
4676
+ { projectId: projectIdValue }
4677
+ );
4678
+ return data.reconcileProject === true;
4679
+ }
4680
+ async function deploymentStatus(projectIdValue, revision) {
4681
+ const data = await graphql(
4682
+ `
4683
+ query DeploymentStatus($projectId: ID!, $revision: Int!) {
4684
+ deploymentStatus(projectId: $projectId, revision: $revision) {
4685
+ id
4686
+ projectId
4687
+ revision
4688
+ bundleHash
4689
+ status
4690
+ activatedAt
4691
+ insertedAt
4692
+ updatedAt
4693
+ }
4694
+ }
4695
+ `,
4696
+ { projectId: projectIdValue, revision }
4697
+ );
4698
+ return data.deploymentStatus;
4699
+ }
3653
4700
  async function quotaStatuses() {
3654
4701
  const data = await graphql(
3655
4702
  `
@@ -3701,27 +4748,263 @@ function createClient(config) {
3701
4748
  organizations,
3702
4749
  projects: projects2,
3703
4750
  createProject,
4751
+ deleteProject,
4752
+ principals: principals2,
4753
+ createPrincipal,
4754
+ updatePrincipal,
4755
+ deletePrincipal,
3704
4756
  listSecrets,
3705
4757
  putSecret,
3706
4758
  deleteSecret,
3707
4759
  fetchRemoteManifest,
4760
+ fetchRemoteCatalog,
3708
4761
  deployBundle,
4762
+ deploymentStatus,
4763
+ reconcileProject,
3709
4764
  emitSignal,
3710
4765
  quotaStatuses,
3711
4766
  projectEvents
3712
4767
  };
3713
4768
  }
4769
+ Object.assign(globalThis, { WebSocket });
4770
+ var SessionTokenNotSupportedError = class extends Error {
4771
+ constructor(message) {
4772
+ super(
4773
+ message ?? "WebSocket subscriptions require API key authentication on the current io_runtime socket contract."
4774
+ );
4775
+ this.name = "SessionTokenNotSupportedError";
4776
+ }
4777
+ };
4778
+ function buildSocketUrl(apiUrl) {
4779
+ const url = new URL(apiUrl);
4780
+ const basePath = url.pathname.replace(/\/+$/, "");
4781
+ const socketBase = basePath.endsWith("/graphql") ? basePath.slice(0, -"/graphql".length) : basePath;
4782
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
4783
+ url.pathname = `${socketBase === "/" ? "" : socketBase}/socket`;
4784
+ url.search = "";
4785
+ url.hash = "";
4786
+ return url.toString();
4787
+ }
4788
+ function createSocketClient(options) {
4789
+ let phoenixSocket = null;
4790
+ let absintheSocket = null;
4791
+ const notifiers = /* @__PURE__ */ new Set();
4792
+ function ensureConnection() {
4793
+ if (!options.credentials.apiKey) {
4794
+ throw new SessionTokenNotSupportedError();
4795
+ }
4796
+ if (!phoenixSocket || !absintheSocket) {
4797
+ phoenixSocket = new Socket(buildSocketUrl(options.apiUrl), {
4798
+ params: socketAuthParams(options.credentials),
4799
+ timeout: options.timeoutMs ?? 1e4
4800
+ });
4801
+ absintheSocket = AbsintheSocket.create(phoenixSocket);
4802
+ phoenixSocket.connect();
4803
+ }
4804
+ return { absintheSocket };
4805
+ }
4806
+ function tearDownIfIdle() {
4807
+ if (notifiers.size === 0 && phoenixSocket) {
4808
+ phoenixSocket.disconnect();
4809
+ phoenixSocket = null;
4810
+ absintheSocket = null;
4811
+ }
4812
+ }
4813
+ return {
4814
+ subscribe(operation, variables, observer) {
4815
+ const conn = ensureConnection();
4816
+ const notifier = AbsintheSocket.send(conn.absintheSocket, {
4817
+ operation,
4818
+ variables
4819
+ });
4820
+ notifiers.add(notifier);
4821
+ AbsintheSocket.observe(conn.absintheSocket, notifier, {
4822
+ onAbort: (err) => {
4823
+ notifiers.delete(notifier);
4824
+ if (observer.onAbort) {
4825
+ observer.onAbort(err);
4826
+ } else {
4827
+ observer.onError?.(err);
4828
+ }
4829
+ tearDownIfIdle();
4830
+ },
4831
+ onCancel: () => {
4832
+ notifiers.delete(notifier);
4833
+ tearDownIfIdle();
4834
+ },
4835
+ onError: (err) => {
4836
+ observer.onError?.(err);
4837
+ },
4838
+ onResult: (result) => {
4839
+ observer.onResult?.(result);
4840
+ },
4841
+ onStart: () => {
4842
+ observer.onStart?.();
4843
+ }
4844
+ });
4845
+ return () => {
4846
+ if (!notifiers.has(notifier)) return;
4847
+ notifiers.delete(notifier);
4848
+ try {
4849
+ AbsintheSocket.cancel(conn.absintheSocket, notifier);
4850
+ } catch {
4851
+ }
4852
+ tearDownIfIdle();
4853
+ };
4854
+ },
4855
+ close() {
4856
+ notifiers.clear();
4857
+ if (phoenixSocket) {
4858
+ phoenixSocket.disconnect();
4859
+ }
4860
+ phoenixSocket = null;
4861
+ absintheSocket = null;
4862
+ }
4863
+ };
4864
+ }
4865
+
4866
+ // src/api/watcher.ts
4867
+ var DEFAULT_POLL_INTERVAL_MS = 2e3;
4868
+ var DEFAULT_SEEN_ID_LIMIT = 1e3;
4869
+ var DEFAULT_POLL_SAFETY_LOOPS = 10;
4870
+ function installSigintHandler(onTerminate) {
4871
+ const handler = () => {
4872
+ onTerminate();
4873
+ };
4874
+ process.once("SIGINT", handler);
4875
+ return () => {
4876
+ process.removeListener("SIGINT", handler);
4877
+ };
4878
+ }
4879
+ async function watch(plan, handlers) {
4880
+ const seenIds = /* @__PURE__ */ new Set();
4881
+ const seenOrder = [];
4882
+ const seenLimit = plan.seenIdLimit ?? DEFAULT_SEEN_ID_LIMIT;
4883
+ function rememberId(id) {
4884
+ if (seenIds.has(id)) return;
4885
+ seenIds.add(id);
4886
+ seenOrder.push(id);
4887
+ while (seenOrder.length > seenLimit) {
4888
+ const evicted = seenOrder.shift();
4889
+ if (evicted) seenIds.delete(evicted);
4890
+ }
4891
+ }
4892
+ function handle(event) {
4893
+ const id = plan.getId(event);
4894
+ if (seenIds.has(id)) return false;
4895
+ rememberId(id);
4896
+ handlers.onEvent(event);
4897
+ return true;
4898
+ }
4899
+ let cursor = plan.initialSince ?? (/* @__PURE__ */ new Date()).toISOString();
4900
+ function advanceCursor(event) {
4901
+ if (!plan.getCursor) return;
4902
+ const next = plan.getCursor(event);
4903
+ if (next > cursor) cursor = next;
4904
+ }
4905
+ if (plan.snapshot) {
4906
+ const initial = await plan.snapshot();
4907
+ for (const event of initial) {
4908
+ handle(event);
4909
+ advanceCursor(event);
4910
+ if (plan.isTerminal?.(event)) return;
4911
+ }
4912
+ }
4913
+ return new Promise((resolve5, reject) => {
4914
+ let cleanupSigint;
4915
+ let subscribeHandle;
4916
+ let pollTimer;
4917
+ let terminated = false;
4918
+ function finalize(action) {
4919
+ if (terminated) return;
4920
+ terminated = true;
4921
+ subscribeHandle?.unsubscribe();
4922
+ if (pollTimer) clearTimeout(pollTimer);
4923
+ cleanupSigint?.();
4924
+ action();
4925
+ }
4926
+ cleanupSigint = installSigintHandler(() => {
4927
+ finalize(() => resolve5());
4928
+ });
4929
+ function processLiveEvent(event) {
4930
+ const accepted = handle(event);
4931
+ if (accepted) advanceCursor(event);
4932
+ if (plan.isTerminal?.(event)) finalize(() => resolve5());
4933
+ }
4934
+ async function startPolling() {
4935
+ if (!plan.poll) {
4936
+ finalize(() => reject(new Error("No polling fallback configured for this watch.")));
4937
+ return;
4938
+ }
4939
+ handlers.onConnected?.("polling");
4940
+ const interval = plan.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
4941
+ const safetyLoops = plan.pollBatchSafetyLoops ?? DEFAULT_POLL_SAFETY_LOOPS;
4942
+ const loop = async () => {
4943
+ if (terminated) return;
4944
+ try {
4945
+ for (let i = 0; i < safetyLoops; i += 1) {
4946
+ const batch = await plan.poll(cursor);
4947
+ let anyNew = false;
4948
+ for (const event of batch) {
4949
+ if (handle(event)) anyNew = true;
4950
+ advanceCursor(event);
4951
+ if (plan.isTerminal?.(event)) {
4952
+ finalize(() => resolve5());
4953
+ return;
4954
+ }
4955
+ }
4956
+ if (!anyNew) break;
4957
+ }
4958
+ } catch (err) {
4959
+ finalize(
4960
+ () => reject(err instanceof Error ? err : new Error(String(err)))
4961
+ );
4962
+ return;
4963
+ }
4964
+ pollTimer = setTimeout(loop, interval);
4965
+ };
4966
+ void loop();
4967
+ }
4968
+ if (!plan.subscribe) {
4969
+ void startPolling();
4970
+ return;
4971
+ }
4972
+ try {
4973
+ subscribeHandle = plan.subscribe(
4974
+ processLiveEvent,
4975
+ (err) => {
4976
+ if (err instanceof SessionTokenNotSupportedError && plan.poll) {
4977
+ subscribeHandle = void 0;
4978
+ void startPolling();
4979
+ return;
4980
+ }
4981
+ finalize(() => reject(err));
4982
+ },
4983
+ () => handlers.onConnected?.("subscription")
4984
+ );
4985
+ } catch (err) {
4986
+ if (err instanceof SessionTokenNotSupportedError && plan.poll) {
4987
+ void startPolling();
4988
+ return;
4989
+ }
4990
+ finalize(() => reject(err instanceof Error ? err : new Error(String(err))));
4991
+ }
4992
+ });
4993
+ }
3714
4994
 
3715
4995
  // src/commands/deploy.ts
4996
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["active", "failed", "superseded"]);
4997
+ var WATCH_POLL_INTERVAL_MS = 2e3;
4998
+ var WATCH_DEADLINE_MS = 5 * 6e4;
3716
4999
  async function deployCommand(options) {
3717
5000
  let stagingDir;
3718
5001
  try {
3719
5002
  const config = await loadConfig({
3720
5003
  dir: options.dir
3721
5004
  });
3722
- console.log(chalk11.gray("Planning deployment..."));
5005
+ console.log(chalk12.gray("Planning deployment..."));
3723
5006
  stagingDir = await createStagingDir();
3724
- const { manifest } = await buildPipeline(
5007
+ const { manifest, catalog: catalog2 } = await buildPipeline(
3725
5008
  config,
3726
5009
  process.cwd(),
3727
5010
  options.minify ?? true,
@@ -3729,7 +5012,7 @@ async function deployCommand(options) {
3729
5012
  );
3730
5013
  if (!config.apiUrl || !config.projectId) {
3731
5014
  console.log(
3732
- chalk11.yellow(
5015
+ chalk12.yellow(
3733
5016
  "\nNo apiUrl or projectId configured. Skipping upload."
3734
5017
  )
3735
5018
  );
@@ -3739,30 +5022,37 @@ async function deployCommand(options) {
3739
5022
  apiUrl: config.apiUrl,
3740
5023
  projectId: config.projectId
3741
5024
  });
3742
- console.log(chalk11.gray("\nComparing with remote..."));
3743
- const remoteManifest = await client.fetchRemoteManifest(client.projectId());
5025
+ console.log(chalk12.gray("\nComparing with remote..."));
5026
+ const remoteManifest = await client.fetchRemoteManifest(
5027
+ client.projectId()
5028
+ );
5029
+ const remoteCatalog = await client.fetchRemoteCatalog(client.projectId());
3744
5030
  const diff = diffManifests(manifest, remoteManifest);
3745
- if (!diff.hasChanges) {
3746
- console.log(chalk11.green("\n\u2713 No changes to deploy."));
5031
+ const catalogDiff = diffCatalogs(catalog2, remoteCatalog);
5032
+ if (!diff.hasChanges && !catalogDiff.hasChanges) {
5033
+ console.log(chalk12.green("\n\u2713 No changes to deploy."));
3747
5034
  return;
3748
5035
  }
3749
- console.log("\n" + formatPlan(diff));
5036
+ console.log("\n" + formatPlan(diff, catalogDiff));
3750
5037
  const confirmed = await confirmPlan(options.yes ?? false);
3751
5038
  if (!confirmed) {
3752
- console.log(chalk11.yellow("Deploy cancelled."));
5039
+ console.log(chalk12.yellow("Deploy cancelled."));
3753
5040
  return;
3754
5041
  }
3755
- console.log(chalk11.gray("\nApplying deployment..."));
5042
+ console.log(chalk12.gray("\nApplying deployment..."));
3756
5043
  const zipPath = join(stagingDir, "deploy.zip");
3757
5044
  await zipBuildOutput(stagingDir, zipPath);
3758
- const revision = await client.deployBundle(zipPath, manifest);
5045
+ const revision = await client.deployBundle(zipPath, manifest, catalog2);
3759
5046
  console.log(
3760
- chalk11.green(`
3761
- \u2713 Deployment complete (revision ${revision.revision}, ${revision.status})`)
5047
+ chalk12.green(`
5048
+ \u2713 Deployment submitted (revision ${revision.revision}, ${revision.status})`)
3762
5049
  );
5050
+ if (options.watch) {
5051
+ await watchDeployment(client, revision);
5052
+ }
3763
5053
  } catch (err) {
3764
5054
  if (err instanceof CliError) {
3765
- console.error(chalk11.red(err.message));
5055
+ console.error(chalk12.red(err.message));
3766
5056
  process.exit(1);
3767
5057
  }
3768
5058
  throw err;
@@ -3770,6 +5060,72 @@ async function deployCommand(options) {
3770
5060
  await cleanupStagingDir(stagingDir);
3771
5061
  }
3772
5062
  }
5063
+ function colorStatus(status) {
5064
+ switch (status) {
5065
+ case "active":
5066
+ return chalk12.green(status);
5067
+ case "failed":
5068
+ return chalk12.red(status);
5069
+ case "superseded":
5070
+ return chalk12.yellow(status);
5071
+ case "staged":
5072
+ return chalk12.cyan(status);
5073
+ case "draft":
5074
+ default:
5075
+ return chalk12.gray(status);
5076
+ }
5077
+ }
5078
+ async function watchDeployment(client, initial) {
5079
+ if (TERMINAL_STATUSES.has(initial.status)) {
5080
+ return;
5081
+ }
5082
+ console.log(chalk12.gray(`
5083
+ Watching revision ${initial.revision}\u2026`));
5084
+ let lastStatus = initial.status;
5085
+ const startedAt = Date.now();
5086
+ await watch(
5087
+ {
5088
+ poll: async () => {
5089
+ if (Date.now() - startedAt > WATCH_DEADLINE_MS) {
5090
+ throw new Error(
5091
+ `Deployment did not reach a terminal status within ${WATCH_DEADLINE_MS / 1e3}s.`
5092
+ );
5093
+ }
5094
+ const next = await client.deploymentStatus(
5095
+ client.projectId(),
5096
+ initial.revision
5097
+ );
5098
+ return next ? [next] : [];
5099
+ },
5100
+ pollIntervalMs: WATCH_POLL_INTERVAL_MS,
5101
+ getId: (rev) => `${rev.revision}:${rev.status}:${rev.updatedAt}`,
5102
+ isTerminal: (rev) => TERMINAL_STATUSES.has(rev.status)
5103
+ },
5104
+ {
5105
+ onEvent: (rev) => {
5106
+ if (rev.status !== lastStatus) {
5107
+ console.log(
5108
+ ` revision ${rev.revision} \u2192 ${colorStatus(rev.status)}`
5109
+ );
5110
+ lastStatus = rev.status;
5111
+ }
5112
+ }
5113
+ }
5114
+ );
5115
+ if (lastStatus === "active") {
5116
+ console.log(chalk12.green(`
5117
+ \u2713 Deployment active (revision ${initial.revision}).`));
5118
+ } else if (lastStatus === "failed") {
5119
+ console.log(chalk12.red(`
5120
+ \u2717 Deployment failed (revision ${initial.revision}).`));
5121
+ process.exit(1);
5122
+ } else if (lastStatus === "superseded") {
5123
+ console.log(
5124
+ chalk12.yellow(`
5125
+ ! Revision ${initial.revision} was superseded by a later deploy.`)
5126
+ );
5127
+ }
5128
+ }
3773
5129
  var SUCCESS_HTML = `<!DOCTYPE html>
3774
5130
  <html>
3775
5131
  <head><title>IO CLI</title><style>
@@ -3873,7 +5229,7 @@ async function loginCommand(options) {
3873
5229
  try {
3874
5230
  if (options.token) {
3875
5231
  await saveToken(options.token, true);
3876
- console.log(chalk11.green("\u2713 API key saved"));
5232
+ console.log(chalk12.green("\u2713 API key saved"));
3877
5233
  return;
3878
5234
  }
3879
5235
  const config = await loadConfig();
@@ -3884,7 +5240,7 @@ async function loginCommand(options) {
3884
5240
  }
3885
5241
  const existing = await loadToken();
3886
5242
  if (existing) {
3887
- console.log(chalk11.gray("Existing session found. Re-authenticating..."));
5243
+ console.log(chalk12.gray("Existing session found. Re-authenticating..."));
3888
5244
  }
3889
5245
  const callbackServer = await startCallbackServer();
3890
5246
  const pkce = generatePkcePair();
@@ -3894,12 +5250,12 @@ async function loginCommand(options) {
3894
5250
  callbackServer.state,
3895
5251
  pkce.challenge
3896
5252
  );
3897
- console.log(chalk11.gray("Opening your browser for sign-in..."));
5253
+ console.log(chalk12.gray("Opening your browser for sign-in..."));
3898
5254
  try {
3899
5255
  await open(authorizationUrl);
3900
5256
  } catch {
3901
- console.log(chalk11.yellow("Could not open the browser automatically."));
3902
- console.log(chalk11.gray(`Open this URL manually:
5257
+ console.log(chalk12.yellow("Could not open the browser automatically."));
5258
+ console.log(chalk12.gray(`Open this URL manually:
3903
5259
  ${authorizationUrl}`));
3904
5260
  }
3905
5261
  try {
@@ -3923,10 +5279,10 @@ ${authorizationUrl}`));
3923
5279
  } finally {
3924
5280
  callbackServer.close();
3925
5281
  }
3926
- console.log(chalk11.green("\n\u2713 Authenticated successfully"));
5282
+ console.log(chalk12.green("\n\u2713 Authenticated successfully"));
3927
5283
  } catch (err) {
3928
5284
  if (err instanceof CliError) {
3929
- console.error(chalk11.red(err.message));
5285
+ console.error(chalk12.red(err.message));
3930
5286
  process.exit(1);
3931
5287
  }
3932
5288
  throw err;
@@ -3936,10 +5292,10 @@ async function logoutCommand() {
3936
5292
  const creds = await loadCredentials();
3937
5293
  await clearToken();
3938
5294
  if (!creds) {
3939
- console.log(chalk11.yellow("Not logged in."));
5295
+ console.log(chalk12.yellow("Not logged in."));
3940
5296
  return;
3941
5297
  }
3942
- console.log(chalk11.green("\u2713 Logged out"));
5298
+ console.log(chalk12.green("\u2713 Logged out"));
3943
5299
  }
3944
5300
  async function hydrateIdentity() {
3945
5301
  const creds = await loadCredentials();
@@ -3983,35 +5339,35 @@ async function hydrateIdentity() {
3983
5339
  async function whoamiCommand() {
3984
5340
  const { creds, sessionName, sessionEmail } = await hydrateIdentity();
3985
5341
  if (!creds) {
3986
- console.log(chalk11.yellow("Not logged in. Run `io login` to authenticate."));
5342
+ console.log(chalk12.yellow("Not logged in. Run `io login` to authenticate."));
3987
5343
  return;
3988
5344
  }
3989
- console.log(chalk11.green("\u2713 Logged in"));
5345
+ console.log(chalk12.green("\u2713 Logged in"));
3990
5346
  const displayUser = sessionName ?? sessionEmail ?? creds.userId;
3991
5347
  if (displayUser) {
3992
- console.log(chalk11.gray(` User: ${displayUser}`));
5348
+ console.log(chalk12.gray(` User: ${displayUser}`));
3993
5349
  }
3994
5350
  if (sessionEmail && sessionEmail !== displayUser) {
3995
- console.log(chalk11.gray(` Email: ${sessionEmail}`));
5351
+ console.log(chalk12.gray(` Email: ${sessionEmail}`));
3996
5352
  }
3997
5353
  if (creds.organizationId) {
3998
- console.log(chalk11.gray(` Organization: ${creds.organizationId}`));
5354
+ console.log(chalk12.gray(` Organization: ${creds.organizationId}`));
3999
5355
  }
4000
5356
  if (creds.apiKey) {
4001
- console.log(chalk11.gray(" Session: API key"));
5357
+ console.log(chalk12.gray(" Session: API key"));
4002
5358
  } else if (creds.refreshToken) {
4003
- console.log(chalk11.gray(" Session: refresh token stored"));
5359
+ console.log(chalk12.gray(" Session: refresh token stored"));
4004
5360
  } else {
4005
- console.log(chalk11.gray(" Session: token only (no refresh)"));
5361
+ console.log(chalk12.gray(" Session: token only (no refresh)"));
4006
5362
  }
4007
5363
  }
4008
5364
  var CONFIG_FILE = "io.config.json";
4009
5365
  async function prompt(question) {
4010
5366
  const rl = createInterface({ input: process.stdin, output: process.stdout });
4011
- return new Promise((resolve4) => {
5367
+ return new Promise((resolve5) => {
4012
5368
  rl.question(question, (answer) => {
4013
5369
  rl.close();
4014
- resolve4(answer.trim());
5370
+ resolve5(answer.trim());
4015
5371
  });
4016
5372
  });
4017
5373
  }
@@ -4032,13 +5388,13 @@ async function selectOrganization(organizations, preferredId) {
4032
5388
  if (organizations.length === 1) {
4033
5389
  return organizations[0];
4034
5390
  }
4035
- console.log(chalk11.gray("\nAvailable organizations:"));
5391
+ console.log(chalk12.gray("\nAvailable organizations:"));
4036
5392
  organizations.forEach((organization, index2) => {
4037
5393
  console.log(
4038
- ` ${chalk11.green(String(index2 + 1))}. ${organization.name} ${chalk11.gray(organization.id)}`
5394
+ ` ${chalk12.green(String(index2 + 1))}. ${organization.name} ${chalk12.gray(organization.id)}`
4039
5395
  );
4040
5396
  });
4041
- const choice = await prompt(chalk11.gray("\nSelect an organization (number or ID): "));
5397
+ const choice = await prompt(chalk12.gray("\nSelect an organization (number or ID): "));
4042
5398
  const index = Number(choice);
4043
5399
  if (index > 0 && index <= organizations.length) {
4044
5400
  return organizations[index - 1];
@@ -4051,16 +5407,16 @@ async function initCommand(options) {
4051
5407
  const existing = await readExistingConfig(cwd);
4052
5408
  if (existing.projectId && !options.yes) {
4053
5409
  const overwrite = await prompt(
4054
- chalk11.yellow(`io.config.json already has projectId "${existing.projectId}". Overwrite? (y/n) `)
5410
+ chalk12.yellow(`io.config.json already has projectId "${existing.projectId}". Overwrite? (y/n) `)
4055
5411
  );
4056
5412
  if (overwrite.toLowerCase() !== "y") {
4057
- console.log(chalk11.gray("Cancelled."));
5413
+ console.log(chalk12.gray("Cancelled."));
4058
5414
  return;
4059
5415
  }
4060
5416
  }
4061
5417
  const config = { ...existing };
4062
5418
  if (!config.apiUrl) {
4063
- const apiUrl = await prompt(chalk11.gray("API URL (leave blank for default): "));
5419
+ const apiUrl = await prompt(chalk12.gray("API URL (leave blank for default): "));
4064
5420
  if (apiUrl) {
4065
5421
  config.apiUrl = apiUrl;
4066
5422
  }
@@ -4082,7 +5438,7 @@ async function initCommand(options) {
4082
5438
  try {
4083
5439
  organizations = await client.organizations();
4084
5440
  } catch {
4085
- console.log(chalk11.yellow("Could not fetch organizations. Enter project ID manually."));
5441
+ console.log(chalk12.yellow("Could not fetch organizations. Enter project ID manually."));
4086
5442
  }
4087
5443
  const selectedOrganization = await selectOrganization(
4088
5444
  organizations,
@@ -4091,14 +5447,14 @@ async function initCommand(options) {
4091
5447
  if (selectedOrganization) {
4092
5448
  const projects2 = await client.projects(selectedOrganization.id);
4093
5449
  if (projects2.length > 0) {
4094
- console.log(chalk11.gray("\nAvailable projects:"));
5450
+ console.log(chalk12.gray("\nAvailable projects:"));
4095
5451
  projects2.forEach((project, index2) => {
4096
5452
  console.log(
4097
- ` ${chalk11.green(String(index2 + 1))}. ${project.name} ${chalk11.gray(project.id)}`
5453
+ ` ${chalk12.green(String(index2 + 1))}. ${project.name} ${chalk12.gray(project.id)}`
4098
5454
  );
4099
5455
  });
4100
5456
  const choice = await prompt(
4101
- chalk11.gray("\nSelect a project (number) or enter a project ID: ")
5457
+ chalk12.gray("\nSelect a project (number) or enter a project ID: ")
4102
5458
  );
4103
5459
  const index = Number(choice);
4104
5460
  if (index > 0 && index <= projects2.length) {
@@ -4108,26 +5464,26 @@ async function initCommand(options) {
4108
5464
  }
4109
5465
  } else {
4110
5466
  const createNew = await prompt(
4111
- chalk11.gray("No projects found. Create one? (y/n) ")
5467
+ chalk12.gray("No projects found. Create one? (y/n) ")
4112
5468
  );
4113
5469
  if (createNew.toLowerCase() === "y") {
4114
- const name = await prompt(chalk11.gray("Project name: "));
5470
+ const name = await prompt(chalk12.gray("Project name: "));
4115
5471
  if (name) {
4116
5472
  const project = await client.createProject(selectedOrganization.id, name);
4117
5473
  config.projectId = project.id;
4118
- console.log(chalk11.green(`\u2713 Created project: ${project.name}`));
5474
+ console.log(chalk12.green(`\u2713 Created project: ${project.name}`));
4119
5475
  }
4120
5476
  }
4121
5477
  }
4122
5478
  } else {
4123
- const projectId = await prompt(chalk11.gray("Project ID (or leave blank): "));
5479
+ const projectId = await prompt(chalk12.gray("Project ID (or leave blank): "));
4124
5480
  if (projectId) {
4125
5481
  config.projectId = projectId;
4126
5482
  }
4127
5483
  }
4128
5484
  } else {
4129
- console.log(chalk11.yellow("Not logged in. Run `io login` first to select a project."));
4130
- const projectId = await prompt(chalk11.gray("Project ID (or leave blank): "));
5485
+ console.log(chalk12.yellow("Not logged in. Run `io login` first to select a project."));
5486
+ const projectId = await prompt(chalk12.gray("Project ID (or leave blank): "));
4131
5487
  if (projectId) {
4132
5488
  config.projectId = projectId;
4133
5489
  }
@@ -4137,14 +5493,14 @@ async function initCommand(options) {
4137
5493
  JSON.stringify(config, null, 2) + "\n",
4138
5494
  "utf-8"
4139
5495
  );
4140
- console.log(chalk11.green(`
5496
+ console.log(chalk12.green(`
4141
5497
  \u2713 Wrote ${CONFIG_FILE}`));
4142
5498
  if (config.projectId) {
4143
- console.log(chalk11.gray(` Project: ${config.projectId}`));
5499
+ console.log(chalk12.gray(` Project: ${config.projectId}`));
4144
5500
  }
4145
5501
  } catch (err) {
4146
5502
  if (err instanceof CliError) {
4147
- console.error(chalk11.red(err.message));
5503
+ console.error(chalk12.red(err.message));
4148
5504
  process.exit(1);
4149
5505
  }
4150
5506
  throw err;
@@ -4172,16 +5528,65 @@ async function projectsListCommand() {
4172
5528
  const organizationId = await resolveOrganizationId(client);
4173
5529
  const projects2 = await client.projects(organizationId);
4174
5530
  if (!projects2 || projects2.length === 0) {
4175
- console.log(chalk11.gray("No projects found. Create one with `io projects create <name>`."));
5531
+ console.log(chalk12.gray("No projects found. Create one with `io projects create <name>`."));
4176
5532
  return;
4177
5533
  }
4178
5534
  for (const p of projects2) {
4179
- const current = p.id === config.projectId ? chalk11.green(" (current)") : "";
4180
- console.log(` ${p.name} ${chalk11.gray(p.id)}${current}`);
5535
+ const current = p.id === config.projectId ? chalk12.green(" (current)") : "";
5536
+ console.log(` ${p.name} ${chalk12.gray(p.id)}${current}`);
5537
+ }
5538
+ } catch (err) {
5539
+ if (err instanceof CliError) {
5540
+ console.error(chalk12.red(err.message));
5541
+ process.exit(1);
5542
+ }
5543
+ throw err;
5544
+ }
5545
+ }
5546
+ async function confirmDelete(projectName, projectId) {
5547
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
5548
+ return new Promise((resolve5) => {
5549
+ rl.question(
5550
+ `Delete project "${projectName}" (${projectId})? Type the project name to confirm: `,
5551
+ (answer) => {
5552
+ rl.close();
5553
+ resolve5(answer.trim() === projectName);
5554
+ }
5555
+ );
5556
+ });
5557
+ }
5558
+ async function projectsDeleteCommand(id, options = {}) {
5559
+ try {
5560
+ const config = await loadConfig();
5561
+ const client = createClient(config);
5562
+ const organizationId = options.organizationId ?? await resolveOrganizationId(client);
5563
+ const projects2 = await client.projects(organizationId);
5564
+ const target = projects2.find((p) => p.id === id);
5565
+ if (!target) {
5566
+ throw new ConfigError(
5567
+ `Project "${id}" not found under organization ${organizationId}.`
5568
+ );
5569
+ }
5570
+ if (!options.yes) {
5571
+ const confirmed = await confirmDelete(target.name, target.id);
5572
+ if (!confirmed) {
5573
+ console.log(chalk12.yellow("Delete cancelled (name did not match)."));
5574
+ return;
5575
+ }
5576
+ }
5577
+ const deleted = await client.deleteProject(organizationId, target.id);
5578
+ console.log(chalk12.green(`\u2713 Deleted project: ${deleted.name}`));
5579
+ console.log(chalk12.gray(` ID: ${deleted.id}`));
5580
+ if (config.projectId === deleted.id) {
5581
+ console.log(
5582
+ chalk12.yellow(
5583
+ "\n io.config.json still references this project \u2014 clear the projectId field before redeploying."
5584
+ )
5585
+ );
4181
5586
  }
4182
5587
  } catch (err) {
4183
5588
  if (err instanceof CliError) {
4184
- console.error(chalk11.red(err.message));
5589
+ console.error(chalk12.red(err.message));
4185
5590
  process.exit(1);
4186
5591
  }
4187
5592
  throw err;
@@ -4193,10 +5598,10 @@ async function projectsCreateCommand(name) {
4193
5598
  const client = createClient(config);
4194
5599
  const organizationId = await resolveOrganizationId(client);
4195
5600
  const project = await client.createProject(organizationId, name);
4196
- console.log(chalk11.green(`\u2713 Created project: ${project.name}`));
4197
- console.log(chalk11.gray(` ID: ${project.id}`));
5601
+ console.log(chalk12.green(`\u2713 Created project: ${project.name}`));
5602
+ console.log(chalk12.gray(` ID: ${project.id}`));
4198
5603
  console.log(
4199
- chalk11.gray(
5604
+ chalk12.gray(
4200
5605
  `
4201
5606
  Add to io.config.json:
4202
5607
  { "projectId": "${project.id}" }`
@@ -4204,7 +5609,7 @@ async function projectsCreateCommand(name) {
4204
5609
  );
4205
5610
  } catch (err) {
4206
5611
  if (err instanceof CliError) {
4207
- console.error(chalk11.red(err.message));
5612
+ console.error(chalk12.red(err.message));
4208
5613
  process.exit(1);
4209
5614
  }
4210
5615
  throw err;
@@ -4216,10 +5621,10 @@ async function secretsSetCommand(name, value) {
4216
5621
  const client = createClient(config);
4217
5622
  const pid = client.projectId();
4218
5623
  await client.putSecret(pid, { name, value });
4219
- console.log(chalk11.green(`\u2713 Set secret: ${name}`));
5624
+ console.log(chalk12.green(`\u2713 Set secret: ${name}`));
4220
5625
  } catch (err) {
4221
5626
  if (err instanceof CliError) {
4222
- console.error(chalk11.red(err.message));
5627
+ console.error(chalk12.red(err.message));
4223
5628
  process.exit(1);
4224
5629
  }
4225
5630
  throw err;
@@ -4231,16 +5636,16 @@ async function secretsListCommand() {
4231
5636
  const client = createClient(config);
4232
5637
  const secrets2 = await client.listSecrets(client.projectId());
4233
5638
  if (!secrets2 || secrets2.length === 0) {
4234
- console.log(chalk11.gray("No secrets configured."));
5639
+ console.log(chalk12.gray("No secrets configured."));
4235
5640
  return;
4236
5641
  }
4237
5642
  for (const s of secrets2) {
4238
- const value = s.hasValue ? "" : chalk11.gray(" (no value)");
5643
+ const value = s.hasValue ? "" : chalk12.gray(" (no value)");
4239
5644
  console.log(` ${s.name}${value}`);
4240
5645
  }
4241
5646
  } catch (err) {
4242
5647
  if (err instanceof CliError) {
4243
- console.error(chalk11.red(err.message));
5648
+ console.error(chalk12.red(err.message));
4244
5649
  process.exit(1);
4245
5650
  }
4246
5651
  throw err;
@@ -4252,25 +5657,123 @@ async function secretsRemoveCommand(name) {
4252
5657
  const client = createClient(config);
4253
5658
  const removed = await client.deleteSecret(client.projectId(), name);
4254
5659
  if (!removed) {
4255
- console.log(chalk11.yellow(`Secret "${name}" not found.`));
5660
+ console.log(chalk12.yellow(`Secret "${name}" not found.`));
4256
5661
  return;
4257
5662
  }
4258
- console.log(chalk11.green(`\u2713 Removed secret: ${name}`));
5663
+ console.log(chalk12.green(`\u2713 Removed secret: ${name}`));
4259
5664
  } catch (err) {
4260
5665
  if (err instanceof CliError) {
4261
- console.error(chalk11.red(err.message));
5666
+ console.error(chalk12.red(err.message));
4262
5667
  process.exit(1);
4263
5668
  }
4264
5669
  throw err;
4265
5670
  }
4266
5671
  }
5672
+ function parseMetadata(input) {
5673
+ if (!input) return void 0;
5674
+ try {
5675
+ const parsed = JSON.parse(input);
5676
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
5677
+ return parsed;
5678
+ }
5679
+ } catch {
5680
+ }
5681
+ throw new ConfigError("metadata must be a JSON object");
5682
+ }
5683
+ function validateState(state) {
5684
+ if (!state) return void 0;
5685
+ if (state === "active" || state === "inactive" || state === "archived") return state;
5686
+ throw new ConfigError("state must be active, inactive, or archived");
5687
+ }
5688
+ async function withClient(options) {
5689
+ const config = await loadConfig({ projectId: options.projectId });
5690
+ const client = createClient(config);
5691
+ return { client, projectId: client.projectId() };
5692
+ }
5693
+ function handleError(err) {
5694
+ if (err instanceof CliError) {
5695
+ console.error(chalk12.red(err.message));
5696
+ process.exit(1);
5697
+ }
5698
+ throw err;
5699
+ }
5700
+ async function principalsListCommand(options) {
5701
+ try {
5702
+ const { client, projectId } = await withClient(options);
5703
+ const principals2 = await client.principals(projectId);
5704
+ if (principals2.length === 0) {
5705
+ console.log(chalk12.gray("No principals found. Create one with `io principals create <external-id>`."));
5706
+ return;
5707
+ }
5708
+ for (const principal of principals2) {
5709
+ console.log(
5710
+ ` ${principal.externalId} ${chalk12.gray(principal.id)} ${chalk12.gray(principal.state)}`
5711
+ );
5712
+ }
5713
+ } catch (err) {
5714
+ handleError(err);
5715
+ }
5716
+ }
5717
+ async function principalsCreateCommand(externalId, options) {
5718
+ try {
5719
+ const { client, projectId } = await withClient(options);
5720
+ const principal = await client.createPrincipal(projectId, {
5721
+ externalId,
5722
+ state: validateState(options.state),
5723
+ metadata: parseMetadata(options.metadata)
5724
+ });
5725
+ console.log(chalk12.green(`\u2713 Created principal: ${principal.externalId}`));
5726
+ console.log(chalk12.gray(` ID: ${principal.id}`));
5727
+ console.log(chalk12.gray(` Project: ${principal.projectId}`));
5728
+ console.log(chalk12.gray(` State: ${principal.state}`));
5729
+ } catch (err) {
5730
+ handleError(err);
5731
+ }
5732
+ }
5733
+ async function principalsUpdateCommand(id, options) {
5734
+ try {
5735
+ const { client, projectId } = await withClient(options);
5736
+ const input = {
5737
+ externalId: options.externalId,
5738
+ state: validateState(options.state),
5739
+ metadata: parseMetadata(options.metadata)
5740
+ };
5741
+ if (!input.externalId && !input.state && !input.metadata) {
5742
+ throw new ConfigError("nothing to update; pass --external-id, --state, or --metadata");
5743
+ }
5744
+ const principal = await client.updatePrincipal(projectId, id, input);
5745
+ if (!principal) {
5746
+ console.log(chalk12.yellow(`Principal "${id}" not found.`));
5747
+ return;
5748
+ }
5749
+ console.log(chalk12.green(`\u2713 Updated principal: ${principal.externalId}`));
5750
+ console.log(chalk12.gray(` ID: ${principal.id}`));
5751
+ console.log(chalk12.gray(` Project: ${principal.projectId}`));
5752
+ console.log(chalk12.gray(` State: ${principal.state}`));
5753
+ } catch (err) {
5754
+ handleError(err);
5755
+ }
5756
+ }
5757
+ async function principalsDeleteCommand(id, options) {
5758
+ try {
5759
+ const { client, projectId } = await withClient(options);
5760
+ const principal = await client.deletePrincipal(projectId, id);
5761
+ if (!principal) {
5762
+ console.log(chalk12.yellow(`Principal "${id}" not found.`));
5763
+ return;
5764
+ }
5765
+ console.log(chalk12.green(`\u2713 Deleted principal: ${principal.externalId}`));
5766
+ } catch (err) {
5767
+ handleError(err);
5768
+ }
5769
+ }
4267
5770
  function meterBar(used, limit, width = 20) {
4268
- if (limit === 0) return chalk11.gray("\u2591".repeat(width));
5771
+ if (limit === 0) return chalk12.gray("\u2591".repeat(width));
4269
5772
  const ratio = Math.min(used / limit, 1);
4270
5773
  const filled = Math.round(ratio * width);
4271
5774
  const empty = width - filled;
4272
- const color = ratio >= 0.9 ? chalk11.red : ratio >= 0.7 ? chalk11.yellow : chalk11.green;
4273
- return color("\u2588".repeat(filled)) + chalk11.gray("\u2591".repeat(empty));
5775
+ const color = ratio >= 0.9 ? chalk12.red : ratio >= 0.7 ? chalk12.yellow : chalk12.green;
5776
+ return color("\u2588".repeat(filled)) + chalk12.gray("\u2591".repeat(empty));
4274
5777
  }
4275
5778
  function formatNumber(n) {
4276
5779
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
@@ -4280,9 +5783,11 @@ function formatNumber(n) {
4280
5783
  var METER_LABELS = {
4281
5784
  events: "Events",
4282
5785
  ingest_tokens: "Ingest Tokens",
5786
+ query_tokens: "Query Tokens",
4283
5787
  voice_seconds: "Voice Seconds",
4284
5788
  compute_gb_sec: "Compute (GB\xB7s)",
4285
- storage_gb: "Storage (GB)"
5789
+ storage_gb: "Memory Storage (GB)",
5790
+ artifact_storage_gb: "Artifact Storage (GB)"
4286
5791
  };
4287
5792
  async function quotaCommand() {
4288
5793
  try {
@@ -4290,29 +5795,30 @@ async function quotaCommand() {
4290
5795
  const client = createClient(config);
4291
5796
  const statuses = await client.quotaStatuses();
4292
5797
  if (statuses.length === 0) {
4293
- console.log(chalk11.gray("No quota data available."));
5798
+ console.log(chalk12.gray("No quota data available."));
4294
5799
  return;
4295
5800
  }
4296
- console.log(chalk11.bold("\nQuota Usage\n"));
5801
+ console.log(chalk12.bold("\nQuota Usage\n"));
4297
5802
  for (const status of statuses) {
4298
5803
  const label = METER_LABELS[status.meter] ?? status.meter;
4299
5804
  const bar = meterBar(status.used, status.limit);
4300
5805
  const pct = status.limit > 0 ? `${status.percentageUsed.toFixed(1)}%` : "\u2014";
4301
5806
  const usage = `${formatNumber(status.used)} / ${formatNumber(status.limit)}`;
4302
- const enforcement = status.enforcement === "hard_cap" ? chalk11.red("hard") : chalk11.yellow("soft");
4303
- console.log(` ${label.padEnd(18)} ${bar} ${usage.padStart(16)} ${pct.padStart(6)} ${enforcement}`);
5807
+ const enforcement = status.enforcement === "hard_cap" ? chalk12.red("hard") : chalk12.yellow("soft");
5808
+ console.log(` ${label.padEnd(22)} ${bar} ${usage.padStart(16)} ${pct.padStart(6)} ${enforcement}`);
4304
5809
  }
4305
5810
  console.log();
4306
5811
  } catch (err) {
4307
5812
  if (err instanceof CliError) {
4308
- console.error(chalk11.red(err.message));
5813
+ console.error(chalk12.red(err.message));
4309
5814
  process.exit(1);
4310
5815
  }
4311
5816
  throw err;
4312
5817
  }
4313
5818
  }
4314
- Object.assign(globalThis, { WebSocket });
4315
- var ACTIVITY_SUBSCRIPTION = `
5819
+
5820
+ // src/api/subscription.ts
5821
+ var ACTIVITY_EVENT_SUBSCRIPTION = `
4316
5822
  subscription ActivityEventReceived($projectId: ID!, $input: ActivityEventFilterInput) {
4317
5823
  activityEventReceived(projectId: $projectId, input: $input) {
4318
5824
  id
@@ -4333,56 +5839,6 @@ var ACTIVITY_SUBSCRIPTION = `
4333
5839
  }
4334
5840
  }
4335
5841
  `;
4336
- function buildActivityEventsSocketUrl(apiUrl) {
4337
- const url = new URL(apiUrl);
4338
- const basePath = url.pathname.replace(/\/+$/, "");
4339
- const socketBase = basePath.endsWith("/graphql") ? basePath.slice(0, -"/graphql".length) : basePath;
4340
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
4341
- url.pathname = `${socketBase === "/" ? "" : socketBase}/socket`;
4342
- url.search = "";
4343
- url.hash = "";
4344
- return url.toString();
4345
- }
4346
- function subscribeToActivityEvents(options) {
4347
- if (!options.credentials.apiKey) {
4348
- throw new ConfigError(
4349
- "Live activity subscriptions require API key authentication on the current io_beam socket contract."
4350
- );
4351
- }
4352
- const phoenixSocket = new Socket(buildActivityEventsSocketUrl(options.apiUrl), {
4353
- params: socketAuthParams(options.credentials),
4354
- timeout: 1e4
4355
- });
4356
- const absintheSocket = AbsintheSocket.create(phoenixSocket);
4357
- const notifier = AbsintheSocket.send(absintheSocket, {
4358
- operation: ACTIVITY_SUBSCRIPTION,
4359
- variables: { projectId: options.projectId, input: options.input }
4360
- });
4361
- const observer = {
4362
- onAbort: (error) => {
4363
- options.onError?.(error);
4364
- },
4365
- onCancel: () => {
4366
- },
4367
- onError: (error) => {
4368
- options.onError?.(error);
4369
- },
4370
- onResult: (result) => {
4371
- const event = result.data?.activityEventReceived;
4372
- if (event) {
4373
- options.onEvent(event);
4374
- }
4375
- },
4376
- onStart: () => {
4377
- options.onConnected?.();
4378
- }
4379
- };
4380
- AbsintheSocket.observe(absintheSocket, notifier, observer);
4381
- phoenixSocket.connect();
4382
- return () => {
4383
- phoenixSocket.disconnect();
4384
- };
4385
- }
4386
5842
 
4387
5843
  // src/activity/presentation.ts
4388
5844
  var DISPLAY_NAME_NAMESPACES = /* @__PURE__ */ new Set([
@@ -4701,18 +6157,17 @@ function inferActivityPrincipalLabel(event) {
4701
6157
 
4702
6158
  // src/commands/logs.ts
4703
6159
  var LEVEL_COLORS = {
4704
- error: chalk11.red,
4705
- warning: chalk11.yellow,
4706
- info: chalk11.blue,
4707
- success: chalk11.green,
4708
- scheduled: chalk11.magenta
6160
+ error: chalk12.red,
6161
+ warning: chalk12.yellow,
6162
+ info: chalk12.blue,
6163
+ success: chalk12.green,
6164
+ scheduled: chalk12.magenta
4709
6165
  };
4710
6166
  var FOLLOW_POLL_INTERVAL_MS = 2e3;
4711
- var FOLLOW_SEEN_ID_LIMIT = 1e3;
4712
6167
  var FOLLOW_BATCH_LIMIT = 200;
4713
6168
  function colorLevel(level) {
4714
- if (!level) return chalk11.gray("\u2014");
4715
- const colorFn = LEVEL_COLORS[level] ?? chalk11.gray;
6169
+ if (!level) return chalk12.gray("\u2014");
6170
+ const colorFn = LEVEL_COLORS[level] ?? chalk12.gray;
4716
6171
  return colorFn(level.padEnd(7));
4717
6172
  }
4718
6173
  function pad(value, width) {
@@ -4725,8 +6180,8 @@ function formatTime(isoString) {
4725
6180
  }
4726
6181
  function formatDuration(ms) {
4727
6182
  if (ms == null) return "";
4728
- if (ms < 1e3) return chalk11.gray(`${ms}ms`);
4729
- return chalk11.gray(`${(ms / 1e3).toFixed(1)}s`);
6183
+ if (ms < 1e3) return chalk12.gray(`${ms}ms`);
6184
+ return chalk12.gray(`${(ms / 1e3).toFixed(1)}s`);
4730
6185
  }
4731
6186
  function truncate(value, width) {
4732
6187
  if (value.length <= width) return value;
@@ -4734,19 +6189,19 @@ function truncate(value, width) {
4734
6189
  }
4735
6190
  function formatHeader() {
4736
6191
  return [
4737
- ` ${chalk11.gray(pad("TIME", 10))}`,
4738
- chalk11.gray(pad("STATUS", 9)),
4739
- chalk11.gray(pad("CATEGORY", 14)),
4740
- chalk11.gray(pad("NAME", 24)),
4741
- chalk11.gray(pad("DURATION", 10)),
4742
- chalk11.gray("DETAIL")
6192
+ ` ${chalk12.gray(pad("TIME", 10))}`,
6193
+ chalk12.gray(pad("STATUS", 9)),
6194
+ chalk12.gray(pad("CATEGORY", 14)),
6195
+ chalk12.gray(pad("NAME", 24)),
6196
+ chalk12.gray(pad("DURATION", 10)),
6197
+ chalk12.gray("DETAIL")
4743
6198
  ].join(" ");
4744
6199
  }
4745
6200
  function formatEvent(event) {
4746
- const time = chalk11.gray(formatTime(event.occurredAt));
6201
+ const time = chalk12.gray(formatTime(event.occurredAt));
4747
6202
  const statusKey = inferActivityEventStatus(event);
4748
6203
  const status = colorLevel(statusKey);
4749
- const category = chalk11.cyan(pad(inferActivityEventCategory(event), 14));
6204
+ const category = chalk12.cyan(pad(inferActivityEventCategory(event), 14));
4750
6205
  const name = pad(truncate(inferActivityEventName(event), 24), 24);
4751
6206
  const duration = formatDuration(event.durationMs);
4752
6207
  const detail = inferActivityEventDetail(event) || inferActivityPrincipalLabel(event) || "";
@@ -4756,77 +6211,16 @@ function formatEvent(event) {
4756
6211
  category,
4757
6212
  name,
4758
6213
  pad(duration || "", 10),
4759
- detail ? chalk11.gray(detail) : ""
6214
+ detail ? chalk12.gray(detail) : ""
4760
6215
  ].join(" ").trimEnd();
4761
6216
  }
4762
- function latestOccurredAt(events, fallback) {
4763
- return events.reduce((latest, event) => {
4764
- return event.occurredAt > latest ? event.occurredAt : latest;
4765
- }, fallback);
4766
- }
4767
- function trimSeenIds(seenIds, order) {
4768
- while (order.length > FOLLOW_SEEN_ID_LIMIT) {
4769
- const evicted = order.shift();
4770
- if (evicted) {
4771
- seenIds.delete(evicted);
4772
- }
4773
- }
4774
- }
4775
- function orderEvents(events) {
4776
- return [...events].sort((left, right) => {
4777
- if (left.occurredAt === right.occurredAt) {
4778
- return left.id.localeCompare(right.id);
4779
- }
4780
- return left.occurredAt.localeCompare(right.occurredAt);
4781
- });
4782
- }
4783
- function wait(ms) {
4784
- return new Promise((resolve4) => {
4785
- setTimeout(resolve4, ms);
4786
- });
4787
- }
4788
- function followBatchInput(input, since) {
6217
+ function followPollInput(input, since) {
4789
6218
  return {
4790
6219
  ...input,
4791
6220
  since,
4792
6221
  limit: Math.max(input?.limit ?? 0, FOLLOW_BATCH_LIMIT)
4793
6222
  };
4794
6223
  }
4795
- async function drainFollowEvents(client, projectId, input, since, seenIds, seenOrder) {
4796
- let cursor = since;
4797
- const events = [];
4798
- for (let i = 0; i < 10; i += 1) {
4799
- const batch = orderEvents(
4800
- await client.projectEvents(projectId, followBatchInput(input, cursor))
4801
- );
4802
- const unseen = batch.filter((event) => !seenIds.has(event.id));
4803
- if (unseen.length === 0) {
4804
- return { events, since: cursor };
4805
- }
4806
- events.push(...unseen);
4807
- cursor = latestOccurredAt(unseen, cursor);
4808
- for (const event of unseen) {
4809
- seenIds.add(event.id);
4810
- seenOrder.push(event.id);
4811
- }
4812
- trimSeenIds(seenIds, seenOrder);
4813
- if (batch.length < FOLLOW_BATCH_LIMIT) {
4814
- return { events, since: cursor };
4815
- }
4816
- }
4817
- return { events, since: cursor };
4818
- }
4819
- function installSigintHandler(onExit) {
4820
- const handleSigint = () => {
4821
- console.log(chalk11.gray("\n Disconnected."));
4822
- onExit();
4823
- process.exit(0);
4824
- };
4825
- process.once("SIGINT", handleSigint);
4826
- return () => {
4827
- process.removeListener("SIGINT", handleSigint);
4828
- };
4829
- }
4830
6224
  async function logsCommand(options) {
4831
6225
  try {
4832
6226
  const config = await loadConfig();
@@ -4842,18 +6236,18 @@ async function logsCommand(options) {
4842
6236
  const since = parseSince(options.since);
4843
6237
  if (since) input.since = since;
4844
6238
  }
4845
- const events = await client.projectEvents(pid, input);
4846
- if (events.length === 0 && !options.follow) {
4847
- console.log(chalk11.gray("No events found."));
6239
+ const initialEvents = await client.projectEvents(pid, input);
6240
+ if (initialEvents.length === 0 && !options.follow) {
6241
+ console.log(chalk12.gray("No events found."));
4848
6242
  return;
4849
6243
  }
4850
- if (events.length > 0) {
4851
- console.log(chalk11.bold(`
4852
- Activity Log \u2014 ${events.length} events
6244
+ if (initialEvents.length > 0) {
6245
+ console.log(chalk12.bold(`
6246
+ Activity Log \u2014 ${initialEvents.length} events
4853
6247
  `));
4854
6248
  console.log(formatHeader());
4855
- console.log(chalk11.gray(` ${"\u2500".repeat(92)}`));
4856
- for (const event of events) {
6249
+ console.log(chalk12.gray(` ${"\u2500".repeat(92)}`));
6250
+ for (const event of initialEvents) {
4857
6251
  console.log(formatEvent(event));
4858
6252
  }
4859
6253
  }
@@ -4863,7 +6257,7 @@ Activity Log \u2014 ${events.length} events
4863
6257
  client,
4864
6258
  pid,
4865
6259
  input,
4866
- events,
6260
+ initialEvents,
4867
6261
  followStartedAt ?? (/* @__PURE__ */ new Date()).toISOString()
4868
6262
  );
4869
6263
  } else {
@@ -4871,7 +6265,7 @@ Activity Log \u2014 ${events.length} events
4871
6265
  }
4872
6266
  } catch (err) {
4873
6267
  if (err instanceof CliError) {
4874
- console.error(chalk11.red(err.message));
6268
+ console.error(chalk12.red(err.message));
4875
6269
  process.exit(1);
4876
6270
  }
4877
6271
  throw err;
@@ -4882,89 +6276,69 @@ async function tailEvents(apiUrl, client, projectId, input, initialEvents, follo
4882
6276
  if (!creds || !creds.token) {
4883
6277
  throw new ConfigError("Not authenticated. Run `io login` first.");
4884
6278
  }
4885
- console.log(chalk11.gray("\n Waiting for events... (Ctrl+C to stop)\n"));
4886
- if (creds.apiKey) {
4887
- return new Promise((_resolve, reject) => {
4888
- let disconnect;
4889
- const seenIds2 = new Set(initialEvents.map((event) => event.id));
4890
- const seenOrder2 = initialEvents.map((event) => event.id);
4891
- const removeSigintHandler2 = installSigintHandler(() => {
4892
- disconnect?.();
4893
- });
4894
- disconnect = subscribeToActivityEvents({
4895
- apiUrl,
4896
- credentials: creds,
4897
- projectId,
4898
- input,
6279
+ console.log(chalk12.gray("\n Waiting for events... (Ctrl+C to stop)\n"));
6280
+ const initialSince = initialEvents.reduce(
6281
+ (latest, event) => event.occurredAt > latest ? event.occurredAt : latest,
6282
+ followStartedAt
6283
+ );
6284
+ const socketClient = createSocketClient({ apiUrl, credentials: creds });
6285
+ const subscriptionVars = input;
6286
+ let liveStarted = false;
6287
+ try {
6288
+ await watch(
6289
+ {
6290
+ subscribe: (onEvent, onError, onConnected) => {
6291
+ const unsubscribe = socketClient.subscribe(
6292
+ ACTIVITY_EVENT_SUBSCRIPTION,
6293
+ { projectId, input: subscriptionVars },
6294
+ {
6295
+ onStart: onConnected,
6296
+ onResult: (result) => {
6297
+ const event = result.data?.activityEventReceived;
6298
+ if (event) onEvent(event);
6299
+ },
6300
+ onError,
6301
+ onAbort: onError
6302
+ }
6303
+ );
6304
+ return { unsubscribe };
6305
+ },
6306
+ poll: async (since) => {
6307
+ return client.projectEvents(projectId, followPollInput(input, since));
6308
+ },
6309
+ pollIntervalMs: FOLLOW_POLL_INTERVAL_MS,
6310
+ initialSince,
6311
+ getId: (event) => event.id,
6312
+ getCursor: (event) => event.occurredAt,
6313
+ seenIdLimit: 1e3
6314
+ },
6315
+ {
4899
6316
  onEvent: (event) => {
4900
- if (seenIds2.has(event.id)) {
4901
- return;
6317
+ if (!liveStarted) {
6318
+ liveStarted = true;
4902
6319
  }
4903
6320
  console.log(formatEvent(event));
4904
- seenIds2.add(event.id);
4905
- seenOrder2.push(event.id);
4906
- trimSeenIds(seenIds2, seenOrder2);
4907
6321
  },
4908
- onConnected: () => {
4909
- console.log(chalk11.green(" \u25CF Connected \u2014 tailing live events\n"));
4910
- void drainFollowEvents(
4911
- client,
4912
- projectId,
4913
- input,
4914
- followStartedAt,
4915
- seenIds2,
4916
- seenOrder2
4917
- ).then(({ events }) => {
4918
- for (const event of events) {
4919
- console.log(formatEvent(event));
4920
- }
4921
- }).catch((error) => {
4922
- console.error(chalk11.red(`
4923
- Connection error: ${error.message}`));
4924
- removeSigintHandler2();
4925
- disconnect?.();
4926
- reject(error);
4927
- });
6322
+ onConnected: (mode) => {
6323
+ if (mode === "subscription") {
6324
+ console.log(chalk12.green(" \u25CF Connected \u2014 tailing live events\n"));
6325
+ } else {
6326
+ console.log(
6327
+ chalk12.green(
6328
+ " \u25CF Connected \u2014 polling for live events with your authenticated session\n"
6329
+ )
6330
+ );
6331
+ }
4928
6332
  },
4929
- onError: (error) => {
4930
- console.error(chalk11.red(`
4931
- Connection error: ${error.message}`));
4932
- removeSigintHandler2();
4933
- disconnect?.();
4934
- reject(error);
6333
+ onError: (err) => {
6334
+ console.error(chalk12.red(`
6335
+ Connection error: ${err.message}`));
4935
6336
  }
4936
- });
4937
- });
4938
- }
4939
- console.log(
4940
- chalk11.green(" \u25CF Connected \u2014 polling for live events with your authenticated session\n")
4941
- );
4942
- const seenIds = new Set(initialEvents.map((event) => event.id));
4943
- const seenOrder = initialEvents.map((event) => event.id);
4944
- let since = latestOccurredAt(initialEvents, followStartedAt);
4945
- const removeSigintHandler = installSigintHandler(() => {
4946
- });
4947
- try {
4948
- for (; ; ) {
4949
- const batch = await drainFollowEvents(
4950
- client,
4951
- projectId,
4952
- input,
4953
- since,
4954
- seenIds,
4955
- seenOrder
4956
- );
4957
- const nextEvents = batch.events;
4958
- for (const event of nextEvents) {
4959
- console.log(formatEvent(event));
4960
- }
4961
- if (nextEvents.length > 0) {
4962
- since = batch.since;
4963
6337
  }
4964
- await wait(FOLLOW_POLL_INTERVAL_MS);
4965
- }
6338
+ );
6339
+ console.log(chalk12.gray("\n Disconnected."));
4966
6340
  } finally {
4967
- removeSigintHandler();
6341
+ socketClient.close();
4968
6342
  }
4969
6343
  }
4970
6344
  function parseSince(value) {
@@ -4997,16 +6371,20 @@ async function signalEmitCommand(name, options) {
4997
6371
  try {
4998
6372
  const config = await loadConfig({ projectId: options.projectId });
4999
6373
  const client = createClient(config);
5000
- const signal = normalizeRuntimeSignalName2(name);
6374
+ const signal = normalizeRuntimeSignalName(name);
5001
6375
  if (!signal) {
5002
6376
  throw new ConfigError(
5003
6377
  "signal name must be a bare event name like smoke.start or use the signal: namespace"
5004
6378
  );
5005
6379
  }
6380
+ if (!options.principalId || options.principalId.trim() === "") {
6381
+ throw new ConfigError("signals emit requires --principal-id <id>");
6382
+ }
5006
6383
  const payload = parseJsonObject(options.payload, "payload");
5007
6384
  const metadata = parseJsonObject(options.metadata, "metadata");
5008
6385
  const event = await client.emitSignal({
5009
6386
  signal,
6387
+ principalId: options.principalId,
5010
6388
  payload,
5011
6389
  metadata,
5012
6390
  correlationId: options.correlationId,
@@ -5018,18 +6396,266 @@ async function signalEmitCommand(name, options) {
5018
6396
  console.log(JSON.stringify(event, null, 2));
5019
6397
  return;
5020
6398
  }
5021
- console.log(chalk11.green(`\u2713 Emitted ${event.name}`));
5022
- console.log(chalk11.gray(` Event: ${event.id}`));
5023
- console.log(chalk11.gray(` Project: ${event.projectId}`));
6399
+ console.log(chalk12.green(`\u2713 Emitted ${event.name}`));
6400
+ console.log(chalk12.gray(` Event: ${event.id}`));
6401
+ console.log(chalk12.gray(` Project: ${event.projectId}`));
5024
6402
  if (event.dispatchStatus) {
5025
- console.log(chalk11.gray(` Dispatch: ${event.dispatchStatus}`));
6403
+ console.log(chalk12.gray(` Dispatch: ${event.dispatchStatus}`));
5026
6404
  }
5027
6405
  if (event.occurredAt) {
5028
- console.log(chalk11.gray(` At: ${event.occurredAt}`));
6406
+ console.log(chalk12.gray(` At: ${event.occurredAt}`));
6407
+ }
6408
+ } catch (err) {
6409
+ if (err instanceof CliError) {
6410
+ console.error(chalk12.red(err.message));
6411
+ process.exit(1);
6412
+ }
6413
+ throw err;
6414
+ }
6415
+ }
6416
+ var POLL_INTERVAL_MS = 2e3;
6417
+ var POLL_BATCH_LIMIT = 200;
6418
+ function formatEvent2(event) {
6419
+ const time = new Date(event.occurredAt).toLocaleTimeString("en-GB", {
6420
+ hour12: false
6421
+ });
6422
+ const status = inferActivityEventStatus(event);
6423
+ const category = inferActivityEventCategory(event);
6424
+ const name = inferActivityEventName(event);
6425
+ const detail = inferActivityEventDetail(event) ?? "";
6426
+ return ` ${chalk12.gray(time)} ${chalk12.cyan(category)} ${name}${detail ? ` ${chalk12.gray(detail)}` : ""}${status ? ` ${chalk12.gray(`(${status})`)}` : ""}`;
6427
+ }
6428
+ async function reconcileCommand(options) {
6429
+ try {
6430
+ const config = await loadConfig();
6431
+ const client = createClient(config);
6432
+ const projectId = options.projectId ?? client.projectId();
6433
+ console.log(chalk12.gray(`Requesting reconciliation for project ${projectId}\u2026`));
6434
+ const accepted = await client.reconcileProject(projectId);
6435
+ if (!accepted) {
6436
+ throw new CliError(
6437
+ "Runtime rejected the reconcile request.",
6438
+ "RECONCILE_REJECTED"
6439
+ );
6440
+ }
6441
+ console.log(chalk12.green("\u2713 Reconcile requested."));
6442
+ if (!options.watch) {
6443
+ return;
6444
+ }
6445
+ const creds = await loadCredentials();
6446
+ if (!creds?.token) {
6447
+ throw new ConfigError("Not authenticated. Run `io login` first.");
6448
+ }
6449
+ console.log(
6450
+ chalk12.gray("\nTailing activity for this project (Ctrl+C to stop)\u2026\n")
6451
+ );
6452
+ const socketClient = createSocketClient({
6453
+ apiUrl: config.apiUrl,
6454
+ credentials: creds
6455
+ });
6456
+ const initialSince = (/* @__PURE__ */ new Date()).toISOString();
6457
+ try {
6458
+ await watch(
6459
+ {
6460
+ subscribe: (onEvent, onError, onConnected) => {
6461
+ const unsubscribe = socketClient.subscribe(
6462
+ ACTIVITY_EVENT_SUBSCRIPTION,
6463
+ { projectId, input: { limit: POLL_BATCH_LIMIT } },
6464
+ {
6465
+ onStart: onConnected,
6466
+ onResult: (result) => {
6467
+ const event = result.data?.activityEventReceived;
6468
+ if (event) onEvent(event);
6469
+ },
6470
+ onError,
6471
+ onAbort: onError
6472
+ }
6473
+ );
6474
+ return { unsubscribe };
6475
+ },
6476
+ poll: async (since) => {
6477
+ return client.projectEvents(projectId, {
6478
+ since,
6479
+ limit: POLL_BATCH_LIMIT
6480
+ });
6481
+ },
6482
+ pollIntervalMs: POLL_INTERVAL_MS,
6483
+ initialSince,
6484
+ getId: (event) => event.id,
6485
+ getCursor: (event) => event.occurredAt
6486
+ },
6487
+ {
6488
+ onEvent: (event) => console.log(formatEvent2(event)),
6489
+ onConnected: (mode) => {
6490
+ if (mode === "subscription") {
6491
+ console.log(chalk12.green(" \u25CF Connected \u2014 tailing live events\n"));
6492
+ } else {
6493
+ console.log(
6494
+ chalk12.green(
6495
+ " \u25CF Connected \u2014 polling for live events with your authenticated session\n"
6496
+ )
6497
+ );
6498
+ }
6499
+ }
6500
+ }
6501
+ );
6502
+ console.log(chalk12.gray("\nDisconnected."));
6503
+ } finally {
6504
+ socketClient.close();
6505
+ }
6506
+ } catch (err) {
6507
+ if (err instanceof CliError) {
6508
+ console.error(chalk12.red(err.message));
6509
+ process.exit(1);
6510
+ }
6511
+ throw err;
6512
+ }
6513
+ }
6514
+ function isRecord4(value) {
6515
+ return !!value && typeof value === "object" && !Array.isArray(value);
6516
+ }
6517
+ function isCatalogShape2(value) {
6518
+ if (!isRecord4(value)) return false;
6519
+ return value.contract_version === "io.invocation.v2" && value.snapshot_version === "io_runtime.snapshot.v2" && typeof value.catalog_hash === "string" && isRecord4(value.resources) && isRecord4(value.signals) && isRecord4(value.tools) && isRecord4(value.schemas);
6520
+ }
6521
+ async function readLocalCatalog(buildDir) {
6522
+ const path = resolve(process.cwd(), buildDir, "catalog.json");
6523
+ let raw;
6524
+ try {
6525
+ raw = await readFile(path, "utf-8");
6526
+ } catch (err) {
6527
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
6528
+ throw new CliError(
6529
+ `Local catalog not found at ${path}. Run \`io build\` or \`io deploy\` first.`,
6530
+ "CATALOG_NOT_FOUND"
6531
+ );
6532
+ }
6533
+ throw err;
6534
+ }
6535
+ let parsed;
6536
+ try {
6537
+ parsed = JSON.parse(raw);
6538
+ } catch (err) {
6539
+ throw new CliError(
6540
+ `Local catalog at ${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
6541
+ "CATALOG_INVALID_JSON"
6542
+ );
6543
+ }
6544
+ if (!isCatalogShape2(parsed)) {
6545
+ throw new CliError(
6546
+ `Local catalog at ${path} does not match the io.invocation.v2 catalog shape.`,
6547
+ "CATALOG_SHAPE_MISMATCH"
6548
+ );
6549
+ }
6550
+ return parsed;
6551
+ }
6552
+ function describeExecutor(executor) {
6553
+ switch (executor.kind) {
6554
+ case "platform_tool":
6555
+ return `${executor.provider_kind}.${executor.backend_kind}.${executor.operation}`;
6556
+ case "stream_publish":
6557
+ return `${executor.provider_kind}.${executor.backend_kind} \u2192 ${executor.destination_ref.slice(0, 19)}\u2026`;
6558
+ case "webhook_response":
6559
+ return "webhook response";
6560
+ case "http_request":
6561
+ return `${executor.method} ${executor.url}`;
6562
+ case "graphql_request":
6563
+ return `POST ${executor.endpoint}${executor.operation_name ? ` (${executor.operation_name})` : ""}`;
6564
+ case "http_operation":
6565
+ return `${executor.method} ${executor.base_url}${executor.path}`;
6566
+ case "graphql_operation":
6567
+ return `POST ${executor.endpoint}${executor.operation_name ? ` (${executor.operation_name})` : ""}`;
6568
+ case "unsupported":
6569
+ return chalk12.red(`unsupported (${executor.reason})`);
6570
+ }
6571
+ }
6572
+ function formatCatalog(catalog2, label) {
6573
+ const lines = [];
6574
+ lines.push(chalk12.bold(`
6575
+ Catalog (${label})`));
6576
+ lines.push(
6577
+ ` ${chalk12.gray("contract_version:")} ${catalog2.contract_version}`
6578
+ );
6579
+ lines.push(
6580
+ ` ${chalk12.gray("snapshot_version:")} ${catalog2.snapshot_version}`
6581
+ );
6582
+ lines.push(` ${chalk12.gray("catalog_hash: ")} ${catalog2.catalog_hash}`);
6583
+ lines.push("");
6584
+ lines.push(chalk12.bold(" Counts"));
6585
+ lines.push(` resources: ${Object.keys(catalog2.resources).length}`);
6586
+ lines.push(` signals: ${Object.keys(catalog2.signals).length}`);
6587
+ lines.push(` tools: ${Object.keys(catalog2.tools).length}`);
6588
+ lines.push(` schemas: ${Object.keys(catalog2.schemas).length}`);
6589
+ const resourceIds = Object.keys(catalog2.resources).sort();
6590
+ if (resourceIds.length > 0) {
6591
+ lines.push("");
6592
+ lines.push(chalk12.bold(" Resources"));
6593
+ for (const id of resourceIds) {
6594
+ const resource = catalog2.resources[id];
6595
+ lines.push(
6596
+ ` ${chalk12.cyan(resource.id)} ${chalk12.gray(
6597
+ `${resource.resource_kind}/${resource.backend_kind} scope=${resource.binding_scope} cred=${resource.credential_source}`
6598
+ )}`
6599
+ );
6600
+ for (const signalId of resource.signals) {
6601
+ const signal = catalog2.signals[signalId];
6602
+ if (!signal) continue;
6603
+ lines.push(
6604
+ ` ${chalk12.magenta("signal")} ${signal.name} ${chalk12.gray(
6605
+ `(${signal.mode}, scope=${signal.scope})`
6606
+ )}`
6607
+ );
6608
+ }
6609
+ for (const toolId of resource.tools) {
6610
+ const tool = catalog2.tools[toolId];
6611
+ if (!tool) continue;
6612
+ lines.push(
6613
+ ` ${chalk12.yellow("tool ")} ${tool.name} ${chalk12.gray(
6614
+ `(${tool.effect}, audit=${tool.audit_requirement}) ${describeExecutor(tool.executor)}`
6615
+ )}`
6616
+ );
6617
+ }
6618
+ }
6619
+ }
6620
+ lines.push("");
6621
+ return lines.join("\n");
6622
+ }
6623
+ async function catalogShowCommand(options) {
6624
+ try {
6625
+ let catalog2;
6626
+ let label;
6627
+ if (options.remote) {
6628
+ const config = await loadConfig();
6629
+ if (!config.apiUrl || !config.projectId) {
6630
+ throw new CliError(
6631
+ "No apiUrl or projectId configured. Run `io login` and set a project first.",
6632
+ "CONFIG_MISSING"
6633
+ );
6634
+ }
6635
+ const client = createClient(config);
6636
+ const remote = await client.fetchRemoteCatalog(client.projectId());
6637
+ if (!remote) {
6638
+ console.log(
6639
+ chalk12.gray("No active deployment catalog for this project yet.")
6640
+ );
6641
+ return;
6642
+ }
6643
+ catalog2 = remote;
6644
+ label = `remote: project ${client.projectId()}`;
6645
+ } else {
6646
+ const buildDir = options.buildDir ?? ".io";
6647
+ catalog2 = await readLocalCatalog(buildDir);
6648
+ label = `local: ${join(buildDir, "catalog.json")}`;
6649
+ }
6650
+ if (options.json) {
6651
+ process.stdout.write(stableStringify(catalog2, 2));
6652
+ process.stdout.write("\n");
6653
+ return;
5029
6654
  }
6655
+ process.stdout.write(formatCatalog(catalog2, label));
5030
6656
  } catch (err) {
5031
6657
  if (err instanceof CliError) {
5032
- console.error(chalk11.red(err.message));
6658
+ console.error(chalk12.red(err.message));
5033
6659
  process.exit(1);
5034
6660
  }
5035
6661
  throw err;
@@ -5038,24 +6664,33 @@ async function signalEmitCommand(name, options) {
5038
6664
 
5039
6665
  // src/index.ts
5040
6666
  var program = new Command();
5041
- program.name("io").description("IO CLI \u2014 build and deploy behaviors").version("0.1.14");
6667
+ program.name("io").description("IO CLI \u2014 build and deploy behaviors").version("0.1.16");
5042
6668
  program.command("init").description("Initialize io.config.json for the current project").option("--yes", "Overwrite existing config without prompting").action(initCommand);
5043
6669
  program.command("build").description("Build behaviors from the io/ directory").option("--dir <path>", "Source directory", "io").option("--minify", "Minify bundles", false).action(buildCommand);
5044
- program.command("deploy").description("Build and package for deployment").option("--dir <path>", "Source directory", "io").option("--no-minify", "Skip minification").option("--yes", "Skip confirmation prompt").action(deployCommand);
6670
+ program.command("deploy").description("Build and package for deployment").option("--dir <path>", "Source directory", "io").option("--no-minify", "Skip minification").option("--yes", "Skip confirmation prompt").option("--watch", "Tail deployment status until it reaches a terminal state").action(deployCommand);
5045
6671
  program.command("login").description("Authenticate with the IO platform").option("--token <value>", "API token for CI/CD environments").action(loginCommand);
5046
6672
  program.command("logout").description("Remove stored credentials").action(logoutCommand);
5047
6673
  program.command("whoami").description("Show current authentication status").action(whoamiCommand);
5048
6674
  var projects = program.command("projects").description("Manage projects");
5049
6675
  projects.command("list").description("List all projects").action(projectsListCommand);
5050
6676
  projects.command("create <name>").description("Create a new project").action(projectsCreateCommand);
6677
+ projects.command("delete <id>").description("Delete a project (requires typing the project name)").option("--yes", "Skip the name confirmation prompt").option("--organization-id <id>", "Organization that owns the project").action(projectsDeleteCommand);
5051
6678
  var secrets = program.command("secrets").description("Manage project secrets");
5052
6679
  secrets.command("set <name> <value>").description("Set a secret value").option("--target <targets...>", "Restrict to specific targets").action(secretsSetCommand);
5053
6680
  secrets.command("list").description("List all secrets").action(secretsListCommand);
5054
6681
  secrets.command("remove <name>").description("Remove a secret").action(secretsRemoveCommand);
6682
+ var principals = program.command("principals").description("Manage project principals");
6683
+ principals.command("list").description("List principals in the active project").option("--project-id <id>", "Override the configured project ID").action(principalsListCommand);
6684
+ principals.command("create <external-id>").description("Create a principal in the active project").option("--project-id <id>", "Override the configured project ID").option("--state <state>", "Principal state: active, inactive, or archived").option("--metadata <json>", "Principal metadata as a JSON object").action(principalsCreateCommand);
6685
+ 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);
6686
+ principals.command("delete <id>").description("Delete a principal").option("--project-id <id>", "Override the configured project ID").action(principalsDeleteCommand);
5055
6687
  var signals = program.command("signals").description("Emit runtime signals");
5056
- 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").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);
6688
+ signals.command("emit <name>").description("Emit a signal into the active project's event stream").option("--payload <json>", "Signal payload as a JSON object", "{}").option("--metadata <json>", "Signal metadata as a JSON object", "{}").option("--project-id <id>", "Override the configured project ID").requiredOption("--principal-id <id>", "Project principal ID for the emitted signal").option("--correlation-id <id>", "Correlation ID to attach to the signal").option("--causation-id <id>", "Causation ID to attach to the signal").option("--idempotency-key <key>", "Optional idempotency key").option("--source-name <name>", "Optional source name for the emitted signal").option("--json", "Print the raw emitted event as JSON", false).action(signalEmitCommand);
5057
6689
  program.command("quota").description("Show current quota usage for the active organization").action(quotaCommand);
5058
6690
  program.command("logs").description("Show recent activity events").option("--limit <count>", "Number of events to show", "50").option("--type <types...>", "Filter by event types").option("--since <duration>", "Show events since (e.g. 15m, 1h, 7d)").option("--follow", "Tail events in real-time").action(logsCommand);
6691
+ program.command("reconcile").description("Force runtime to reconcile resources for the active project").option("--project-id <id>", "Override the configured project ID").option("--watch", "Tail activity events after the reconcile request").action(reconcileCommand);
6692
+ var catalog = program.command("catalog").description("Inspect the v2 deployment catalog");
6693
+ catalog.command("show").description("Print the local or remote deployment catalog").option("--remote", "Show the active deployment catalog from runtime").option("--json", "Print the raw catalog JSON").option("--build-dir <path>", "Local build directory", ".io").action(catalogShowCommand);
5059
6694
  program.parse();
5060
6695
  //# sourceMappingURL=index.js.map
5061
6696
  //# sourceMappingURL=index.js.map