@warmhub/cli 0.47.0 → 0.49.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 (2) hide show
  1. package/dist/wh.js +511 -89
  2. package/package.json +1 -1
package/dist/wh.js CHANGED
@@ -19192,16 +19192,28 @@ var PLATFORM_STATUS_PROBE_ARTIFACT_FIELDS = {
19192
19192
  }
19193
19193
  };
19194
19194
  // ../../packages/rules/src/reserved-orgs.ts
19195
+ var RESERVED_RAW_CONTENT_TOP_LEVEL_NAMES = [
19196
+ "_app",
19197
+ "_static",
19198
+ "api",
19199
+ "health",
19200
+ "healthz",
19201
+ "mcp",
19202
+ "readyz",
19203
+ "robots.txt",
19204
+ "sse",
19205
+ "trpc",
19206
+ "version"
19207
+ ];
19195
19208
  var RESERVED_ORG_NAMES = [
19209
+ ...RESERVED_RAW_CONTENT_TOP_LEVEL_NAMES,
19196
19210
  "admin",
19197
- "api",
19198
19211
  "billing",
19199
19212
  "blog",
19200
19213
  "docs",
19201
19214
  "help",
19202
19215
  "login",
19203
19216
  "public",
19204
- "robots.txt",
19205
19217
  "settings",
19206
19218
  "signup",
19207
19219
  "status",
@@ -19515,7 +19527,7 @@ function findSystemComponent(componentId) {
19515
19527
  // ../../packages/sdk-ts/package.json
19516
19528
  var package_default = {
19517
19529
  name: "@warmhub/sdk-ts",
19518
- version: "0.46.0",
19530
+ version: "0.48.0",
19519
19531
  private: false,
19520
19532
  type: "module",
19521
19533
  description: "The TypeScript SDK for WarmHub — create repos, commit and query data, and compound knowledge with your AI agents.",
@@ -19614,12 +19626,16 @@ var package_default = {
19614
19626
 
19615
19627
  // ../../packages/sdk-ts/src/operation-normalize.ts
19616
19628
  function toBackendStreamOperation(operation) {
19629
+ if (operation.expectedVersion !== undefined && operation.operation !== "revise") {
19630
+ throw new Error("expectedVersion is only valid on revise operations — set operation: 'revise'");
19631
+ }
19617
19632
  if (operation.operation === "retract") {
19618
19633
  return {
19619
19634
  operation: "retract",
19620
19635
  name: operation.name,
19621
19636
  ...operation.kind ? { kind: operation.kind } : {},
19622
- ...operation.reason ? { reason: operation.reason } : {}
19637
+ ...operation.reason ? { reason: operation.reason } : {},
19638
+ ...operation.leaseId ? { leaseId: operation.leaseId } : {}
19623
19639
  };
19624
19640
  }
19625
19641
  if (operation.operation === "revise") {
@@ -19642,14 +19658,18 @@ function toBackendStreamOperation(operation) {
19642
19658
  operation: "revise",
19643
19659
  kind: "assertion",
19644
19660
  name,
19645
- data: operation.data
19661
+ data: operation.data,
19662
+ ...operation.expectedVersion !== undefined ? { expectedVersion: operation.expectedVersion } : {},
19663
+ ...operation.leaseId ? { leaseId: operation.leaseId } : {}
19646
19664
  };
19647
19665
  }
19648
19666
  return {
19649
19667
  operation: "revise",
19650
19668
  kind: kind2,
19651
19669
  name,
19652
- data: operation.data
19670
+ data: operation.data,
19671
+ ...operation.expectedVersion !== undefined ? { expectedVersion: operation.expectedVersion } : {},
19672
+ ...operation.leaseId ? { leaseId: operation.leaseId } : {}
19653
19673
  };
19654
19674
  }
19655
19675
  const kind = operation.kind ?? inferKind(operation.name, operation);
@@ -20143,7 +20163,8 @@ class WarmHubError extends Error {
20143
20163
  hint;
20144
20164
  retryAfter;
20145
20165
  backendCode;
20146
- constructor(code, message, status, hint, retryAfter, backendCode) {
20166
+ details;
20167
+ constructor(code, message, status, hint, retryAfter, backendCode, details) {
20147
20168
  super(message);
20148
20169
  this.name = "WarmHubError";
20149
20170
  this.code = code;
@@ -20151,6 +20172,7 @@ class WarmHubError extends Error {
20151
20172
  this.hint = hint;
20152
20173
  this.retryAfter = retryAfter;
20153
20174
  this.backendCode = backendCode;
20175
+ this.details = details;
20154
20176
  }
20155
20177
  get kind() {
20156
20178
  return this.code;
@@ -20175,12 +20197,12 @@ function toWarmHubError(error) {
20175
20197
  const data = error.data;
20176
20198
  const wireCode = data?.warmhub?.code;
20177
20199
  const message = data?.warmhub?.message ?? sanitizeErrorMessage(error.message);
20178
- return new WarmHubError(wireCode ?? "BACKEND", message, data?.warmhub?.status, data?.warmhub?.hint, data?.warmhub?.retryAfter, wireCode);
20200
+ return new WarmHubError(wireCode ?? "BACKEND", message, data?.warmhub?.status, data?.warmhub?.hint, data?.warmhub?.retryAfter, wireCode, data?.warmhub?.details);
20179
20201
  }
20180
20202
  if (error instanceof Error) {
20181
20203
  const warmhubLike = error;
20182
20204
  if (error.name === "WarmHubError" && typeof warmhubLike.code === "string") {
20183
- return new WarmHubError(warmhubLike.code, sanitizeErrorMessage(error.message), typeof warmhubLike.status === "number" ? warmhubLike.status : undefined, typeof warmhubLike.hint === "string" ? warmhubLike.hint : undefined, typeof warmhubLike.retryAfter === "number" ? warmhubLike.retryAfter : undefined, typeof warmhubLike.backendCode === "string" ? warmhubLike.backendCode : undefined);
20205
+ return new WarmHubError(warmhubLike.code, sanitizeErrorMessage(error.message), typeof warmhubLike.status === "number" ? warmhubLike.status : undefined, typeof warmhubLike.hint === "string" ? warmhubLike.hint : undefined, typeof warmhubLike.retryAfter === "number" ? warmhubLike.retryAfter : undefined, typeof warmhubLike.backendCode === "string" ? warmhubLike.backendCode : undefined, warmhubLike.details);
20184
20206
  }
20185
20207
  if (error.name === "AbortError") {
20186
20208
  return new WarmHubError("CANCELLED", error.message);
@@ -21308,6 +21330,30 @@ class WarmHubClient {
21308
21330
  throw toWarmHubError(error);
21309
21331
  }
21310
21332
  },
21333
+ getWithLease: async (orgName, repoName, wref, opts) => {
21334
+ try {
21335
+ return await this.trpc.thing.getWithLease.mutate({
21336
+ orgName,
21337
+ repoName,
21338
+ wref,
21339
+ ttlMs: opts?.ttlMs
21340
+ });
21341
+ } catch (error) {
21342
+ throw toWarmHubError(error);
21343
+ }
21344
+ },
21345
+ releaseLease: async (orgName, repoName, wref, leaseId) => {
21346
+ try {
21347
+ await this.trpc.thing.releaseLease.mutate({
21348
+ orgName,
21349
+ repoName,
21350
+ wref,
21351
+ leaseId
21352
+ });
21353
+ } catch (error) {
21354
+ throw toWarmHubError(error);
21355
+ }
21356
+ },
21311
21357
  graph: async (orgName, repoName, wref, opts) => {
21312
21358
  try {
21313
21359
  return await this.trpc.thing.graph.query({
@@ -22045,6 +22091,10 @@ function classifyConflict(message, code) {
22045
22091
  return "archived";
22046
22092
  case "REPO_PENDING_DELETE":
22047
22093
  return "pending-delete";
22094
+ case "LEASE_UNAVAILABLE":
22095
+ return "lease-held";
22096
+ default:
22097
+ break;
22048
22098
  }
22049
22099
  const lower = message.toLowerCase();
22050
22100
  if (lower.includes("already exists") || lower.includes("already a member")) {
@@ -22074,6 +22124,8 @@ function conflictHint(message, code) {
22074
22124
  return "Other things reference this. Retract those assertions first, or use a tombstone.";
22075
22125
  case "pending-delete":
22076
22126
  return "Repo is being deleted. Wait for the delete to finish or cancel it before retrying.";
22127
+ case "lease-held":
22128
+ return "This thing is leased by another holder. Back off until the lease expires (see leaseExpiresAt / retryAfter), then retry.";
22077
22129
  case "other":
22078
22130
  return "Resolve the conflicting existing state and try again.";
22079
22131
  }
@@ -22097,7 +22149,8 @@ var CONFLICT_SHAPED_CODES = new Set([
22097
22149
  "ARCHIVED",
22098
22150
  "HAS_INBOUND_REFS",
22099
22151
  "REPO_PENDING_DELETE",
22100
- "ALREADY_RETRACTED"
22152
+ "ALREADY_RETRACTED",
22153
+ "LEASE_UNAVAILABLE"
22101
22154
  ]);
22102
22155
  function fromWh(exit, kind, err, hint = err.hint) {
22103
22156
  return new CliError(exit, kind, err.message, err, hint, undefined, err.backendCode);
@@ -22257,6 +22310,11 @@ function generateSuggestions(code, message, context, backendCode) {
22257
22310
  action: "Wait for the repo delete to finish or cancel it before retrying"
22258
22311
  });
22259
22312
  break;
22313
+ case "lease-held":
22314
+ suggestions.push({
22315
+ action: "Another holder leased this thing. Back off until the lease expires (leaseExpiresAt / retryAfter), then retry"
22316
+ });
22317
+ break;
22260
22318
  case "other":
22261
22319
  suggestions.push({
22262
22320
  action: "Resolve the conflicting existing state and retry"
@@ -22468,6 +22526,21 @@ function safeParseJson(input, label, options) {
22468
22526
  throw new CliError(2 /* UserInput */, "USER_INPUT", `Invalid JSON for ${label}: ${input.length > 80 ? `${input.slice(0, 77)}...` : input}`, undefined, hint);
22469
22527
  }
22470
22528
  }
22529
+ function parsePositiveIntFlag(value, label, example) {
22530
+ if (value !== undefined && (!Number.isInteger(value) || value < 1)) {
22531
+ usageError(`${label} must be a positive integer`, example);
22532
+ }
22533
+ return value;
22534
+ }
22535
+ function requireLeaseIdFlag(raw, example = `wh thing revise Player/alice --data '{"score":2}' --lease-id <id>`) {
22536
+ if (raw === undefined)
22537
+ return;
22538
+ const trimmed = raw.trim();
22539
+ if (trimmed === "") {
22540
+ usageError("--lease-id must be a non-empty lease token from `wh thing lease`", example);
22541
+ }
22542
+ return trimmed;
22543
+ }
22471
22544
  function parseArgs(argv) {
22472
22545
  const positionals = [];
22473
22546
  const flags = {};
@@ -23847,6 +23920,17 @@ function parseOrgRepo(ref, config) {
23847
23920
  }
23848
23921
  throw new CliError(3 /* Config */, "CONFIG", "No repo specified. Use --repo org/repo, set WARMHUB_REPO, or run: wh use org/repo");
23849
23922
  }
23923
+ function parseOrgRepoOptional(ref, config) {
23924
+ try {
23925
+ return parseOrgRepo(ref, config);
23926
+ } catch (error) {
23927
+ const suppliedRepoInfo = ref !== undefined && ref !== "" || Boolean(config.defaultRepo);
23928
+ if (suppliedRepoInfo) {
23929
+ throw error;
23930
+ }
23931
+ return {};
23932
+ }
23933
+ }
23850
23934
 
23851
23935
  // ../../packages/warmhub-cli/src/domain-registry.ts
23852
23936
  class DomainRegistry {
@@ -24264,9 +24348,15 @@ function renderWarningLine(out, c, chars, op) {
24264
24348
  const moreSuffix = remaining > 0 ? ` ${c.dim}(+${remaining} more)${c.reset}` : "";
24265
24349
  out(` ${c.yellow}${chars.warn}${c.reset} ${total} ${noun} not declared in shape${shapeLabel}: ${c.dim}${fieldsList}${c.reset}${moreSuffix}`);
24266
24350
  }
24351
+ function renderCommitterEcho(out, c, committer) {
24352
+ if (!committer)
24353
+ return;
24354
+ out(` ${c.dim}committer:${c.reset} ${pinnedWref(c, committer)}`);
24355
+ }
24267
24356
  function renderSingleOpSuccess(out, c, chars, op, opts) {
24268
24357
  const name = op.name ?? opts.fallbackName;
24269
24358
  const suffix = opts.suffix ?? "";
24359
+ renderCommitterEcho(out, c, opts.committer);
24270
24360
  out(`${opts.color}${opts.marker}${c.reset} ${displayName(c, name)}${suffix}`);
24271
24361
  renderWarningLine(out, c, chars, op);
24272
24362
  }
@@ -24345,6 +24435,10 @@ async function readContentInput(filePath, inline, stdinStream) {
24345
24435
  }
24346
24436
 
24347
24437
  // ../../packages/warmhub-cli/src/domains/thing.ts
24438
+ var DURABLE_ID_PATTERN_RE = /^[0-9a-zA-HJ-NP-Tv-z]{60}(@(v\d+|HEAD|ALL))?$/i;
24439
+ function looksLikeDurableId(wref) {
24440
+ return DURABLE_ID_PATTERN_RE.test(wref);
24441
+ }
24348
24442
  function usageError2(usage, example) {
24349
24443
  throw new CliError(2 /* UserInput */, "USER_INPUT", usage, undefined, `Example: ${example}`);
24350
24444
  }
@@ -24441,6 +24535,10 @@ function renderHead(out, c, chars, result, org, repo, shape, kind) {
24441
24535
  const truncated = preview.length > 80 ? `${preview.slice(0, 77)}...` : preview;
24442
24536
  out(` ${c.dim}${truncated}${c.reset}`);
24443
24537
  }
24538
+ const itemMeta = item.metadata;
24539
+ if (itemMeta?.durableId) {
24540
+ out(` ${c.dim}durableId:${c.reset} ${itemMeta.durableId}`);
24541
+ }
24444
24542
  }
24445
24543
  out(`${c.dim}${items.length} item(s)${c.reset}`);
24446
24544
  }
@@ -24460,6 +24558,19 @@ function renderThing(out, c, result) {
24460
24558
  if (aboutWref) {
24461
24559
  out(` ${c.dim}about:${c.reset} ${String(aboutWref)}`);
24462
24560
  }
24561
+ const meta = result.metadata;
24562
+ if (meta?.durableId || meta?.thingCreatedAt || meta?.versionCreatedAt) {
24563
+ const now = Date.now();
24564
+ if (meta.durableId) {
24565
+ out(` ${c.dim}durableId:${c.reset} ${meta.durableId}`);
24566
+ }
24567
+ if (meta.thingCreatedAt) {
24568
+ out(` ${c.dim}thingCreated:${c.reset} ${formatTime(meta.thingCreatedAt, now)}`);
24569
+ }
24570
+ if (meta.versionCreatedAt) {
24571
+ out(` ${c.dim}versionCreated:${c.reset}${formatTime(meta.versionCreatedAt, now)}`);
24572
+ }
24573
+ }
24463
24574
  const fields = shapeName && result.data ? collectionFields(shapeName, result.data) : null;
24464
24575
  if (fields) {
24465
24576
  for (const field of fields) {
@@ -24550,6 +24661,10 @@ function renderHistory(out, c, result) {
24550
24661
  const wref = result.thing.wref ?? result.thing.name ?? "(unknown)";
24551
24662
  out(`${c.bold}History: ${pinnedWref(c, wref)}${c.reset} ${kindLabel(c, result.thing.kind ?? "thing")}`);
24552
24663
  }
24664
+ const firstMeta = result.versions?.[0]?.metadata;
24665
+ if (firstMeta?.durableId) {
24666
+ out(` ${c.dim}durableId:${c.reset} ${firstMeta.durableId}`);
24667
+ }
24553
24668
  const versions = result.versions ?? [];
24554
24669
  const now = Date.now();
24555
24670
  for (const ver of versions) {
@@ -24566,7 +24681,9 @@ function renderHistory(out, c, result) {
24566
24681
  const wref = ver.wref ?? ver.thingName;
24567
24682
  const wrefStr = wref ? pinnedWref(c, wref, ver.version) : "";
24568
24683
  const by = ver.committerWref ? ` ${c.dim}by${c.reset} ${pinnedWref(c, ver.committerWref)}` : "";
24569
- out(` ${wrefStr} ${op} ${c.dim}${time}${c.reset}${by}`);
24684
+ const thingCreatedAt = ver.metadata?.thingCreatedAt;
24685
+ const thingCreatedStr = thingCreatedAt ? ` ${c.dim}born:${formatTime(thingCreatedAt, now)}${c.reset}` : "";
24686
+ out(` ${wrefStr} ${op} ${c.dim}${time}${c.reset}${by}${thingCreatedStr}`);
24570
24687
  }
24571
24688
  }
24572
24689
  function renderRefs(out, c, result, wref, direction) {
@@ -24834,9 +24951,9 @@ async function collectWrefs(opts) {
24834
24951
  ]));
24835
24952
  }
24836
24953
  async function runSingleView(ctx, wref, flags) {
24837
- const { org, repo } = parseOrgRepo(getRepoRef(ctx), ctx.config);
24838
24954
  const version = flags.version;
24839
24955
  const depth = flags.depth;
24956
+ const { org, repo } = looksLikeDurableId(wref) && depth === undefined ? parseOrgRepoOptional(getRepoRef(ctx), ctx.config) : parseOrgRepo(getRepoRef(ctx), ctx.config);
24840
24957
  const includeRetracted = flags["include-retracted"] || version !== undefined;
24841
24958
  if (depth !== undefined && (depth < 1 || depth > 5)) {
24842
24959
  usageError2("Usage: wh thing view <wref> --depth <1-5>", "wh thing view Game/base --depth 2");
@@ -24880,7 +24997,8 @@ async function runSingleView(ctx, wref, flags) {
24880
24997
  writeOutput(ctx, result, () => renderThing(ctx.out, ctx.colors, result));
24881
24998
  }
24882
24999
  async function runBatchView(ctx, wrefs, flags) {
24883
- const { org, repo } = parseOrgRepo(getRepoRef(ctx), ctx.config);
25000
+ const allDurable = wrefs.length > 0 && wrefs.every(looksLikeDurableId);
25001
+ const { org, repo } = allDurable ? parseOrgRepoOptional(getRepoRef(ctx), ctx.config) : parseOrgRepo(getRepoRef(ctx), ctx.config);
24884
25002
  const includeRetracted = flags["include-retracted"] || flags.version !== undefined;
24885
25003
  const result = await ctx.client.thing.getMany(org, repo, wrefs, flags.version, { includeRetracted });
24886
25004
  if (ctx.format === "jsonl") {
@@ -24932,6 +25050,53 @@ var handleView = async (ctx, { flags, args, terminator }) => {
24932
25050
  const singleWref = wrefs[0];
24933
25051
  return runSingleView(ctx, singleWref, flags);
24934
25052
  };
25053
+ var LEASE_MIN_TTL_MS = 1000;
25054
+ var leaseFlags = {
25055
+ ttl: flag.number({
25056
+ description: `Lease duration in ms (default 5000, min ${LEASE_MIN_TTL_MS}; max is operator-configured)`
25057
+ })
25058
+ };
25059
+ function renderLease(out, c, result) {
25060
+ renderThing(out, c, result);
25061
+ out("");
25062
+ out(` ${c.dim}lease id:${c.reset} ${result.lease.id}`);
25063
+ out(` ${c.dim}expires at:${c.reset} ${result.lease.expiresAt}`);
25064
+ }
25065
+ var handleLease = async (ctx, { flags, args }) => {
25066
+ const wref = args[0];
25067
+ if (!wref) {
25068
+ usageError2("Usage: wh thing lease <wref> [--ttl <ms>]", "wh thing lease Player/alice --ttl 5000");
25069
+ }
25070
+ const ttl = flags.ttl;
25071
+ if (ttl !== undefined && (!Number.isInteger(ttl) || ttl < LEASE_MIN_TTL_MS)) {
25072
+ usageError2(`--ttl must be an integer >= ${LEASE_MIN_TTL_MS} (ms)`, "wh thing lease Player/alice --ttl 5000");
25073
+ }
25074
+ const { org, repo } = parseOrgRepo(getRepoRef(ctx), ctx.config);
25075
+ const result = await ctx.client.thing.getWithLease(org, repo, wref, {
25076
+ ttlMs: ttl
25077
+ });
25078
+ writeOutput(ctx, result, () => renderLease(ctx.out, ctx.colors, result));
25079
+ };
25080
+ var releaseLeaseFlags = {
25081
+ "lease-id": flag.string({
25082
+ description: "Lease token from `wh thing lease` to release early"
25083
+ })
25084
+ };
25085
+ var handleReleaseLease = async (ctx, { flags, args }) => {
25086
+ const wref = args[0];
25087
+ if (!wref) {
25088
+ usageError2("Usage: wh thing release-lease <wref> --lease-id <id>", "wh thing release-lease Player/alice --lease-id <id>");
25089
+ }
25090
+ const leaseId = requireLeaseIdFlag(flags["lease-id"], "wh thing release-lease Player/alice --lease-id <id>");
25091
+ if (leaseId === undefined) {
25092
+ usageError2("--lease-id is required for release-lease", "wh thing release-lease Player/alice --lease-id <id>");
25093
+ }
25094
+ const { org, repo } = parseOrgRepo(getRepoRef(ctx), ctx.config);
25095
+ await ctx.client.thing.releaseLease(org, repo, wref, leaseId);
25096
+ writeOutput(ctx, { released: true, wref, leaseId }, () => {
25097
+ ctx.out(`Released lease ${leaseId} on ${wref}`);
25098
+ });
25099
+ };
24935
25100
  var graphFlags = {
24936
25101
  depth: flag.number({
24937
25102
  description: "Resolve embedded graph to this depth (default: 2, max: 5)"
@@ -24971,7 +25136,7 @@ var historyFlags = {
24971
25136
  };
24972
25137
  var handleHistory = async (ctx, { flags, args }) => {
24973
25138
  const wref = args[0];
24974
- const { org, repo } = parseOrgRepo(getRepoRef(ctx), ctx.config);
25139
+ const { org, repo } = wref !== undefined && looksLikeDurableId(wref) ? parseOrgRepoOptional(getRepoRef(ctx), ctx.config) : parseOrgRepo(getRepoRef(ctx), ctx.config);
24975
25140
  const shape = flags.shape;
24976
25141
  const about = flags.about;
24977
25142
  const limit = flags.limit;
@@ -25070,7 +25235,7 @@ var handleResolve = async (ctx, { args }) => {
25070
25235
  if (!wref) {
25071
25236
  usageError2("Usage: wh thing resolve <wref>", "wh thing resolve player");
25072
25237
  }
25073
- const { org, repo } = parseOrgRepo(getRepoRef(ctx), ctx.config);
25238
+ const { org, repo } = looksLikeDurableId(wref) ? parseOrgRepoOptional(getRepoRef(ctx), ctx.config) : parseOrgRepo(getRepoRef(ctx), ctx.config);
25074
25239
  const c = ctx.colors;
25075
25240
  const result = await ctx.client.thing.resolve(org, repo, wref);
25076
25241
  writeOutput(ctx, result, () => {
@@ -25086,6 +25251,12 @@ var reviseFlags = {
25086
25251
  message: flag.string({ short: "m", description: "Commit message" }),
25087
25252
  committer: flag.string({
25088
25253
  description: "Committer thing wref (e.g. Agent/bot-1)"
25254
+ }),
25255
+ "expected-version": flag.number({
25256
+ description: "Only apply if the target is still at this version (optimistic concurrency)"
25257
+ }),
25258
+ "lease-id": flag.string({
25259
+ description: "Read-lease token from `wh thing lease` (auto-released on success)"
25089
25260
  })
25090
25261
  };
25091
25262
  var createFlags = {
@@ -25126,7 +25297,8 @@ var handleCreate = async (ctx, { flags, args }) => {
25126
25297
  writeOutput(ctx, result, () => renderSingleOpSuccess(ctx.out, c, ctx.chars, result.operations[0] ?? {}, {
25127
25298
  marker: "+",
25128
25299
  color: c.green,
25129
- fallbackName: name
25300
+ fallbackName: name,
25301
+ committer
25130
25302
  }));
25131
25303
  };
25132
25304
  var handleRevise = async (ctx, { flags, args }) => {
@@ -25137,8 +25309,10 @@ var handleRevise = async (ctx, { flags, args }) => {
25137
25309
  const { org, repo } = parseOrgRepo(getRepoRef(ctx), ctx.config);
25138
25310
  const committer = flags.committer;
25139
25311
  const message = flags.message;
25312
+ const leaseId = requireLeaseIdFlag(flags["lease-id"]);
25140
25313
  const data = flags.data ? safeParseJson(flags.data, "--data") : undefined;
25141
25314
  const c = ctx.colors;
25315
+ const expectedVersion = parsePositiveIntFlag(flags["expected-version"], "--expected-version", "wh thing revise Player/alice --data '{...}' --expected-version 3");
25142
25316
  if (!data) {
25143
25317
  usageError2("--data is required for revise", `wh thing revise Location/player --data '{"x":1}'`);
25144
25318
  }
@@ -25147,13 +25321,16 @@ var handleRevise = async (ctx, { flags, args }) => {
25147
25321
  operation: "revise",
25148
25322
  kind: "thing",
25149
25323
  name,
25150
- data
25324
+ data,
25325
+ ...expectedVersion !== undefined ? { expectedVersion } : {},
25326
+ ...leaseId ? { leaseId } : {}
25151
25327
  }
25152
25328
  ], { committer });
25153
25329
  assertSingleOpSuccess(result);
25154
25330
  writeOutput(ctx, result, () => renderSingleOpSuccess(ctx.out, c, ctx.chars, result.operations[0] ?? {}, {
25155
25331
  marker: "~",
25156
- color: c.yellow
25332
+ color: c.yellow,
25333
+ committer
25157
25334
  }));
25158
25335
  };
25159
25336
  var retractFlags = {
@@ -25164,6 +25341,9 @@ var retractFlags = {
25164
25341
  message: flag.string({ short: "m", description: "Commit message" }),
25165
25342
  committer: flag.string({
25166
25343
  description: "Committer thing wref (e.g. Agent/bot-1)"
25344
+ }),
25345
+ "lease-id": flag.string({
25346
+ description: "Read-lease token from `wh thing lease` (auto-released on success)"
25167
25347
  })
25168
25348
  };
25169
25349
  var handleThingRetract = async (ctx, { flags, args }) => {
@@ -25176,18 +25356,21 @@ var handleThingRetract = async (ctx, { flags, args }) => {
25176
25356
  const message = flags.message;
25177
25357
  const kind = flags.kind;
25178
25358
  const reason = flags.reason;
25359
+ const leaseId = requireLeaseIdFlag(flags["lease-id"], "wh thing retract Player/alice --lease-id <id>");
25179
25360
  const c = ctx.colors;
25180
25361
  const result = await ctx.client.commit.apply(org, repo, message ?? `retract ${name}`, [
25181
25362
  {
25182
25363
  operation: "retract",
25183
25364
  name,
25184
25365
  ...kind ? { kind } : {},
25185
- ...reason ? { reason } : {}
25366
+ ...reason ? { reason } : {},
25367
+ ...leaseId ? { leaseId } : {}
25186
25368
  }
25187
25369
  ], { committer });
25188
25370
  assertSingleOpSuccess(result);
25189
25371
  writeOutput(ctx, result, () => {
25190
25372
  const op = result.operations[0];
25373
+ renderCommitterEcho(ctx.out, c, committer);
25191
25374
  ctx.out(`${c.red}-${c.reset} ${displayName(c, op?.name ?? name)}`);
25192
25375
  });
25193
25376
  };
@@ -25530,7 +25713,7 @@ var handleAbout = async (ctx, { flags, args }) => {
25530
25713
  if (!wref) {
25531
25714
  usageError2("Usage: wh thing about <wref> [--shape] [--match] [--limit] [--include-retracted]", "wh thing about Location/cave");
25532
25715
  }
25533
- const { org, repo } = parseOrgRepo(getRepoRef(ctx), ctx.config);
25716
+ const { org, repo } = looksLikeDurableId(wref) ? parseOrgRepoOptional(getRepoRef(ctx), ctx.config) : parseOrgRepo(getRepoRef(ctx), ctx.config);
25534
25717
  const includeRetracted = flags["include-retracted"];
25535
25718
  const limit = flags.limit;
25536
25719
  const cursor = flags.cursor;
@@ -25650,7 +25833,7 @@ var handleRefs = async (ctx, { flags, args }) => {
25650
25833
  if (!wref) {
25651
25834
  usageError2("Usage: wh thing refs <wref> [--inbound|--outbound] [--field FIELD] [--limit N]", "wh thing refs Loc/player");
25652
25835
  }
25653
- const { org, repo } = parseOrgRepo(getRepoRef(ctx), ctx.config);
25836
+ const { org, repo } = looksLikeDurableId(wref) ? parseOrgRepoOptional(getRepoRef(ctx), ctx.config) : parseOrgRepo(getRepoRef(ctx), ctx.config);
25654
25837
  if (flags.inbound && flags.outbound) {
25655
25838
  usageError2("Cannot specify both --inbound and --outbound", "wh thing refs Loc/player --outbound");
25656
25839
  }
@@ -25731,6 +25914,25 @@ var THING_DOMAIN = defineDomain({
25731
25914
  ],
25732
25915
  handler: handleView
25733
25916
  },
25917
+ lease: {
25918
+ prime: true,
25919
+ summary: "Acquire a short, exclusive read lease on a thing (read + lease in one call)",
25920
+ args: "<wref>",
25921
+ flags: leaseFlags,
25922
+ examples: [
25923
+ "wh thing lease Player/alice",
25924
+ "wh thing lease Player/alice --ttl 5000"
25925
+ ],
25926
+ handler: handleLease
25927
+ },
25928
+ "release-lease": {
25929
+ prime: true,
25930
+ summary: "Release a read lease early (idempotent)",
25931
+ args: "<wref>",
25932
+ flags: releaseLeaseFlags,
25933
+ examples: ["wh thing release-lease Player/alice --lease-id <id>"],
25934
+ handler: handleReleaseLease
25935
+ },
25734
25936
  history: {
25735
25937
  prime: true,
25736
25938
  summary: "Show version history",
@@ -26044,7 +26246,8 @@ var handleRevise2 = async (ctx, { flags, args }) => {
26044
26246
  assertSingleOpSuccess(result);
26045
26247
  writeOutput(ctx, result, () => renderSingleOpSuccess(ctx.out, c, ctx.chars, result.operations[0] ?? {}, {
26046
26248
  marker: "~",
26047
- color: c.yellow
26249
+ color: c.yellow,
26250
+ committer: flags.committer
26048
26251
  }));
26049
26252
  };
26050
26253
  var retractFlags2 = {
@@ -26073,6 +26276,7 @@ var handleRetract = async (ctx, { flags, args }) => {
26073
26276
  assertSingleOpSuccess(result);
26074
26277
  writeOutput(ctx, result, () => {
26075
26278
  const op = result.operations[0];
26279
+ renderCommitterEcho(ctx.out, ctx.colors, flags.committer);
26076
26280
  ctx.out(`${ctx.colors.red}-${ctx.colors.reset} ${displayName(ctx.colors, op?.name ?? name)}`);
26077
26281
  });
26078
26282
  };
@@ -26169,7 +26373,8 @@ var handleCreate2 = async (ctx, { flags, args }) => {
26169
26373
  writeOutput(ctx, result, () => renderSingleOpSuccess(ctx.out, c, ctx.chars, result.operations[0] ?? {}, {
26170
26374
  marker: "+",
26171
26375
  color: c.green,
26172
- suffix: ` ${c.dim}(${shape})${c.reset}`
26376
+ suffix: ` ${c.dim}(${shape})${c.reset}`,
26377
+ committer
26173
26378
  }));
26174
26379
  };
26175
26380
  var listFlags = {
@@ -27601,6 +27806,59 @@ import { createReadStream as createReadStream2 } from "node:fs";
27601
27806
  import { readFile, writeFile } from "node:fs/promises";
27602
27807
  import { createInterface as createInterface2 } from "node:readline";
27603
27808
 
27809
+ // ../../packages/warmhub-cli/src/commit-payload-validate.ts
27810
+ var NUL = String.fromCharCode(0);
27811
+ function findInvalidControlByte(data, rootLabel = "data") {
27812
+ const segs = [];
27813
+ const pos = visit(data, segs);
27814
+ return pos === -1 ? null : { path: buildPath(rootLabel, segs), position: pos };
27815
+ }
27816
+ function visit(value, segs) {
27817
+ if (typeof value === "string")
27818
+ return value.indexOf(NUL);
27819
+ if (value === null || typeof value !== "object")
27820
+ return -1;
27821
+ if (Array.isArray(value)) {
27822
+ for (let i = 0;i < value.length; i++) {
27823
+ segs.push(i);
27824
+ const p = visit(value[i], segs);
27825
+ if (p !== -1)
27826
+ return p;
27827
+ segs.pop();
27828
+ }
27829
+ return -1;
27830
+ }
27831
+ const keys = Object.keys(value);
27832
+ for (let i = 0;i < keys.length; i++) {
27833
+ const k = keys[i];
27834
+ segs.push(k);
27835
+ const p = visit(value[k], segs);
27836
+ if (p !== -1)
27837
+ return p;
27838
+ segs.pop();
27839
+ }
27840
+ return -1;
27841
+ }
27842
+ function buildPath(root, segs) {
27843
+ let out = root;
27844
+ for (const s of segs)
27845
+ out += typeof s === "number" ? `[${s}]` : `.${s}`;
27846
+ return out;
27847
+ }
27848
+ function formatInvalidControlByteMessage(hit, locator) {
27849
+ return {
27850
+ message: `${locator}: ${hit.path} contains literal U+0000 byte at position ${hit.position}, ` + "which PostgreSQL `text` cannot store (SQLSTATE 22P05).",
27851
+ hint: "Strip NUL bytes before submit, e.g. `.replace(/\\u0000/g, '')`, or pass --allow-nul-bytes to send anyway."
27852
+ };
27853
+ }
27854
+ function assertNoNulBytes(data, locator) {
27855
+ const hit = findInvalidControlByte(data);
27856
+ if (!hit)
27857
+ return;
27858
+ const { message, hint } = formatInvalidControlByteMessage(hit, locator);
27859
+ throw new CliError(2 /* UserInput */, "USER_INPUT", message, undefined, hint);
27860
+ }
27861
+
27604
27862
  // ../../packages/warmhub-cli/src/domains/stream-progress.ts
27605
27863
  import { createReadStream } from "node:fs";
27606
27864
  import { createInterface } from "node:readline";
@@ -27902,7 +28160,9 @@ async function applyJsonlCommit(ctx, args) {
27902
28160
  } catch (cause) {
27903
28161
  const completed = operationOffset + opCount;
27904
28162
  if (opCount > 0 || firstJsonlAppendErrorMayHaveCommitted(cause)) {
27905
- throw new CliError(4 /* Backend */, "BACKEND", `Stream append failed after ${completed} acknowledged operation(s).`, cause, "The failed append may also have landed; inspect repository state before submitting any remaining JSONL operations.");
28163
+ const backendCode = toWarmHubError(cause).backendCode;
28164
+ const suffix = backendCode ? ` (backend: ${backendCode})` : "";
28165
+ throw new CliError(4 /* Backend */, "BACKEND", `Stream append failed after ${completed} acknowledged operation(s).${suffix}`, cause, "The failed append may also have landed; inspect repository state before submitting any remaining JSONL operations.", undefined, backendCode);
27906
28166
  }
27907
28167
  throw cause;
27908
28168
  }
@@ -27960,6 +28220,24 @@ async function applyJsonlCommit(ctx, args) {
27960
28220
  continue;
27961
28221
  assertWithinStreamOpLimit(parsedOpCount + 1);
27962
28222
  const operation = safeParseJson(trimmed, `${args.lineLabel} JSONL line ${lineNumber}`);
28223
+ if (args.allowNulBytes !== true) {
28224
+ try {
28225
+ assertNoNulBytes("data" in operation ? operation.data : undefined, `${args.lineLabel} JSONL line ${lineNumber}`);
28226
+ } catch (cause) {
28227
+ if (cause instanceof CliError) {
28228
+ if (opCount > 0) {
28229
+ const completed = operationOffset + opCount;
28230
+ const baseHint = cause.hint && cause.hint.length > 0 ? `${cause.hint} ` : "";
28231
+ throw new CliError(4 /* Backend */, "BACKEND", `${cause.message} (${completed} earlier operation(s) already acknowledged by the server before this NUL-byte rejection).`, undefined, `${baseHint}The earlier chunks have landed; inspect repository state before submitting any remaining JSONL operations.`);
28232
+ }
28233
+ if (operationOffset > 0) {
28234
+ const baseHint = cause.hint && cause.hint.length > 0 ? `${cause.hint} ` : "";
28235
+ throw new CliError(cause.code, cause.kind, `${cause.message} (${operationOffset} earlier operation(s) already acknowledged by the server in a prior run; this run made no backend writes before the NUL-byte rejection).`, undefined, `${baseHint}Fix the offending input; the prior-run chunks remain landed and unaffected by this local validation failure.`);
28236
+ }
28237
+ }
28238
+ throw cause;
28239
+ }
28240
+ }
27963
28241
  parsedOpCount += 1;
27964
28242
  chunk.push(withSkipExisting(operation, args.skipExisting === true));
27965
28243
  if (chunk.length < chunkSize)
@@ -28177,7 +28455,7 @@ function buildAddOperations(input) {
28177
28455
  });
28178
28456
  }
28179
28457
  function buildRetractOperations(input) {
28180
- const { retractNames, kinds, reasons } = input;
28458
+ const { retractNames, kinds, reasons, leaseId } = input;
28181
28459
  const assertCardinality = (flagName, values) => {
28182
28460
  if (values.length === 0)
28183
28461
  return;
@@ -28203,7 +28481,8 @@ function buildRetractOperations(input) {
28203
28481
  operation: "retract",
28204
28482
  name,
28205
28483
  ...kind ? { kind } : {},
28206
- ...reason ? { reason } : {}
28484
+ ...reason ? { reason } : {},
28485
+ ...leaseId ? { leaseId } : {}
28207
28486
  };
28208
28487
  });
28209
28488
  }
@@ -28260,6 +28539,9 @@ var createFlags3 = {
28260
28539
  "chunk-size": flag.number({
28261
28540
  description: `Streamed ops per append chunk for --stream or .jsonl --file (default: ${DEFAULT_STREAM_APPEND_CHUNK_SIZE}, max: ${MAX_STREAM_APPEND_OPERATION_COUNT})`
28262
28541
  }),
28542
+ "allow-nul-bytes": flag.boolean({
28543
+ description: "Skip the client-side pre-check that rejects literal U+0000 bytes in op data. PostgreSQL `text` columns still cannot store these — use only when the backend storage type is known to tolerate them."
28544
+ }),
28263
28545
  "timing-out": flag.string({
28264
28546
  description: "Write per-append timing sidecar JSON to this path (debug/bench instrumentation)"
28265
28547
  }),
@@ -28310,6 +28592,12 @@ var createFlags3 = {
28310
28592
  }),
28311
28593
  members: flag.string({
28312
28594
  description: "Collection members (comma-separated wrefs)"
28595
+ }),
28596
+ "expected-version": flag.number({
28597
+ description: "Only apply if the --revise target is still at this version (optimistic concurrency). Requires --revise."
28598
+ }),
28599
+ "lease-id": flag.string({
28600
+ description: "Read-lease token from `wh thing lease`, bound to --revise/--retract (auto-released on success)"
28313
28601
  })
28314
28602
  };
28315
28603
  var handleSubmit = async (ctx, { flags, args }) => {
@@ -28322,6 +28610,7 @@ var handleSubmit = async (ctx, { flags, args }) => {
28322
28610
  const progressRequested = flags.progress === true;
28323
28611
  const skipExisting = flags["skip-existing"] === true;
28324
28612
  const chunkSize = flags["chunk-size"];
28613
+ const allowNulBytes = flags["allow-nul-bytes"] === true;
28325
28614
  const timingOut = flags["timing-out"];
28326
28615
  const resumeStreamId = flags["stream-id"];
28327
28616
  const resumeAllocatedTokens = parseAllocatedTokensFlag(flags["allocated-tokens"]);
@@ -28335,6 +28624,14 @@ var handleSubmit = async (ctx, { flags, args }) => {
28335
28624
  const abouts = flags.about ?? [];
28336
28625
  const reasons = flags.reason ?? [];
28337
28626
  const kinds = flags.kind ?? [];
28627
+ const expectedVersion = parsePositiveIntFlag(flags["expected-version"], "--expected-version", "wh commit submit --revise Player/alice --data '{...}' --expected-version 3");
28628
+ if (expectedVersion !== undefined && !reviseName) {
28629
+ usageError4("--expected-version requires --revise", "wh commit submit --revise Player/alice --data '{...}' --expected-version 3");
28630
+ }
28631
+ const leaseIdFlag = requireLeaseIdFlag(flags["lease-id"], `wh commit submit --revise Player/alice --data '{"score":2}' --lease-id <id>`);
28632
+ if (leaseIdFlag !== undefined && reviseName === undefined && retractNames.length === 0) {
28633
+ throw new CliError(2 /* UserInput */, "USER_INPUT", "--lease-id requires --revise or --retract.", undefined, 'For --ops/--file/--stream writes, carry "leaseId" inline on the revise/retract op.');
28634
+ }
28338
28635
  const validKinds = [
28339
28636
  "thing",
28340
28637
  "assertion",
@@ -28426,7 +28723,8 @@ var handleSubmit = async (ctx, { flags, args }) => {
28426
28723
  operations = buildRetractOperations({
28427
28724
  retractNames,
28428
28725
  kinds,
28429
- reasons
28726
+ reasons,
28727
+ leaseId: leaseIdFlag
28430
28728
  });
28431
28729
  } else if (reviseName) {
28432
28730
  if (dataJsons.length > 1) {
@@ -28439,13 +28737,30 @@ var handleSubmit = async (ctx, { flags, args }) => {
28439
28737
  const data = rawData ? safeParseJson(rawData, "--data") : undefined;
28440
28738
  const kindFlag = kinds[0];
28441
28739
  const kind = kindFlag ?? "thing";
28442
- operations = [{ operation: "revise", kind, name: reviseName, data }];
28740
+ operations = [
28741
+ {
28742
+ operation: "revise",
28743
+ kind,
28744
+ name: reviseName,
28745
+ data,
28746
+ ...expectedVersion !== undefined ? { expectedVersion } : {},
28747
+ ...leaseIdFlag ? { leaseId: leaseIdFlag } : {}
28748
+ }
28749
+ ];
28443
28750
  } else {
28444
28751
  usageError4("Usage: wh commit submit --ops '<json>' or --add <name> [--data <json>]", `wh commit submit --add player --shape location --data '{"x":0,"y":0}'`);
28445
28752
  }
28446
28753
  if (!streamInput && operations.length > 0) {
28447
28754
  rejectLegacyLifecycleOperations(operations);
28448
28755
  assertWithinStreamOpLimit(operations.length);
28756
+ if (!allowNulBytes) {
28757
+ const source = opsJson ? "--ops" : opsFile ? opsFile : addNames.length > 0 ? "--add" : reviseName !== undefined ? "--revise" : collectionType ? "--type" : "op";
28758
+ for (let i = 0;i < operations.length; i++) {
28759
+ const op = operations[i];
28760
+ const namePart = op.name ? ` (${op.name})` : "";
28761
+ assertNoNulBytes("data" in op ? op.data : undefined, `${source} op ${i}${namePart}`);
28762
+ }
28763
+ }
28449
28764
  }
28450
28765
  const message = streamInput || jsonlFile ? messageFlag : messageFlag ?? synthesizeCommitMessage(operations);
28451
28766
  let result;
@@ -28460,7 +28775,8 @@ var handleSubmit = async (ctx, { flags, args }) => {
28460
28775
  skipExisting,
28461
28776
  streamId: resumeStreamId,
28462
28777
  allocatedTokens: resumeAllocatedTokens,
28463
- operationOffset: resumeOperationOffset
28778
+ operationOffset: resumeOperationOffset,
28779
+ allowNulBytes
28464
28780
  }) : jsonlFile ? await applyJsonlFileCommit(ctx, {
28465
28781
  org,
28466
28782
  repo,
@@ -28473,7 +28789,8 @@ var handleSubmit = async (ctx, { flags, args }) => {
28473
28789
  skipExisting,
28474
28790
  streamId: resumeStreamId,
28475
28791
  allocatedTokens: resumeAllocatedTokens,
28476
- operationOffset: resumeOperationOffset
28792
+ operationOffset: resumeOperationOffset,
28793
+ allowNulBytes
28477
28794
  }) : await ctx.client.commit.apply(org, repo, message, operations, {
28478
28795
  committer,
28479
28796
  skipExisting
@@ -28490,6 +28807,7 @@ var handleSubmit = async (ctx, { flags, args }) => {
28490
28807
  writeOutput(ctx, result, () => {
28491
28808
  const failureSuffix = failedCount > 0 ? `, ${failedCount} failed` : "";
28492
28809
  ctx.out(`${message ?? "(no message)"} ${c.dim}(${result.operationCount} ops${failureSuffix})${c.reset}`);
28810
+ renderCommitterEcho(ctx.out, c, committer);
28493
28811
  for (const op of result.operations) {
28494
28812
  const failed = isFailedOpStatus(op.status);
28495
28813
  const marker = failed ? "!" : op.operation === "add" ? "+" : op.operation === "revise" ? "~" : "-";
@@ -30099,6 +30417,17 @@ async function reconcileComponent(client, org, repo, pkg, source, existingInstal
30099
30417
  };
30100
30418
  }
30101
30419
  steps.push({ step: "validate", status: "ok" });
30420
+ const partitionedShapes = partitionByProvisioning(manifest.shapes, "Shape");
30421
+ const partitionedCreds = partitionByProvisioning(manifest.credentials, "CredentialSet");
30422
+ const partitionedSubs = partitionByProvisioning(manifest.subscriptions, "Subscription");
30423
+ const setupCreates = [
30424
+ ...partitionedShapes.setupRefs,
30425
+ ...partitionedCreds.setupRefs,
30426
+ ...partitionedSubs.setupRefs
30427
+ ];
30428
+ const reconcileShapes = partitionedShapes.manifestEntries;
30429
+ const reconcileCreds = partitionedCreds.manifestEntries;
30430
+ const reconcileSubs = partitionedSubs.manifestEntries;
30102
30431
  await ensureSharedInfra(client, org, repo);
30103
30432
  steps.push({ step: "ensure-shared-infra", status: "ok" });
30104
30433
  const existingData = existingInstall.data;
@@ -30118,7 +30447,7 @@ async function reconcileComponent(client, org, repo, pkg, source, existingInstal
30118
30447
  const liveShapeNames = new Set;
30119
30448
  const liveShapeDataByName = new Map;
30120
30449
  const liveShapeComponentIdByName = new Map;
30121
- for (const shape of manifest.shapes) {
30450
+ for (const shape of reconcileShapes) {
30122
30451
  if (isFailedInstallState || previousState !== "degraded" && !oldShapeNames.has(shape.name)) {
30123
30452
  continue;
30124
30453
  }
@@ -30139,7 +30468,7 @@ async function reconcileComponent(client, org, repo, pkg, source, existingInstal
30139
30468
  } catch {}
30140
30469
  }
30141
30470
  const liveSubNames = new Set;
30142
- for (const sub of manifest.subscriptions) {
30471
+ for (const sub of reconcileSubs) {
30143
30472
  if (wasIncomplete || !oldSubNames.has(sub.name))
30144
30473
  continue;
30145
30474
  try {
@@ -30148,7 +30477,7 @@ async function reconcileComponent(client, org, repo, pkg, source, existingInstal
30148
30477
  } catch {}
30149
30478
  }
30150
30479
  const liveCredNames = new Set;
30151
- for (const cred of manifest.credentials) {
30480
+ for (const cred of reconcileCreds) {
30152
30481
  if (wasIncomplete || !oldCredNames.has(cred.name))
30153
30482
  continue;
30154
30483
  const credentialSetName = resolveManifestCredentialName(cred.name, {
@@ -30160,7 +30489,7 @@ async function reconcileComponent(client, org, repo, pkg, source, existingInstal
30160
30489
  liveCredNames.add(cred.name);
30161
30490
  } catch {}
30162
30491
  }
30163
- const newShapes = manifest.shapes.filter((s) => !liveShapeNames.has(s.name));
30492
+ const newShapes = reconcileShapes.filter((s) => !liveShapeNames.has(s.name));
30164
30493
  if (newShapes.length > 0) {
30165
30494
  const shapeOps = newShapes.map((shape) => ({
30166
30495
  operation: "add",
@@ -30181,7 +30510,7 @@ async function reconcileComponent(client, org, repo, pkg, source, existingInstal
30181
30510
  }
30182
30511
  }
30183
30512
  }
30184
- const changedShapes = manifest.shapes.filter((shape) => {
30513
+ const changedShapes = reconcileShapes.filter((shape) => {
30185
30514
  if (!liveShapeNames.has(shape.name))
30186
30515
  return false;
30187
30516
  const liveData = liveShapeDataByName.get(shape.name);
@@ -30208,7 +30537,7 @@ async function reconcileComponent(client, org, repo, pkg, source, existingInstal
30208
30537
  });
30209
30538
  }
30210
30539
  }
30211
- const newCreds = manifest.credentials.filter((c) => !oldCredNames.has(c.name) || !liveCredNames.has(c.name));
30540
+ const newCreds = reconcileCreds.filter((c) => !oldCredNames.has(c.name) || !liveCredNames.has(c.name));
30212
30541
  for (const cred of newCreds) {
30213
30542
  const credentialSetName = resolveManifestCredentialName(cred.name, {
30214
30543
  orgName: org,
@@ -30237,7 +30566,7 @@ async function reconcileComponent(client, org, repo, pkg, source, existingInstal
30237
30566
  }
30238
30567
  }
30239
30568
  }
30240
- const newSubs = manifest.subscriptions.filter((s) => !oldSubNames.has(s.name) || !liveSubNames.has(s.name));
30569
+ const newSubs = reconcileSubs.filter((s) => !oldSubNames.has(s.name) || !liveSubNames.has(s.name));
30241
30570
  for (const sub of newSubs) {
30242
30571
  let compiled;
30243
30572
  try {
@@ -30288,7 +30617,7 @@ async function reconcileComponent(client, org, repo, pkg, source, existingInstal
30288
30617
  } catch {}
30289
30618
  }
30290
30619
  }
30291
- const existingSubs = manifest.subscriptions.filter((s) => oldSubNames.has(s.name) && liveSubNames.has(s.name));
30620
+ const existingSubs = reconcileSubs.filter((s) => oldSubNames.has(s.name) && liveSubNames.has(s.name));
30292
30621
  for (const sub of existingSubs) {
30293
30622
  const stepName = `update-sub-${sub.name}`;
30294
30623
  let compiled;
@@ -30418,7 +30747,7 @@ async function reconcileComponent(client, org, repo, pkg, source, existingInstal
30418
30747
  state,
30419
30748
  steps,
30420
30749
  errors,
30421
- setupCreates: []
30750
+ setupCreates
30422
30751
  };
30423
30752
  }
30424
30753
 
@@ -31041,12 +31370,28 @@ var updateFlags = {
31041
31370
  })
31042
31371
  };
31043
31372
  var validateFlags = {};
31044
- var listFlags2 = {};
31373
+ var listFlags2 = {
31374
+ limit: flag.number({
31375
+ description: "Maximum components per page (default: 50, max: 500)"
31376
+ }),
31377
+ cursor: flag.string({ description: "Opaque pagination cursor" }),
31378
+ all: flag.boolean({
31379
+ description: "Fetch all pages (auto-paginate until exhausted)"
31380
+ })
31381
+ };
31045
31382
  var viewFlags3 = {};
31046
31383
  var teardownFlags = {};
31047
31384
  var doctorFlags = {};
31048
31385
  var initFlags = {};
31049
- var searchFlags2 = {};
31386
+ var searchFlags2 = {
31387
+ limit: flag.number({
31388
+ description: "Maximum components per page (default: 50, max: 500)"
31389
+ }),
31390
+ cursor: flag.string({ description: "Opaque pagination cursor" }),
31391
+ all: flag.boolean({
31392
+ description: "Fetch all pages before filtering (auto-paginate until exhausted)"
31393
+ })
31394
+ };
31050
31395
  var registerFlags = {
31051
31396
  org: flag.string({ description: "Owner org for the registered component" }),
31052
31397
  "source-url": flag.string({ description: "Source GitHub URL for installs" }),
@@ -31426,12 +31771,33 @@ function normalizeGithubRemote(remote) {
31426
31771
  }
31427
31772
  return;
31428
31773
  }
31429
- var handleList2 = async (ctx) => {
31774
+ async function fetchComponentPages(client, org, repo, opts) {
31775
+ const firstPage = await client.component.list(org, repo, {
31776
+ limit: opts.limit,
31777
+ cursor: opts.cursor
31778
+ });
31779
+ const items = [...firstPage.items ?? []];
31780
+ let nextCursor = firstPage.nextCursor;
31781
+ if (opts.all) {
31782
+ while (nextCursor) {
31783
+ const page = await client.component.list(org, repo, {
31784
+ limit: opts.limit,
31785
+ cursor: nextCursor
31786
+ });
31787
+ items.push(...page.items ?? []);
31788
+ nextCursor = page.nextCursor;
31789
+ }
31790
+ }
31791
+ return { items, nextCursor };
31792
+ }
31793
+ var handleList2 = async (ctx, { flags }) => {
31430
31794
  const { org, repo } = parseOrgRepo(getRepoRef(ctx), ctx.config);
31431
- const result = await ctx.client.component.list(org, repo);
31432
- const items = result.items ?? [];
31795
+ const c = ctx.colors;
31796
+ const { items, nextCursor } = await fetchComponentPages(ctx.client, org, repo, { all: flags.all, limit: flags.limit, cursor: flags.cursor });
31797
+ if (!flags.all && nextCursor) {
31798
+ ctx.status(`${c.yellow}${items.length} shown, more available${c.reset} — use ${c.cyan}--all${c.reset} to fetch every page, ` + `or re-run the same command plus ${c.cyan}--cursor=${nextCursor}${c.reset} to resume from here.`);
31799
+ }
31433
31800
  writeOutput(ctx, items, () => {
31434
- const c = ctx.colors;
31435
31801
  if (!items.length) {
31436
31802
  ctx.status(`${c.dim}No components installed${c.reset}`);
31437
31803
  return;
@@ -31447,17 +31813,20 @@ var handleList2 = async (ctx) => {
31447
31813
  }
31448
31814
  });
31449
31815
  };
31450
- var handleSearch2 = async (ctx, { args }) => {
31816
+ var handleSearch2 = async (ctx, { flags, args }) => {
31451
31817
  const query = args[0]?.trim();
31452
31818
  if (!query) {
31453
31819
  usageError("Usage: wh component search <query> --repo org/repo", "wh component search research --repo myorg/myrepo");
31454
31820
  }
31455
31821
  const { org, repo } = parseOrgRepo(getRepoRef(ctx), ctx.config);
31456
- const result = await ctx.client.component.list(org, repo);
31822
+ const c = ctx.colors;
31823
+ const { items: allItems, nextCursor } = await fetchComponentPages(ctx.client, org, repo, { all: flags.all, limit: flags.limit, cursor: flags.cursor });
31457
31824
  const needle = query.toLowerCase();
31458
- const items = (result.items ?? []).filter((item) => [item.componentId, item.componentName, item.source].filter((value) => typeof value === "string").some((value) => value.toLowerCase().includes(needle)));
31825
+ const items = allItems.filter((item) => [item.componentId, item.componentName, item.source].filter((value) => typeof value === "string").some((value) => value.toLowerCase().includes(needle)));
31826
+ if (!flags.all && nextCursor) {
31827
+ ctx.status(`${c.yellow}${allItems.length} component(s) searched, more pages available${c.reset} — matches beyond this page were not searched. ` + `Use ${c.cyan}--all${c.reset} to search every page, ` + `or re-run the same command plus ${c.cyan}--cursor=${nextCursor}${c.reset} to continue from here.`);
31828
+ }
31459
31829
  writeOutput(ctx, items, () => {
31460
- const c = ctx.colors;
31461
31830
  if (!items.length) {
31462
31831
  ctx.status(`${c.dim}No components matching "${query}" in ${org}/${repo}${c.reset}`);
31463
31832
  return;
@@ -33587,7 +33956,7 @@ cat wrefs.txt | wh thing view --format jsonl # one JSON line per *deduped
33587
33956
  - \`wh thing history [wref] [--shape] [--about] [--include-retracted]\` — Version history
33588
33957
  - \`wh thing resolve <wref>\` — Resolve wref
33589
33958
  - \`wh thing create <name|Shape/name> --data <json> [--shape] [--message] [--committer]\` — Create
33590
- - \`wh thing revise <name> [--data] [--message] [--committer]\` — Revise
33959
+ - \`wh thing revise <name> [--data] [--message] [--committer] [--expected-version]\` — Revise (CONFLICT if HEAD≠n)
33591
33960
  - \`wh thing retract <wref> -m <message> [--reason] [--kind]\` — Retract
33592
33961
  - \`wh thing query [--shape] [--kind] [--about] [--match]\` — Query by filters
33593
33962
  - \`wh thing search <query> [--shape] [--kind] [--about] [--mode]\` — Search text
@@ -33596,7 +33965,7 @@ cat wrefs.txt | wh thing view --format jsonl # one JSON line per *deduped
33596
33965
  - \`wh thing about <wref> [--shape] [--match] [--depth] [--resolve-collections] [--limit] [--include-retracted]\` — Show assertions about the target identity; \`--resolve-collections\` expands collection members for bare/@HEAD/@ALL inputs, not pinned @vN
33597
33966
 
33598
33967
  ### commit — Write operations
33599
- - \`wh commit submit [--ops|--file|--add|--revise|--retract] [--kind] [--data] [--reason] [--chunk-size] [--skip-existing] [--progress] [--stream-id] [--allocated-tokens] [--operation-offset]\` — Submit operations (bare \`wh commit\` is equivalent). Use \`--file ops.jsonl --chunk-size 5000 --skip-existing\` for bulk ingest.
33968
+ - \`wh commit submit [--ops|--file|--add|--revise|--retract] [--kind] [--data] [--reason] [--expected-version] [--chunk-size] [--skip-existing] [--progress] [--stream-id] [--allocated-tokens] [--operation-offset]\` — Submit operations (bare \`wh commit\` is equivalent). Use \`--file ops.jsonl --chunk-size 5000 --skip-existing\` for bulk ingest.
33600
33969
  - \`wh shape template <shape> [shape2 ...] [--kind] [--operation add|revise|retract] [-o file]\` — Generate sample ops
33601
33970
 
33602
33971
  ### assertion — Assertion operations
@@ -33918,6 +34287,13 @@ var handleCreate5 = async (ctx, { flags, args }) => {
33918
34287
  var repoListFlags = {
33919
34288
  "include-archived": flag.boolean({
33920
34289
  description: "Include archived repositories"
34290
+ }),
34291
+ limit: flag.number({
34292
+ description: "Maximum repos per page (default: 50, max: 200)"
34293
+ }),
34294
+ cursor: flag.string({ description: "Opaque pagination cursor" }),
34295
+ all: flag.boolean({
34296
+ description: "Fetch all pages (auto-paginate until exhausted)"
33921
34297
  })
33922
34298
  };
33923
34299
  var handleList5 = async (ctx, { flags, args }) => {
@@ -33926,16 +34302,36 @@ var handleList5 = async (ctx, { flags, args }) => {
33926
34302
  usageError8("Usage: wh repo list <org> (or set WARMHUB_ORG)", "wh repo list myorg");
33927
34303
  }
33928
34304
  const c = ctx.colors;
33929
- const repos = await ctx.client.repo.list(orgName, {
33930
- includeArchived: flags["include-archived"]
34305
+ const all = flags.all;
34306
+ const includeArchived = flags["include-archived"];
34307
+ const firstPage = await ctx.client.repo.list(orgName, {
34308
+ includeArchived,
34309
+ limit: flags.limit,
34310
+ cursor: flags.cursor
33931
34311
  });
33932
- writeOutput(ctx, repos.items, () => {
33933
- if (!repos.items.length) {
34312
+ const items = [...firstPage.items];
34313
+ let nextCursor = firstPage.nextCursor;
34314
+ if (all) {
34315
+ while (nextCursor) {
34316
+ const page = await ctx.client.repo.list(orgName, {
34317
+ includeArchived,
34318
+ limit: flags.limit,
34319
+ cursor: nextCursor
34320
+ });
34321
+ items.push(...page.items);
34322
+ nextCursor = page.nextCursor;
34323
+ }
34324
+ }
34325
+ if (!all && nextCursor) {
34326
+ ctx.status(`${c.yellow}${items.length} shown, more available${c.reset} — use ${c.cyan}--all${c.reset} to fetch every page, ` + `or re-run the same command plus ${c.cyan}--cursor=${nextCursor}${c.reset} to resume from here.`);
34327
+ }
34328
+ writeOutput(ctx, items, () => {
34329
+ if (!items.length) {
33934
34330
  ctx.status(`${c.dim}No repos in ${orgName}${c.reset}`);
33935
34331
  return;
33936
34332
  }
33937
34333
  ctx.out(`${c.bold}Repos in ${orgName}:${c.reset}`);
33938
- for (const r of repos.items) {
34334
+ for (const r of items) {
33939
34335
  const display = r.displayName && r.displayName !== r.name ? ` ${c.dim}${r.displayName}${c.reset}` : "";
33940
34336
  const desc = r.description ? ` ${c.dim}${r.description}${c.reset}` : "";
33941
34337
  const archived = r.archivedAt ? ` ${c.yellow}[archived]${c.reset}` : "";
@@ -34365,6 +34761,8 @@ var handleContentSet = async (ctx, { args, flags }) => {
34365
34761
  });
34366
34762
  break;
34367
34763
  }
34764
+ default:
34765
+ throw new CliError(2 /* UserInput */, "USER_INPUT", `--kind ${kind} does not support \`set\`.`);
34368
34766
  }
34369
34767
  };
34370
34768
  var handleContentGenerate = async (ctx, { args, flags }) => {
@@ -34394,6 +34792,8 @@ var handleContentGenerate = async (ctx, { args, flags }) => {
34394
34792
  });
34395
34793
  break;
34396
34794
  }
34795
+ default:
34796
+ throw new CliError(2 /* UserInput */, "USER_INPUT", `--kind ${kind} does not support \`generate\`.`);
34397
34797
  }
34398
34798
  };
34399
34799
  var CONTENT_SUBDOMAIN = defineDomain({
@@ -34751,6 +35151,7 @@ var handleRetract2 = async (ctx, { flags, args }) => {
34751
35151
  ], { committer: flags.committer });
34752
35152
  assertSingleOpSuccess(result);
34753
35153
  writeOutput(ctx, result, () => {
35154
+ renderCommitterEcho(ctx.out, c, flags.committer);
34754
35155
  ctx.out(`${c.red}Retracted${c.reset} ${c.magenta}${shapeName}${c.reset}`);
34755
35156
  });
34756
35157
  };
@@ -37180,7 +37581,7 @@ async function runCli(argv, opts) {
37180
37581
  return exitCode;
37181
37582
  }
37182
37583
  if (globalHelpAllRequested) {
37183
- printDomainHelp(out, { allFlags: true, allDomains: true });
37584
+ await printDomainHelp(out, { allFlags: true, allDomains: true });
37184
37585
  exitCode = 0 /* Ok */;
37185
37586
  return exitCode;
37186
37587
  }
@@ -37332,7 +37733,7 @@ function resolveLogLevel(flags, env) {
37332
37733
  // package.json
37333
37734
  var package_default3 = {
37334
37735
  name: "@warmhub/cli",
37335
- version: "0.47.0",
37736
+ version: "0.49.0",
37336
37737
  private: false,
37337
37738
  type: "module",
37338
37739
  description: "The wh CLI for WarmHub — create repos, commit and query data, and compound knowledge with your AI agents.",
@@ -37576,7 +37977,10 @@ async function maybeHandleComponentShellBoundary(argv, deps = {}) {
37576
37977
  pathExists,
37577
37978
  runGit,
37578
37979
  installFromPath: installFromPathImpl,
37579
- materializeComponentArchive: materializeComponentArchiveImpl
37980
+ resolveRegisteredInstall: resolveRegisteredInstallImpl,
37981
+ downloadRegisteredSource: downloadRegisteredSourceImpl,
37982
+ materializeComponentArchive: materializeComponentArchiveImpl,
37983
+ callRegisteredSetup: callRegisteredSetupImpl
37580
37984
  });
37581
37985
  } catch (error) {
37582
37986
  return printCliError(toCliError2(error), writeErr, {
@@ -37649,13 +38053,33 @@ async function handleGithubInstall(args) {
37649
38053
  }
37650
38054
  async function handleRegisteredInstall(args) {
37651
38055
  const parsedRepo = parseOrgRepo(args.repoFlag, args.config);
37652
- const installRepo = `${parsedRepo.org}/${parsedRepo.repo}`;
38056
+ return performRegisteredInstall({
38057
+ componentRef: args.componentRef,
38058
+ refOverride: args.refOverride,
38059
+ verb: "Installed",
38060
+ client: args.client,
38061
+ parsedRepo,
38062
+ format: args.format,
38063
+ writeOut: args.writeOut,
38064
+ writeErr: args.writeErr,
38065
+ installFromPath: args.installFromPath,
38066
+ resolveRegisteredInstall: args.resolveRegisteredInstall,
38067
+ downloadRegisteredSource: args.downloadRegisteredSource,
38068
+ materializeComponentArchive: args.materializeComponentArchive,
38069
+ callRegisteredSetup: args.callRegisteredSetup
38070
+ });
38071
+ }
38072
+ async function performRegisteredInstall(args) {
38073
+ const installRepo = `${args.parsedRepo.org}/${args.parsedRepo.repo}`;
37653
38074
  const resolved = await args.resolveRegisteredInstall({
37654
38075
  componentRef: args.componentRef,
37655
38076
  installRepo,
37656
38077
  client: args.client
37657
38078
  });
37658
- const ref = normalizeGitRef(args.refOverride ?? resolved.sourceDefaultRef ?? undefined);
38079
+ const persistedInstallId = readPersistedString(args.persistedInstallData, "installId");
38080
+ const persistedRef = readPersistedString(args.persistedInstallData, "sourceRef");
38081
+ const installId = persistedInstallId ?? resolved.installId;
38082
+ const ref = normalizeGitRef(args.refOverride ?? persistedRef ?? resolved.sourceDefaultRef ?? undefined);
37659
38083
  const downloaded = await args.downloadRegisteredSource({
37660
38084
  componentRef: args.componentRef,
37661
38085
  installRepo,
@@ -37666,16 +38090,16 @@ async function handleRegisteredInstall(args) {
37666
38090
  try {
37667
38091
  const resolvedRef = ref ?? downloaded.sourceRef;
37668
38092
  invalidateInstallSnapshotCache(installRepo);
37669
- const result = await args.installFromPath(args.client, parsedRepo.org, parsedRepo.repo, extracted.componentDir, {
38093
+ const result = await args.installFromPath(args.client, args.parsedRepo.org, args.parsedRepo.repo, extracted.componentDir, {
37670
38094
  githubUrl: downloaded.sourceUrl,
37671
38095
  ref: resolvedRef,
37672
38096
  resolvedSha: downloaded.resolvedSha ?? "",
37673
- installId: resolved.installId,
38097
+ installId,
37674
38098
  sourceKind: "registered",
37675
38099
  registeredComponentRef: args.componentRef
37676
38100
  });
37677
38101
  if (!result.ok) {
37678
- await writeInstallResult(args.writeOut, args.writeErr, args.format, "Installed", result, installRepo, args.client);
38102
+ await writeInstallResult(args.writeOut, args.writeErr, args.format, args.verb, result, installRepo, args.client);
37679
38103
  return 1 /* Runtime */;
37680
38104
  }
37681
38105
  if (resolved.hasSetup) {
@@ -37684,7 +38108,7 @@ async function handleRegisteredInstall(args) {
37684
38108
  setupResult = await args.callRegisteredSetup({
37685
38109
  componentRef: args.componentRef,
37686
38110
  installRepo,
37687
- installId: resolved.installId,
38111
+ installId,
37688
38112
  ref: resolvedRef,
37689
38113
  resolvedSha: downloaded.resolvedSha,
37690
38114
  client: args.client
@@ -37692,7 +38116,7 @@ async function handleRegisteredInstall(args) {
37692
38116
  } catch (error) {
37693
38117
  if (isMissingManifestPermissionError(error)) {
37694
38118
  const backendCode = warmHubErrorBackendCode(error) ?? warmHubErrorKind(error);
37695
- throw new CliError(2 /* UserInput */, "USER_INPUT", error.message, undefined, "Ask an org owner/admin to grant the missing permissions on the install repo, then re-run install.", undefined, backendCode);
38119
+ throw new CliError(2 /* UserInput */, "USER_INPUT", error.message, undefined, `Ask an org owner/admin to grant the missing permissions on the install repo, then re-run ${args.verb === "Installed" ? "install" : "update"}.`, undefined, backendCode);
37696
38120
  }
37697
38121
  throw error;
37698
38122
  }
@@ -37700,12 +38124,16 @@ async function handleRegisteredInstall(args) {
37700
38124
  throw new CliError(1 /* Runtime */, "BACKEND", `Component setup failed with status ${setupResult.status}${setupResult.body ? `: ${setupResult.body}` : ""}`);
37701
38125
  }
37702
38126
  }
37703
- await writeInstallResult(args.writeOut, args.writeErr, args.format, "Installed", result, installRepo, args.client);
38127
+ await writeInstallResult(args.writeOut, args.writeErr, args.format, args.verb, result, installRepo, args.client);
37704
38128
  return result.ok ? 0 /* Ok */ : 1 /* Runtime */;
37705
38129
  } finally {
37706
38130
  extracted.cleanup();
37707
38131
  }
37708
38132
  }
38133
+ function readPersistedString(data, key) {
38134
+ const value = data?.[key];
38135
+ return typeof value === "string" && value.length > 0 ? value : undefined;
38136
+ }
37709
38137
  function isMissingManifestPermissionError(error) {
37710
38138
  return warmHubErrorKind(error) === "FORBIDDEN" && error instanceof Error && error.message.startsWith("Cannot install: caller is missing repo permissions required by the manifest:");
37711
38139
  }
@@ -37749,28 +38177,22 @@ async function handleGithubUpdate(args) {
37749
38177
  throw new CliError(2 /* UserInput */, "USER_INPUT", `Component '${args.name}' is a bundled system component. Use 'wh component install ${source.componentId} --repo ${installRepo}' to update it.`);
37750
38178
  }
37751
38179
  if (source.kind === "registered") {
37752
- const installRepo2 = `${parsedRepo.org}/${parsedRepo.repo}`;
37753
- const [ownerOrg, componentName] = source.componentRef.split("/");
37754
- const downloaded = await args.client.component.registry.downloadSource(ownerOrg, componentName, {
37755
- installRepo: installRepo2,
37756
- ...source.ref ? { ref: source.ref } : {}
38180
+ return performRegisteredInstall({
38181
+ componentRef: source.componentRef,
38182
+ refOverride: args.refOverride,
38183
+ persistedInstallData: installData,
38184
+ verb: "Updated",
38185
+ client: args.client,
38186
+ parsedRepo,
38187
+ format: args.format,
38188
+ writeOut: args.writeOut,
38189
+ writeErr: args.writeErr,
38190
+ installFromPath: args.installFromPath,
38191
+ resolveRegisteredInstall: args.resolveRegisteredInstall,
38192
+ downloadRegisteredSource: args.downloadRegisteredSource,
38193
+ materializeComponentArchive: args.materializeComponentArchive,
38194
+ callRegisteredSetup: args.callRegisteredSetup
37757
38195
  });
37758
- const extracted = args.materializeComponentArchive(downloaded.archive, downloaded.componentRef, source.ref ?? downloaded.sourceRef);
37759
- try {
37760
- const updateRef2 = source.ref ?? downloaded.sourceRef;
37761
- invalidateInstallSnapshotCache(installRepo2);
37762
- const result2 = await args.installFromPath(args.client, parsedRepo.org, parsedRepo.repo, extracted.componentDir, {
37763
- githubUrl: downloaded.sourceUrl,
37764
- ref: updateRef2,
37765
- resolvedSha: downloaded.resolvedSha ?? "",
37766
- sourceKind: "registered",
37767
- registeredComponentRef: source.componentRef
37768
- });
37769
- await writeInstallResult(args.writeOut, args.writeErr, args.format, "Updated", result2, installRepo2, args.client);
37770
- return result2.ok ? 0 /* Ok */ : 1 /* Runtime */;
37771
- } finally {
37772
- extracted.cleanup();
37773
- }
37774
38196
  }
37775
38197
  const componentDir = args.getComponentPath(`${source.owner}--${source.repo}`);
37776
38198
  args.ensureCacheDir();
@@ -38338,4 +38760,4 @@ if (!updateCheckSuppressedByArgv && shouldRunUpdateCheck(updateEligibility)) {
38338
38760
  var interceptedExitCode = await maybeHandleComponentShellBoundary(dispatchArgv);
38339
38761
  process.exitCode = interceptedExitCode === undefined ? await runCli(dispatchArgv, { version: package_default3.version }) : interceptedExitCode;
38340
38762
 
38341
- //# debugId=7F3BC88908F9842C64756E2164756E21
38763
+ //# debugId=7BEE2CF049FB035564756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warmhub/cli",
3
- "version": "0.47.0",
3
+ "version": "0.49.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "The wh CLI for WarmHub — create repos, commit and query data, and compound knowledge with your AI agents.",