@cotal-ai/cli 0.151.2 → 0.152.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/command.d.ts.map +1 -1
  2. package/dist/command.js +9 -7
  3. package/dist/command.js.map +1 -1
  4. package/dist/commands/channel-members.d.ts.map +1 -1
  5. package/dist/commands/channel-members.js +4 -12
  6. package/dist/commands/channel-members.js.map +1 -1
  7. package/dist/commands/channels.js +8 -4
  8. package/dist/commands/channels.js.map +1 -1
  9. package/dist/commands/chat.d.ts +4 -3
  10. package/dist/commands/chat.d.ts.map +1 -1
  11. package/dist/commands/chat.js +7 -4
  12. package/dist/commands/chat.js.map +1 -1
  13. package/dist/commands/completion.d.ts +6 -0
  14. package/dist/commands/completion.d.ts.map +1 -1
  15. package/dist/commands/completion.js +27 -9
  16. package/dist/commands/completion.js.map +1 -1
  17. package/dist/commands/history.d.ts.map +1 -1
  18. package/dist/commands/history.js +10 -10
  19. package/dist/commands/history.js.map +1 -1
  20. package/dist/commands/meshes-add.d.ts +1 -21
  21. package/dist/commands/meshes-add.d.ts.map +1 -1
  22. package/dist/commands/meshes-add.js +2 -58
  23. package/dist/commands/meshes-add.js.map +1 -1
  24. package/dist/commands/send.d.ts.map +1 -1
  25. package/dist/commands/send.js +2 -5
  26. package/dist/commands/send.js.map +1 -1
  27. package/dist/commands/service-registry.js +31 -9
  28. package/dist/commands/service-registry.js.map +1 -1
  29. package/dist/commands/spawn-preview.d.ts.map +1 -1
  30. package/dist/commands/spawn-preview.js +15 -8
  31. package/dist/commands/spawn-preview.js.map +1 -1
  32. package/dist/commands/spawn.d.ts.map +1 -1
  33. package/dist/commands/spawn.js +1 -7
  34. package/dist/commands/spawn.js.map +1 -1
  35. package/dist/commands/stack-up.d.ts +1 -0
  36. package/dist/commands/stack-up.d.ts.map +1 -1
  37. package/dist/commands/stack-up.js +8 -2
  38. package/dist/commands/stack-up.js.map +1 -1
  39. package/dist/commands/sync.d.ts.map +1 -1
  40. package/dist/commands/sync.js +9 -3
  41. package/dist/commands/sync.js.map +1 -1
  42. package/dist/commands/up.d.ts.map +1 -1
  43. package/dist/commands/up.js +22 -5
  44. package/dist/commands/up.js.map +1 -1
  45. package/dist/lib/completion-install.d.ts.map +1 -1
  46. package/dist/lib/completion-install.js +35 -23
  47. package/dist/lib/completion-install.js.map +1 -1
  48. package/dist/lib/spawn-defaults.d.ts +3 -1
  49. package/dist/lib/spawn-defaults.d.ts.map +1 -1
  50. package/dist/lib/spawn-defaults.js +7 -1
  51. package/dist/lib/spawn-defaults.js.map +1 -1
  52. package/dist/watch-data.d.ts +8 -1
  53. package/dist/watch-data.d.ts.map +1 -1
  54. package/dist/watch-data.js +17 -8
  55. package/dist/watch-data.js.map +1 -1
  56. package/package.json +4 -4
@@ -6,7 +6,7 @@ import { createConnection, createServer, isIP } from "node:net";
6
6
  import { homedir, hostname } from "node:os";
7
7
  import { accessSync, constants, mkdirSync, readFileSync, existsSync, statSync, closeSync, lstatSync, realpathSync, readdirSync, rmdirSync, } from "node:fs";
8
8
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
9
- import { isReachable, dialerFor, DEFAULT_SERVER, DEFAULT_SPACE, SERVICE_AUTHORITY_MAX_BYTES, SERVICE_CALLER_PAYLOAD_FLOOR, createSpaceAuth, serverConfig, openServerConfig, validateTlsMaterial, credsProblem, mintCreds, mintLifecycleUid, DEV_OWNER, mintConnectionEvictorCreds, mintMembershipObserverCreds, newIdentity, setupSpaceStreams, removeIssuedStoresBeforeRetention, IssuedStoreBeforeRetentionError, IssuedStoreRecreateRefusal, reconcileSpaceTtls, ttlBuckets, standaloneConnectOpts, requireBrokerFloor, parseServerVersion, presenceBucket, spaceDeletionStreams, isJetStreamMissing, seedChannelRegistry, ensureDefaultDeliveryClass, UndecodableRegistryEntry, assertValidChannel, isConcreteChannel, validateChannelConfig, validateChannelDefaults, LEASE_TTL_MS, EpEnvelopeError, mkSecretDir, openPrivateAppend, realDirNoSymlink, writeSecretFile, utf16Problem, } from "@cotal-ai/core";
9
+ import { isReachable, dialerFor, DEFAULT_SERVER, DEFAULT_SPACE, SERVICE_AUTHORITY_MAX_BYTES, SERVICE_CALLER_PAYLOAD_FLOOR, CONTRACT_ARTIFACT_MAX_BYTES, MANAGER_START_PAYLOAD_FLOOR, createSpaceAuth, serverConfig, openServerConfig, validateTlsMaterial, credsProblem, mintCreds, mintLifecycleUid, DEV_OWNER, mintConnectionEvictorCreds, mintMembershipObserverCreds, newIdentity, setupSpaceStreams, removeIssuedStoresBeforeRetention, IssuedStoreBeforeRetentionError, IssuedStoreRecreateRefusal, reconcileSpaceTtls, ttlBuckets, standaloneConnectOpts, requireBrokerFloor, parseServerVersion, presenceBucket, spaceDeletionStreams, isJetStreamMissing, seedChannelRegistry, ensureDefaultDeliveryClass, UndecodableRegistryEntry, assertValidChannel, isConcreteChannel, validateChannelConfig, validateChannelDefaults, LEASE_TTL_MS, EpEnvelopeError, mkSecretDir, openPrivateAppend, realDirNoSymlink, writeSecretFile, utf16Problem, } from "@cotal-ai/core";
10
10
  import { JetStreamApiCodes, jetstreamManager, StorageType } from "@nats-io/jetstream";
11
11
  import { Kvm } from "@nats-io/kv";
12
12
  import { assertManagerName, readManagerName, readManagerSeatCap, recordManagerName, recordManagerSeatCap, parseSeatCapFlag, seatCapText, CommandFailure, codeSpan, commandFailureReport, isCommandFailure, protectedLocalDirectory, jsonFlag, plainFlag, renderReport, assertSingleSpaceBroker, assertUserAuthInfo, authDir, brokerAddressForDisplay, brokerPolicyPath, getSoleSpaceAuth, soleSpaceOf, getSpaceAuth, hasUserAuthState, listSpaceAccounts, preloadSpaceAccounts, putSpaceAuth, brokerAddressKey, canonicalRoot, rootPathProblem, rootControlProblem, clearCurrent, findMesh, findPreparedSpace, homeCotalDir, getCurrent, loadMeshes, preparedSpaceAt, removePreparedSpace, localMeshesForRoot, keepsRegistration, oneLine, presentationOf, writeResult, connectionEvictorCredsKey, membershipConfigPath, membershipObserverCredsKey, membershipRwCredsKey, MEMBERSHIP_CONFIG_KIND, MEMBERSHIP_RW_CREDS_KIND, WEB_CREDS_KIND, webCredsKey, parsePositiveIntegerFlag, recordMesh, retainedLegacyBroker, meshesForRoot, removeMesh, rotateSystemCreds, setCurrent, staleSystemCreds, SYSTEM_CREDS_FILES, userAuthStateDir, userAuthSpacesOnDisk, workspaceSecretStore, acquireMaintenanceLock, assertStoreIdentity, assessMaintenanceClaim, assessRestoreClaim, beginOrdinaryResume, bindOrdinaryResumeListener, consumeRetiredMaintenance, markOrdinaryResumeActive, markOrdinaryResumeDegraded, replaceDeadOrdinaryResumeListener, localProcessOwnerStatus, readMaintenanceJournal, rootNamesNoSpace, readMaintenanceResumeDocument, seatCheckpointDir, readStoreIdentity, recordOrdinaryResumeManagerCommit, releaseMaintenanceLock, retireOrdinaryResume, settleTokenHandoff, sweepTokenHandoff, writeTokenHandoff, sameStoreIdentity, cutController, readBrokerPolicy, writeBrokerPolicy, removePidPair, writePidPair, isLoopbackHost, connectUserControlOrThrow, userViewAuth, recordOperationSpace, recordsElsewhereError, recordOperationWarning, retainSystemSigningSeed, restoreLeftOut, SPACE_NAME_MAX, spaceNameTooLong, NATS_PIDFILE, UNTRUSTED_CERTIFICATE, exitCode, canonicalLocalProcessPath, DELIVERY_LOGFILE, terminalWidth, } from "@cotal-ai/workspace";
@@ -93,7 +93,7 @@ export const upFlags = [
93
93
  { name: "logfile-size-limit", type: "string", value: "<bytes>", description: "nats-server log rotation threshold in bytes; requires --log-file" },
94
94
  { name: "logfile-max-num", type: "string", value: "<count>", description: "maximum total broker log files retained; requires --log-file" },
95
95
  { name: "auth-timeout", type: "string", value: "<seconds>", description: "user-auth only: how long the broker waits for the auth callout, 1 to 60 seconds (default: nats-server's 2); the auth service answers inside it. Fixed at broker start and recorded for the next `up`" },
96
- { name: "max-payload", type: "string", value: "<bytes>", description: "broker-wide message size limit in bytes (default: nats-server's 1048576). Service endpoint caller grants need at least 2098176. Fixed at broker start and recorded for the next `up`" },
96
+ { name: "max-payload", type: "string", value: "<bytes>", description: "broker-wide message size limit in bytes (default: nats-server's 1048576). A manager-backed start needs at least 263168, and service endpoint caller grants need at least 2098176. Fixed at broker start and recorded for the next `up`" },
97
97
  { name: "channels", type: "string", value: "<path>", description: "channel-registry seed file (JSON; default .cotal/channels.json)" },
98
98
  { name: "tls-cert", type: "string", value: "<file>", description: "serve the broker over TLS with this certificate (requires --tls-key)" },
99
99
  { name: "tls-key", type: "string", value: "<file>", description: "the private key for --tls-cert" },
@@ -228,6 +228,11 @@ export function validateUpArguments(args) {
228
228
  // anything starts and a broker that comes up without its auth plane.
229
229
  if (args.values["service-endpoints"] === true && requestedPayload < SERVICE_CALLER_PAYLOAD_FLOOR)
230
230
  throw new CommandFailure("refused", "USAGE_INVALID", `--max-payload ${requestedPayload} is below ${SERVICE_CALLER_PAYLOAD_FLOOR} bytes, the least a space with --service-endpoints starts on: a caller policy row may grow to ${SERVICE_AUTHORITY_MAX_BYTES} bytes and its write needs ${SERVICE_CALLER_PAYLOAD_FLOOR - SERVICE_AUTHORITY_MAX_BYTES} more, so the auth host refuses to open; nothing was started`, { remedy: `pass --max-payload ${SERVICE_CALLER_PAYLOAD_FLOOR} or more, or drop --service-endpoints` });
231
+ // The manager publishes its contract artifacts, one message each, before it is ready. A limit
232
+ // below that publish is recorded and then every later start exits the same way. A broker-only
233
+ // start does not publish them, so it keeps the positive-integer check alone.
234
+ if (args.values["no-manager"] !== true && requestedPayload < MANAGER_START_PAYLOAD_FLOOR)
235
+ throw new CommandFailure("refused", "USAGE_INVALID", `--max-payload ${requestedPayload} is below ${MANAGER_START_PAYLOAD_FLOOR} bytes, the least the space's manager starts on: it publishes its contract artifacts, one message each, and the largest is bounded at ${CONTRACT_ARTIFACT_MAX_BYTES} bytes with ${MANAGER_START_PAYLOAD_FLOOR - CONTRACT_ARTIFACT_MAX_BYTES} more for the publish; nothing was started`, { remedy: `pass --max-payload ${MANAGER_START_PAYLOAD_FLOOR} or more, or pass --no-manager to start the broker without a manager` });
231
236
  }
232
237
  if (typeof args.values["store-dir"] === "string")
233
238
  assertStoreDirArgument(args.values["store-dir"], typeof args.values.root === "string" ? resolve(args.values.root) : freshFolderRoot(args.values) ?? cotalRoot());
@@ -1782,6 +1787,14 @@ async function runUp(args, inheritedLock, onAdopt, freshTeamBoot = false, restor
1782
1787
  console.error(c.red(`✗ mesh "${held.space}" is already running at ${server} - a running broker can't change --max-payload (it is fixed at start); \`cotal down\` it first, then \`cotal up --max-payload ${maxPayload}\``));
1783
1788
  process.exit(1);
1784
1789
  }
1790
+ // A bare refresh adds the manager this broker was started without. The manager publishes its
1791
+ // contract on the limit the broker already serves, so a recorded one below the floor is refused
1792
+ // before that launch rather than dying on the publish.
1793
+ const refreshPayload = maxPayload ?? runningPolicy?.maxPayload;
1794
+ if (!noManager && held.mode !== "open" && refreshPayload !== undefined && refreshPayload < MANAGER_START_PAYLOAD_FLOOR) {
1795
+ console.error(c.red(`✗ --max-payload ${refreshPayload} is below ${MANAGER_START_PAYLOAD_FLOOR} bytes, the least the space's manager starts on: it publishes its contract artifacts, one message each, and the largest is bounded at ${CONTRACT_ARTIFACT_MAX_BYTES} bytes with ${MANAGER_START_PAYLOAD_FLOOR - CONTRACT_ARTIFACT_MAX_BYTES} more for the publish; nothing was started`));
1796
+ process.exit(1);
1797
+ }
1785
1798
  // The store directory is fixed the same way: an explicit --store-dir on a live refresh can
1786
1799
  // only be refused when it disagrees with what the running broker actually opened. Absent
1787
1800
  // `held.storeDir` (a pre-field record) has nothing to compare against, so it is not checked.
@@ -2135,7 +2148,7 @@ async function runUp(args, inheritedLock, onAdopt, freshTeamBoot = false, restor
2135
2148
  // Decided above the branch; the dial host is only settled here (a port collision may have moved
2136
2149
  // the server, and the hostname is what the certificate has to match).
2137
2150
  assertServesDialHost(transport, new URL(server).hostname);
2138
- const setup = useAuth ? await authSetup(storeDir, server, space, host, wantUser ? { idpUrl: values.idp } : undefined, transport, values["rotate-sys"], maxFileStore, false, { authTimeout, maxPayload }) : undefined;
2151
+ const setup = useAuth ? await authSetup(storeDir, server, space, host, wantUser ? { idpUrl: values.idp } : undefined, transport, values["rotate-sys"], maxFileStore, false, { authTimeout, maxPayload, manager: values["no-manager"] !== true }) : undefined;
2139
2152
  const port = Number(new URL(server).port) || 4222;
2140
2153
  // Both modes go through a RENDERER, never bare CLI flags. Open mode used to start from
2141
2154
  // `-js -sd … -p … -a …`, which never called a renderer at all — so the required transport union
@@ -3308,7 +3321,7 @@ export async function recheckFreshTeamBoot(plan, own) {
3308
3321
  export function controllerStackNative() {
3309
3322
  return {
3310
3323
  prepareBroker: async (opts) => {
3311
- const prepared = await prepareBrokerStart({ transport: opts.transport, server: opts.server, space: opts.space, ...(opts.open ? { open: true } : {}), ...(opts.userAuth ? { userAuth: opts.userAuth } : {}), ...(opts.host ? { host: opts.host } : {}), ...(opts.storeDir ? { storeDir: opts.storeDir } : {}), ...(opts.existingStore ? { existingStore: opts.existingStore } : {}), ...(opts.maxFileStore !== undefined ? { maxFileStore: opts.maxFileStore } : {}), ...(opts.logFile !== undefined ? { logFile: opts.logFile } : {}), ...(opts.logfileSizeLimit !== undefined ? { logfileSizeLimit: opts.logfileSizeLimit } : {}), ...(opts.logfileMaxNum !== undefined ? { logfileMaxNum: opts.logfileMaxNum } : {}), ...(opts.authTimeout !== undefined ? { authTimeout: opts.authTimeout } : {}), ...(opts.maxPayload !== undefined ? { maxPayload: opts.maxPayload } : {}), ...(opts.seed !== undefined ? { seed: opts.seed } : opts.channels ? { channels: opts.channels } : {}), quietBoot: true });
3324
+ const prepared = await prepareBrokerStart({ transport: opts.transport, server: opts.server, space: opts.space, ...(opts.open ? { open: true } : {}), ...(opts.userAuth ? { userAuth: opts.userAuth } : {}), ...(opts.host ? { host: opts.host } : {}), ...(opts.storeDir ? { storeDir: opts.storeDir } : {}), ...(opts.existingStore ? { existingStore: opts.existingStore } : {}), ...(opts.maxFileStore !== undefined ? { maxFileStore: opts.maxFileStore } : {}), ...(opts.logFile !== undefined ? { logFile: opts.logFile } : {}), ...(opts.logfileSizeLimit !== undefined ? { logfileSizeLimit: opts.logfileSizeLimit } : {}), ...(opts.logfileMaxNum !== undefined ? { logfileMaxNum: opts.logfileMaxNum } : {}), ...(opts.authTimeout !== undefined ? { authTimeout: opts.authTimeout } : {}), ...(opts.maxPayload !== undefined ? { maxPayload: opts.maxPayload } : {}), ...(opts.noManager ? { noManager: true } : {}), ...(opts.seed !== undefined ? { seed: opts.seed } : opts.channels ? { channels: opts.channels } : {}), quietBoot: true });
3312
3325
  return { root: prepared.root, server: prepared.server, space: prepared.space, storeDir: prepared.storeDir, transport: prepared.transport, ...(prepared.setup ? { setup: { creds: prepared.setup.creds, wsPort: prepared.setup.wsPort } } : {}), ...(prepared.setup?.prepared && prepared.setup.stateDir ? { user: { prepared: prepared.setup.prepared, stateDir: prepared.setup.stateDir } } : {}), confPath: prepared.confPath, bin: prepared.bin, source: prepared.source, version: prepared.version, seedFile: prepared.seedFile, ...(prepared.setup?.authTimeout !== undefined ? { authTimeout: prepared.setup.authTimeout } : {}), ...(prepared.setup?.maxPayload !== undefined ? { maxPayload: prepared.setup.maxPayload } : {}) };
3313
3326
  },
3314
3327
  initializeStreams: (server, space, setup, seedFile, recreateIssuedStores) => postStart(server, space, setup, seedFile, recreateIssuedStores),
@@ -3959,7 +3972,7 @@ export async function prepareBrokerStart(opts) {
3959
3972
  closeSync(fd);
3960
3973
  }
3961
3974
  }
3962
- const setup = useAuth ? await authSetup(storeDir, server, space, host, opts.userAuth, transport, opts.rotateSys, opts.maxFileStore, opts.quietBoot, { authTimeout: opts.authTimeout, maxPayload: opts.maxPayload, logFile, logfileSizeLimit: opts.logfileSizeLimit, logfileMaxNum: opts.logfileMaxNum }) : undefined;
3975
+ const setup = useAuth ? await authSetup(storeDir, server, space, host, opts.userAuth, transport, opts.rotateSys, opts.maxFileStore, opts.quietBoot, { authTimeout: opts.authTimeout, maxPayload: opts.maxPayload, manager: opts.noManager !== true, logFile, logfileSizeLimit: opts.logfileSizeLimit, logfileMaxNum: opts.logfileMaxNum }) : undefined;
3963
3976
  const port = Number(new URL(server).port) || 4222;
3964
3977
  // Same rule as the foreground path: every route to a listener names its transport (see
3965
3978
  // `writeOpenBrokerConf`). Detach must not be the mode where the fence quietly does not apply.
@@ -5388,6 +5401,10 @@ requested = {}) {
5388
5401
  const recorded = readBrokerPolicy(cotalRoot());
5389
5402
  const authTimeout = requested.authTimeout ?? recorded?.authTimeout;
5390
5403
  const maxPayload = requested.maxPayload ?? recorded?.maxPayload;
5404
+ // The same floor an explicit --max-payload already met. A later bare up reads the recorded limit
5405
+ // and would otherwise render it again, then die the same way. A broker-only start does not publish.
5406
+ if (requested.manager === true && maxPayload !== undefined && maxPayload < MANAGER_START_PAYLOAD_FLOOR)
5407
+ throw new CommandFailure("refused", "USAGE_INVALID", `--max-payload ${maxPayload} is below ${MANAGER_START_PAYLOAD_FLOOR} bytes, the least the space's manager starts on: it publishes its contract artifacts, one message each, and the largest is bounded at ${CONTRACT_ARTIFACT_MAX_BYTES} bytes with ${MANAGER_START_PAYLOAD_FLOOR - CONTRACT_ARTIFACT_MAX_BYTES} more for the publish; nothing was started`, { remedy: `pass --max-payload ${MANAGER_START_PAYLOAD_FLOOR} or more, or pass --no-manager to start the broker without a manager` });
5391
5408
  writeBrokerConfig(confPath, serverConfig(auth, spaces, { transport, port, storeDir: configuredStoreDir(cotalRoot(), storeDir), maxFileStore, authTimeout, maxPayload, logFile: requested.logFile, logfileSizeLimit: requested.logfileSizeLimit, logfileMaxNum: requested.logfileMaxNum, host, wsPort, wsHost: host, extraAccounts }));
5392
5409
  // Ephemeral setup cred: used only to probe reachability, pre-create the space streams/buckets
5393
5410
  // (setupSpaceStreams) and seed the channel registry (seedChannelRegistry) — all within the