@remixhq/claude-plugin 0.1.11 → 0.1.14

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.
@@ -29,6 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
30
  mod
31
31
  ));
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
33
 
33
34
  // node_modules/isexe/windows.js
34
35
  var require_windows = __commonJS({
@@ -36,8 +37,8 @@ var require_windows = __commonJS({
36
37
  "use strict";
37
38
  module2.exports = isexe;
38
39
  isexe.sync = sync;
39
- var fs8 = require("fs");
40
- function checkPathExt(path12, options) {
40
+ var fs9 = require("fs");
41
+ function checkPathExt(path13, options) {
41
42
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
42
43
  if (!pathext) {
43
44
  return true;
@@ -48,25 +49,25 @@ var require_windows = __commonJS({
48
49
  }
49
50
  for (var i2 = 0; i2 < pathext.length; i2++) {
50
51
  var p = pathext[i2].toLowerCase();
51
- if (p && path12.substr(-p.length).toLowerCase() === p) {
52
+ if (p && path13.substr(-p.length).toLowerCase() === p) {
52
53
  return true;
53
54
  }
54
55
  }
55
56
  return false;
56
57
  }
57
- function checkStat(stat, path12, options) {
58
+ function checkStat(stat, path13, options) {
58
59
  if (!stat.isSymbolicLink() && !stat.isFile()) {
59
60
  return false;
60
61
  }
61
- return checkPathExt(path12, options);
62
+ return checkPathExt(path13, options);
62
63
  }
63
- function isexe(path12, options, cb) {
64
- fs8.stat(path12, function(er, stat) {
65
- cb(er, er ? false : checkStat(stat, path12, options));
64
+ function isexe(path13, options, cb) {
65
+ fs9.stat(path13, function(er, stat) {
66
+ cb(er, er ? false : checkStat(stat, path13, options));
66
67
  });
67
68
  }
68
- function sync(path12, options) {
69
- return checkStat(fs8.statSync(path12), path12, options);
69
+ function sync(path13, options) {
70
+ return checkStat(fs9.statSync(path13), path13, options);
70
71
  }
71
72
  }
72
73
  });
@@ -77,14 +78,14 @@ var require_mode = __commonJS({
77
78
  "use strict";
78
79
  module2.exports = isexe;
79
80
  isexe.sync = sync;
80
- var fs8 = require("fs");
81
- function isexe(path12, options, cb) {
82
- fs8.stat(path12, function(er, stat) {
81
+ var fs9 = require("fs");
82
+ function isexe(path13, options, cb) {
83
+ fs9.stat(path13, function(er, stat) {
83
84
  cb(er, er ? false : checkStat(stat, options));
84
85
  });
85
86
  }
86
- function sync(path12, options) {
87
- return checkStat(fs8.statSync(path12), options);
87
+ function sync(path13, options) {
88
+ return checkStat(fs9.statSync(path13), options);
88
89
  }
89
90
  function checkStat(stat, options) {
90
91
  return stat.isFile() && checkMode(stat, options);
@@ -109,7 +110,7 @@ var require_mode = __commonJS({
109
110
  var require_isexe = __commonJS({
110
111
  "node_modules/isexe/index.js"(exports2, module2) {
111
112
  "use strict";
112
- var fs8 = require("fs");
113
+ var fs9 = require("fs");
113
114
  var core;
114
115
  if (process.platform === "win32" || global.TESTING_WINDOWS) {
115
116
  core = require_windows();
@@ -118,7 +119,7 @@ var require_isexe = __commonJS({
118
119
  }
119
120
  module2.exports = isexe;
120
121
  isexe.sync = sync;
121
- function isexe(path12, options, cb) {
122
+ function isexe(path13, options, cb) {
122
123
  if (typeof options === "function") {
123
124
  cb = options;
124
125
  options = {};
@@ -128,7 +129,7 @@ var require_isexe = __commonJS({
128
129
  throw new TypeError("callback not provided");
129
130
  }
130
131
  return new Promise(function(resolve, reject) {
131
- isexe(path12, options || {}, function(er, is) {
132
+ isexe(path13, options || {}, function(er, is) {
132
133
  if (er) {
133
134
  reject(er);
134
135
  } else {
@@ -137,7 +138,7 @@ var require_isexe = __commonJS({
137
138
  });
138
139
  });
139
140
  }
140
- core(path12, options || {}, function(er, is) {
141
+ core(path13, options || {}, function(er, is) {
141
142
  if (er) {
142
143
  if (er.code === "EACCES" || options && options.ignoreErrors) {
143
144
  er = null;
@@ -147,9 +148,9 @@ var require_isexe = __commonJS({
147
148
  cb(er, is);
148
149
  });
149
150
  }
150
- function sync(path12, options) {
151
+ function sync(path13, options) {
151
152
  try {
152
- return core.sync(path12, options || {});
153
+ return core.sync(path13, options || {});
153
154
  } catch (er) {
154
155
  if (options && options.ignoreErrors || er.code === "EACCES") {
155
156
  return false;
@@ -166,7 +167,7 @@ var require_which = __commonJS({
166
167
  "node_modules/which/which.js"(exports2, module2) {
167
168
  "use strict";
168
169
  var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
169
- var path12 = require("path");
170
+ var path13 = require("path");
170
171
  var COLON = isWindows ? ";" : ":";
171
172
  var isexe = require_isexe();
172
173
  var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
@@ -204,7 +205,7 @@ var require_which = __commonJS({
204
205
  return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
205
206
  const ppRaw = pathEnv[i2];
206
207
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
207
- const pCmd = path12.join(pathPart, cmd);
208
+ const pCmd = path13.join(pathPart, cmd);
208
209
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
209
210
  resolve(subStep(p, i2, 0));
210
211
  });
@@ -231,7 +232,7 @@ var require_which = __commonJS({
231
232
  for (let i2 = 0; i2 < pathEnv.length; i2++) {
232
233
  const ppRaw = pathEnv[i2];
233
234
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
234
- const pCmd = path12.join(pathPart, cmd);
235
+ const pCmd = path13.join(pathPart, cmd);
235
236
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
236
237
  for (let j = 0; j < pathExt.length; j++) {
237
238
  const cur = p + pathExt[j];
@@ -279,7 +280,7 @@ var require_path_key = __commonJS({
279
280
  var require_resolveCommand = __commonJS({
280
281
  "node_modules/cross-spawn/lib/util/resolveCommand.js"(exports2, module2) {
281
282
  "use strict";
282
- var path12 = require("path");
283
+ var path13 = require("path");
283
284
  var which = require_which();
284
285
  var getPathKey = require_path_key();
285
286
  function resolveCommandAttempt(parsed, withoutPathExt) {
@@ -297,7 +298,7 @@ var require_resolveCommand = __commonJS({
297
298
  try {
298
299
  resolved = which.sync(parsed.command, {
299
300
  path: env[getPathKey({ env })],
300
- pathExt: withoutPathExt ? path12.delimiter : void 0
301
+ pathExt: withoutPathExt ? path13.delimiter : void 0
301
302
  });
302
303
  } catch (e) {
303
304
  } finally {
@@ -306,7 +307,7 @@ var require_resolveCommand = __commonJS({
306
307
  }
307
308
  }
308
309
  if (resolved) {
309
- resolved = path12.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
310
+ resolved = path13.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
310
311
  }
311
312
  return resolved;
312
313
  }
@@ -360,8 +361,8 @@ var require_shebang_command = __commonJS({
360
361
  if (!match) {
361
362
  return null;
362
363
  }
363
- const [path12, argument] = match[0].replace(/#! ?/, "").split(" ");
364
- const binary = path12.split("/").pop();
364
+ const [path13, argument] = match[0].replace(/#! ?/, "").split(" ");
365
+ const binary = path13.split("/").pop();
365
366
  if (binary === "env") {
366
367
  return argument;
367
368
  }
@@ -374,16 +375,16 @@ var require_shebang_command = __commonJS({
374
375
  var require_readShebang = __commonJS({
375
376
  "node_modules/cross-spawn/lib/util/readShebang.js"(exports2, module2) {
376
377
  "use strict";
377
- var fs8 = require("fs");
378
+ var fs9 = require("fs");
378
379
  var shebangCommand = require_shebang_command();
379
380
  function readShebang(command) {
380
381
  const size = 150;
381
382
  const buffer = Buffer.alloc(size);
382
383
  let fd;
383
384
  try {
384
- fd = fs8.openSync(command, "r");
385
- fs8.readSync(fd, buffer, 0, size, 0);
386
- fs8.closeSync(fd);
385
+ fd = fs9.openSync(command, "r");
386
+ fs9.readSync(fd, buffer, 0, size, 0);
387
+ fs9.closeSync(fd);
387
388
  } catch (e) {
388
389
  }
389
390
  return shebangCommand(buffer.toString());
@@ -396,7 +397,7 @@ var require_readShebang = __commonJS({
396
397
  var require_parse = __commonJS({
397
398
  "node_modules/cross-spawn/lib/parse.js"(exports2, module2) {
398
399
  "use strict";
399
- var path12 = require("path");
400
+ var path13 = require("path");
400
401
  var resolveCommand = require_resolveCommand();
401
402
  var escape = require_escape();
402
403
  var readShebang = require_readShebang();
@@ -421,7 +422,7 @@ var require_parse = __commonJS({
421
422
  const needsShell = !isExecutableRegExp.test(commandFile);
422
423
  if (parsed.options.forceShell || needsShell) {
423
424
  const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
424
- parsed.command = path12.normalize(parsed.command);
425
+ parsed.command = path13.normalize(parsed.command);
425
426
  parsed.command = escape.command(parsed.command);
426
427
  parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
427
428
  const shellCommand = [parsed.command].concat(parsed.args).join(" ");
@@ -531,6 +532,13 @@ var require_cross_spawn = __commonJS({
531
532
  }
532
533
  });
533
534
 
535
+ // src/hook-stop-collab.ts
536
+ var hook_stop_collab_exports = {};
537
+ __export(hook_stop_collab_exports, {
538
+ runHookStopCollab: () => runHookStopCollab
539
+ });
540
+ module.exports = __toCommonJS(hook_stop_collab_exports);
541
+
534
542
  // node_modules/@remixhq/core/dist/chunk-YZ34ICNN.js
535
543
  var RemixError = class extends Error {
536
544
  code;
@@ -545,7 +553,7 @@ var RemixError = class extends Error {
545
553
  }
546
554
  };
547
555
 
548
- // node_modules/@remixhq/core/dist/chunk-FAZUMWBS.js
556
+ // node_modules/@remixhq/core/dist/chunk-GEHSFPCD.js
549
557
  var import_promises = __toESM(require("fs/promises"), 1);
550
558
  var import_path = __toESM(require("path"), 1);
551
559
  function getCollabBindingPath(repoRoot) {
@@ -582,7 +590,7 @@ var REMIX_ERROR_CODES = {
582
590
  PREFERRED_BRANCH_MISMATCH: "PREFERRED_BRANCH_MISMATCH"
583
591
  };
584
592
 
585
- // node_modules/@remixhq/core/dist/chunk-RREREIGW.js
593
+ // node_modules/@remixhq/core/dist/chunk-J3J4PBQ7.js
586
594
  var import_promises13 = __toESM(require("fs/promises"), 1);
587
595
  var import_crypto = require("crypto");
588
596
  var import_os = __toESM(require("os"), 1);
@@ -4969,13 +4977,13 @@ var logOutputSync = ({ serializedResult, fdNumber, state, verboseInfo, encoding,
4969
4977
  }
4970
4978
  };
4971
4979
  var writeToFiles = (serializedResult, stdioItems, outputFiles) => {
4972
- for (const { path: path12, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) {
4973
- const pathString = typeof path12 === "string" ? path12 : path12.toString();
4980
+ for (const { path: path13, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) {
4981
+ const pathString = typeof path13 === "string" ? path13 : path13.toString();
4974
4982
  if (append || outputFiles.has(pathString)) {
4975
- (0, import_node_fs4.appendFileSync)(path12, serializedResult);
4983
+ (0, import_node_fs4.appendFileSync)(path13, serializedResult);
4976
4984
  } else {
4977
4985
  outputFiles.add(pathString);
4978
- (0, import_node_fs4.writeFileSync)(path12, serializedResult);
4986
+ (0, import_node_fs4.writeFileSync)(path13, serializedResult);
4979
4987
  }
4980
4988
  }
4981
4989
  };
@@ -7363,7 +7371,7 @@ var {
7363
7371
  getCancelSignal: getCancelSignal2
7364
7372
  } = getIpcExport();
7365
7373
 
7366
- // node_modules/@remixhq/core/dist/chunk-RREREIGW.js
7374
+ // node_modules/@remixhq/core/dist/chunk-J3J4PBQ7.js
7367
7375
  async function runGit(args, cwd) {
7368
7376
  const res = await execa("git", args, { cwd, stderr: "ignore" });
7369
7377
  return String(res.stdout || "").trim();
@@ -7879,6 +7887,25 @@ function formatCliErrorDetail(err) {
7879
7887
  }
7880
7888
  return typeof err === "string" && err.trim() ? err.trim() : null;
7881
7889
  }
7890
+ async function pollAppReady(api, appId) {
7891
+ const started = Date.now();
7892
+ let delay = 2e3;
7893
+ while (Date.now() - started < 20 * 60 * 1e3) {
7894
+ const appResp = await api.getApp(appId);
7895
+ const app = unwrapResponseObject(appResp, "app");
7896
+ const status = typeof app.status === "string" ? app.status : "";
7897
+ if (status === "ready") return app;
7898
+ if (status === "error") {
7899
+ throw new RemixError("App is in error state.", {
7900
+ exitCode: 1,
7901
+ hint: typeof app.statusError === "string" ? app.statusError : null
7902
+ });
7903
+ }
7904
+ await sleep(delay);
7905
+ delay = Math.min(1e4, Math.floor(delay * 1.4));
7906
+ }
7907
+ throw new RemixError("Timed out waiting for app to become ready.", { exitCode: 1 });
7908
+ }
7882
7909
  async function pollChangeStep(api, appId, changeStepId) {
7883
7910
  const started = Date.now();
7884
7911
  let delay = 1500;
@@ -8700,6 +8727,7 @@ async function collabAdd(params) {
8700
8727
  }
8701
8728
  const { backupPath } = await writeTempUnifiedDiffBackup(diff, "remix-add");
8702
8729
  try {
8730
+ await pollAppReady(params.api, binding.currentAppId);
8703
8731
  if (submissionSnapshot) {
8704
8732
  await assertRepoSnapshotUnchanged(repoRoot, submissionSnapshot, {
8705
8733
  operation: "`remix collab add` auto-sync",
@@ -8845,7 +8873,7 @@ async function collabRecordTurn(params) {
8845
8873
  return unwrapResponseObject(resp, "collab turn");
8846
8874
  }
8847
8875
 
8848
- // node_modules/@remixhq/core/dist/chunk-4276ARDF.js
8876
+ // node_modules/@remixhq/core/dist/chunk-XC2FV57P.js
8849
8877
  async function readJsonSafe(res) {
8850
8878
  const ct = res.headers.get("content-type") ?? "";
8851
8879
  if (!ct.toLowerCase().includes("application/json")) return null;
@@ -8859,7 +8887,7 @@ function createApiClient(config, opts) {
8859
8887
  const apiKey = (opts?.apiKey ?? "").trim();
8860
8888
  const tokenProvider = opts?.tokenProvider;
8861
8889
  const CLIENT_KEY_HEADER = "x-comerge-api-key";
8862
- async function request(path12, init) {
8890
+ async function request(path13, init) {
8863
8891
  if (!tokenProvider) {
8864
8892
  throw new RemixError("API client is missing a token provider.", {
8865
8893
  exitCode: 1,
@@ -8867,7 +8895,7 @@ function createApiClient(config, opts) {
8867
8895
  });
8868
8896
  }
8869
8897
  const auth = await tokenProvider();
8870
- const url = new URL(path12, config.apiUrl).toString();
8898
+ const url = new URL(path13, config.apiUrl).toString();
8871
8899
  const doFetch = async (bearer) => fetch(url, {
8872
8900
  ...init,
8873
8901
  headers: {
@@ -8891,7 +8919,7 @@ function createApiClient(config, opts) {
8891
8919
  const json = await readJsonSafe(res);
8892
8920
  return json ?? null;
8893
8921
  }
8894
- async function requestBinary(path12, init) {
8922
+ async function requestBinary(path13, init) {
8895
8923
  if (!tokenProvider) {
8896
8924
  throw new RemixError("API client is missing a token provider.", {
8897
8925
  exitCode: 1,
@@ -8899,7 +8927,7 @@ function createApiClient(config, opts) {
8899
8927
  });
8900
8928
  }
8901
8929
  const auth = await tokenProvider();
8902
- const url = new URL(path12, config.apiUrl).toString();
8930
+ const url = new URL(path13, config.apiUrl).toString();
8903
8931
  const doFetch = async (bearer) => fetch(url, {
8904
8932
  ...init,
8905
8933
  headers: {
@@ -8960,10 +8988,29 @@ function createApiClient(config, opts) {
8960
8988
  if (params?.projectId) qs.set("projectId", params.projectId);
8961
8989
  if (params?.organizationId) qs.set("organizationId", params.organizationId);
8962
8990
  if (params?.forked) qs.set("forked", params.forked);
8991
+ if (typeof params?.limit === "number") qs.set("limit", String(params.limit));
8992
+ if (typeof params?.offset === "number") qs.set("offset", String(params.offset));
8963
8993
  const suffix = qs.toString() ? `?${qs.toString()}` : "";
8964
8994
  return request(`/v1/apps${suffix}`, { method: "GET" });
8965
8995
  },
8966
8996
  getApp: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}`, { method: "GET" }),
8997
+ getAppContext: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}/context`, { method: "GET" }),
8998
+ getAppOverview: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}/overview`, { method: "GET" }),
8999
+ listAppTimeline: (appId, params) => {
9000
+ const qs = new URLSearchParams();
9001
+ if (typeof params?.limit === "number") qs.set("limit", String(params.limit));
9002
+ if (params?.cursor) qs.set("cursor", params.cursor);
9003
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
9004
+ return request(`/v1/apps/${encodeURIComponent(appId)}/timeline${suffix}`, { method: "GET" });
9005
+ },
9006
+ getAppTimelineEvent: (appId, eventId) => request(`/v1/apps/${encodeURIComponent(appId)}/timeline/${encodeURIComponent(eventId)}`, { method: "GET" }),
9007
+ listAppEditQueue: (appId, params) => {
9008
+ const qs = new URLSearchParams();
9009
+ if (typeof params?.limit === "number") qs.set("limit", String(params.limit));
9010
+ if (typeof params?.offset === "number") qs.set("offset", String(params.offset));
9011
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
9012
+ return request(`/v1/apps/${encodeURIComponent(appId)}/edit-queue${suffix}`, { method: "GET" });
9013
+ },
8967
9014
  getMergeRequest: (mrId) => request(`/v1/merge-requests/${encodeURIComponent(mrId)}`, { method: "GET" }),
8968
9015
  presignImportUpload: (payload) => request("/v1/apps/import/upload/presign", { method: "POST", body: JSON.stringify(payload) }),
8969
9016
  importFromUpload: (payload) => request("/v1/apps/import/upload", { method: "POST", body: JSON.stringify(payload) }),
@@ -9045,6 +9092,8 @@ function createApiClient(config, opts) {
9045
9092
  qs.set("status", params.status);
9046
9093
  }
9047
9094
  if (params?.kind) qs.set("kind", params.kind);
9095
+ if (typeof params?.limit === "number") qs.set("limit", String(params.limit));
9096
+ if (typeof params?.offset === "number") qs.set("offset", String(params.offset));
9048
9097
  const suffix = qs.toString() ? `?${qs.toString()}` : "";
9049
9098
  return request(`/v1/merge-requests${suffix}`, { method: "GET" });
9050
9099
  },
@@ -9063,24 +9112,60 @@ function createApiClient(config, opts) {
9063
9112
  method: "POST",
9064
9113
  body: JSON.stringify(payload)
9065
9114
  }),
9066
- listOrganizationMembers: (orgId) => request(`/v1/organizations/${encodeURIComponent(orgId)}/members`, { method: "GET" }),
9115
+ listOrganizationMembers: (orgId, params) => {
9116
+ const qs = new URLSearchParams();
9117
+ if (typeof params?.limit === "number") qs.set("limit", String(params.limit));
9118
+ if (typeof params?.offset === "number") qs.set("offset", String(params.offset));
9119
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
9120
+ return request(`/v1/organizations/${encodeURIComponent(orgId)}/members${suffix}`, { method: "GET" });
9121
+ },
9067
9122
  updateOrganizationMember: (orgId, userId, payload) => request(`/v1/organizations/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}`, {
9068
9123
  method: "PATCH",
9069
9124
  body: JSON.stringify(payload)
9070
9125
  }),
9071
- listProjectMembers: (projectId) => request(`/v1/projects/${encodeURIComponent(projectId)}/members`, { method: "GET" }),
9126
+ listProjectMembers: (projectId, params) => {
9127
+ const qs = new URLSearchParams();
9128
+ if (typeof params?.limit === "number") qs.set("limit", String(params.limit));
9129
+ if (typeof params?.offset === "number") qs.set("offset", String(params.offset));
9130
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
9131
+ return request(`/v1/projects/${encodeURIComponent(projectId)}/members${suffix}`, { method: "GET" });
9132
+ },
9072
9133
  updateProjectMember: (projectId, userId, payload) => request(`/v1/projects/${encodeURIComponent(projectId)}/members/${encodeURIComponent(userId)}`, {
9073
9134
  method: "PATCH",
9074
9135
  body: JSON.stringify(payload)
9075
9136
  }),
9076
- listAppMembers: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}/members`, { method: "GET" }),
9137
+ listAppMembers: (appId, params) => {
9138
+ const qs = new URLSearchParams();
9139
+ if (typeof params?.limit === "number") qs.set("limit", String(params.limit));
9140
+ if (typeof params?.offset === "number") qs.set("offset", String(params.offset));
9141
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
9142
+ return request(`/v1/apps/${encodeURIComponent(appId)}/members${suffix}`, { method: "GET" });
9143
+ },
9077
9144
  updateAppMember: (appId, userId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/members/${encodeURIComponent(userId)}`, {
9078
9145
  method: "PATCH",
9079
9146
  body: JSON.stringify(payload)
9080
9147
  }),
9081
- listOrganizationInvites: (orgId) => request(`/v1/organizations/${encodeURIComponent(orgId)}/invitations`, { method: "GET" }),
9082
- listProjectInvites: (projectId) => request(`/v1/projects/${encodeURIComponent(projectId)}/invitations`, { method: "GET" }),
9083
- listAppInvites: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}/invitations`, { method: "GET" }),
9148
+ listOrganizationInvites: (orgId, params) => {
9149
+ const qs = new URLSearchParams();
9150
+ if (typeof params?.limit === "number") qs.set("limit", String(params.limit));
9151
+ if (typeof params?.offset === "number") qs.set("offset", String(params.offset));
9152
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
9153
+ return request(`/v1/organizations/${encodeURIComponent(orgId)}/invitations${suffix}`, { method: "GET" });
9154
+ },
9155
+ listProjectInvites: (projectId, params) => {
9156
+ const qs = new URLSearchParams();
9157
+ if (typeof params?.limit === "number") qs.set("limit", String(params.limit));
9158
+ if (typeof params?.offset === "number") qs.set("offset", String(params.offset));
9159
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
9160
+ return request(`/v1/projects/${encodeURIComponent(projectId)}/invitations${suffix}`, { method: "GET" });
9161
+ },
9162
+ listAppInvites: (appId, params) => {
9163
+ const qs = new URLSearchParams();
9164
+ if (typeof params?.limit === "number") qs.set("limit", String(params.limit));
9165
+ if (typeof params?.offset === "number") qs.set("offset", String(params.offset));
9166
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
9167
+ return request(`/v1/apps/${encodeURIComponent(appId)}/invitations${suffix}`, { method: "GET" });
9168
+ },
9084
9169
  resendOrganizationInvite: (orgId, inviteId, payload) => request(`/v1/organizations/${encodeURIComponent(orgId)}/invitations/${encodeURIComponent(inviteId)}/resend`, {
9085
9170
  method: "POST",
9086
9171
  body: JSON.stringify(payload ?? {})
@@ -9102,6 +9187,7 @@ function createApiClient(config, opts) {
9102
9187
  revokeAppInvite: (appId, inviteId) => request(`/v1/apps/${encodeURIComponent(appId)}/invitations/${encodeURIComponent(inviteId)}`, {
9103
9188
  method: "DELETE"
9104
9189
  }),
9190
+ acceptInvitation: (payload) => request("/v1/invitations/accept", { method: "POST", body: JSON.stringify(payload) }),
9105
9191
  syncUpstreamApp: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}/sync-upstream`, {
9106
9192
  method: "POST",
9107
9193
  body: JSON.stringify({})
@@ -9137,7 +9223,31 @@ function createApiClient(config, opts) {
9137
9223
  `/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/assets/download?${qs.toString()}`,
9138
9224
  { method: "GET" }
9139
9225
  );
9140
- }
9226
+ },
9227
+ listAgentRuns: (appId, params) => {
9228
+ const qs = new URLSearchParams();
9229
+ if (typeof params?.limit === "number") qs.set("limit", String(params.limit));
9230
+ if (typeof params?.offset === "number") qs.set("offset", String(params.offset));
9231
+ if (params?.status) qs.set("status", params.status);
9232
+ if (params?.currentPhase) qs.set("currentPhase", params.currentPhase);
9233
+ if (params?.createdAfter) qs.set("createdAfter", params.createdAfter);
9234
+ if (params?.createdBefore) qs.set("createdBefore", params.createdBefore);
9235
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
9236
+ return request(`/v1/apps/${encodeURIComponent(appId)}/agent-runs${suffix}`, { method: "GET" });
9237
+ },
9238
+ getAgentRun: (appId, runId) => request(`/v1/apps/${encodeURIComponent(appId)}/agent-runs/${encodeURIComponent(runId)}`, { method: "GET" }),
9239
+ listAgentRunEvents: (appId, runId, params) => {
9240
+ const qs = new URLSearchParams();
9241
+ if (typeof params?.limit === "number") qs.set("limit", String(params.limit));
9242
+ if (typeof params?.offset === "number") qs.set("offset", String(params.offset));
9243
+ if (params?.createdAfter) qs.set("createdAfter", params.createdAfter);
9244
+ if (params?.createdBefore) qs.set("createdBefore", params.createdBefore);
9245
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
9246
+ return request(`/v1/apps/${encodeURIComponent(appId)}/agent-runs/${encodeURIComponent(runId)}/events${suffix}`, {
9247
+ method: "GET"
9248
+ });
9249
+ },
9250
+ getSandboxStatus: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}/sandbox/status`, { method: "GET" })
9141
9251
  };
9142
9252
  }
9143
9253
 
@@ -9619,8 +9729,8 @@ function getErrorMap() {
9619
9729
 
9620
9730
  // node_modules/zod/v3/helpers/parseUtil.js
9621
9731
  var makeIssue = (params) => {
9622
- const { data, path: path12, errorMaps, issueData } = params;
9623
- const fullPath = [...path12, ...issueData.path || []];
9732
+ const { data, path: path13, errorMaps, issueData } = params;
9733
+ const fullPath = [...path13, ...issueData.path || []];
9624
9734
  const fullIssue = {
9625
9735
  ...issueData,
9626
9736
  path: fullPath
@@ -9736,11 +9846,11 @@ var errorUtil;
9736
9846
 
9737
9847
  // node_modules/zod/v3/types.js
9738
9848
  var ParseInputLazyPath = class {
9739
- constructor(parent, value, path12, key) {
9849
+ constructor(parent, value, path13, key) {
9740
9850
  this._cachedPath = [];
9741
9851
  this.parent = parent;
9742
9852
  this.data = value;
9743
- this._path = path12;
9853
+ this._path = path13;
9744
9854
  this._key = key;
9745
9855
  }
9746
9856
  get path() {
@@ -22073,8 +22183,8 @@ var IcebergError = class extends Error {
22073
22183
  return this.status === 419;
22074
22184
  }
22075
22185
  };
22076
- function buildUrl(baseUrl, path12, query) {
22077
- const url = new URL(path12, baseUrl);
22186
+ function buildUrl(baseUrl, path13, query) {
22187
+ const url = new URL(path13, baseUrl);
22078
22188
  if (query) {
22079
22189
  for (const [key, value] of Object.entries(query)) {
22080
22190
  if (value !== void 0) {
@@ -22104,12 +22214,12 @@ function createFetchClient(options) {
22104
22214
  return {
22105
22215
  async request({
22106
22216
  method,
22107
- path: path12,
22217
+ path: path13,
22108
22218
  query,
22109
22219
  body,
22110
22220
  headers
22111
22221
  }) {
22112
- const url = buildUrl(options.baseUrl, path12, query);
22222
+ const url = buildUrl(options.baseUrl, path13, query);
22113
22223
  const authHeaders = await buildAuthHeaders(options.auth);
22114
22224
  const res = await fetchFn(url, {
22115
22225
  method,
@@ -22928,7 +23038,7 @@ var StorageFileApi = class extends BaseApiClient {
22928
23038
  * @param path The relative file path. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload.
22929
23039
  * @param fileBody The body of the file to be stored in the bucket.
22930
23040
  */
22931
- async uploadOrUpdate(method, path12, fileBody, fileOptions) {
23041
+ async uploadOrUpdate(method, path13, fileBody, fileOptions) {
22932
23042
  var _this = this;
22933
23043
  return _this.handleOperation(async () => {
22934
23044
  let body;
@@ -22952,7 +23062,7 @@ var StorageFileApi = class extends BaseApiClient {
22952
23062
  if ((typeof ReadableStream !== "undefined" && body instanceof ReadableStream || body && typeof body === "object" && "pipe" in body && typeof body.pipe === "function") && !options.duplex) options.duplex = "half";
22953
23063
  }
22954
23064
  if (fileOptions === null || fileOptions === void 0 ? void 0 : fileOptions.headers) headers = _objectSpread22(_objectSpread22({}, headers), fileOptions.headers);
22955
- const cleanPath = _this._removeEmptyFolders(path12);
23065
+ const cleanPath = _this._removeEmptyFolders(path13);
22956
23066
  const _path = _this._getFinalPath(cleanPath);
22957
23067
  const data = await (method == "PUT" ? put : post)(_this.fetch, `${_this.url}/object/${_path}`, body, _objectSpread22({ headers }, (options === null || options === void 0 ? void 0 : options.duplex) ? { duplex: options.duplex } : {}));
22958
23068
  return {
@@ -23013,8 +23123,8 @@ var StorageFileApi = class extends BaseApiClient {
23013
23123
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23014
23124
  * - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Upload file using `ArrayBuffer` from base64 file data instead, see example below.
23015
23125
  */
23016
- async upload(path12, fileBody, fileOptions) {
23017
- return this.uploadOrUpdate("POST", path12, fileBody, fileOptions);
23126
+ async upload(path13, fileBody, fileOptions) {
23127
+ return this.uploadOrUpdate("POST", path13, fileBody, fileOptions);
23018
23128
  }
23019
23129
  /**
23020
23130
  * Upload a file with a token generated from `createSignedUploadUrl`.
@@ -23053,9 +23163,9 @@ var StorageFileApi = class extends BaseApiClient {
23053
23163
  * - `objects` table permissions: none
23054
23164
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23055
23165
  */
23056
- async uploadToSignedUrl(path12, token, fileBody, fileOptions) {
23166
+ async uploadToSignedUrl(path13, token, fileBody, fileOptions) {
23057
23167
  var _this3 = this;
23058
- const cleanPath = _this3._removeEmptyFolders(path12);
23168
+ const cleanPath = _this3._removeEmptyFolders(path13);
23059
23169
  const _path = _this3._getFinalPath(cleanPath);
23060
23170
  const url = new URL(_this3.url + `/object/upload/sign/${_path}`);
23061
23171
  url.searchParams.set("token", token);
@@ -23117,10 +23227,10 @@ var StorageFileApi = class extends BaseApiClient {
23117
23227
  * - `objects` table permissions: `insert`
23118
23228
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23119
23229
  */
23120
- async createSignedUploadUrl(path12, options) {
23230
+ async createSignedUploadUrl(path13, options) {
23121
23231
  var _this4 = this;
23122
23232
  return _this4.handleOperation(async () => {
23123
- let _path = _this4._getFinalPath(path12);
23233
+ let _path = _this4._getFinalPath(path13);
23124
23234
  const headers = _objectSpread22({}, _this4.headers);
23125
23235
  if (options === null || options === void 0 ? void 0 : options.upsert) headers["x-upsert"] = "true";
23126
23236
  const data = await post(_this4.fetch, `${_this4.url}/object/upload/sign/${_path}`, {}, { headers });
@@ -23129,7 +23239,7 @@ var StorageFileApi = class extends BaseApiClient {
23129
23239
  if (!token) throw new StorageError("No token returned by API");
23130
23240
  return {
23131
23241
  signedUrl: url.toString(),
23132
- path: path12,
23242
+ path: path13,
23133
23243
  token
23134
23244
  };
23135
23245
  });
@@ -23185,8 +23295,8 @@ var StorageFileApi = class extends BaseApiClient {
23185
23295
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23186
23296
  * - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Update file using `ArrayBuffer` from base64 file data instead, see example below.
23187
23297
  */
23188
- async update(path12, fileBody, fileOptions) {
23189
- return this.uploadOrUpdate("PUT", path12, fileBody, fileOptions);
23298
+ async update(path13, fileBody, fileOptions) {
23299
+ return this.uploadOrUpdate("PUT", path13, fileBody, fileOptions);
23190
23300
  }
23191
23301
  /**
23192
23302
  * Moves an existing file to a new path in the same bucket.
@@ -23333,10 +23443,10 @@ var StorageFileApi = class extends BaseApiClient {
23333
23443
  * - `objects` table permissions: `select`
23334
23444
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23335
23445
  */
23336
- async createSignedUrl(path12, expiresIn, options) {
23446
+ async createSignedUrl(path13, expiresIn, options) {
23337
23447
  var _this8 = this;
23338
23448
  return _this8.handleOperation(async () => {
23339
- let _path = _this8._getFinalPath(path12);
23449
+ let _path = _this8._getFinalPath(path13);
23340
23450
  const hasTransform = typeof (options === null || options === void 0 ? void 0 : options.transform) === "object" && options.transform !== null && Object.keys(options.transform).length > 0;
23341
23451
  let data = await post(_this8.fetch, `${_this8.url}/object/sign/${_path}`, _objectSpread22({ expiresIn }, hasTransform ? { transform: options.transform } : {}), { headers: _this8.headers });
23342
23452
  const downloadQueryParam = (options === null || options === void 0 ? void 0 : options.download) ? `&download=${options.download === true ? "" : options.download}` : "";
@@ -23463,11 +23573,11 @@ var StorageFileApi = class extends BaseApiClient {
23463
23573
  * - `objects` table permissions: `select`
23464
23574
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23465
23575
  */
23466
- download(path12, options, parameters) {
23576
+ download(path13, options, parameters) {
23467
23577
  const renderPath = typeof (options === null || options === void 0 ? void 0 : options.transform) !== "undefined" ? "render/image/authenticated" : "object";
23468
23578
  const transformationQuery = this.transformOptsToQueryString((options === null || options === void 0 ? void 0 : options.transform) || {});
23469
23579
  const queryString = transformationQuery ? `?${transformationQuery}` : "";
23470
- const _path = this._getFinalPath(path12);
23580
+ const _path = this._getFinalPath(path13);
23471
23581
  const downloadFn = () => get(this.fetch, `${this.url}/${renderPath}/${_path}${queryString}`, {
23472
23582
  headers: this.headers,
23473
23583
  noResolveJson: true
@@ -23497,9 +23607,9 @@ var StorageFileApi = class extends BaseApiClient {
23497
23607
  * }
23498
23608
  * ```
23499
23609
  */
23500
- async info(path12) {
23610
+ async info(path13) {
23501
23611
  var _this10 = this;
23502
- const _path = _this10._getFinalPath(path12);
23612
+ const _path = _this10._getFinalPath(path13);
23503
23613
  return _this10.handleOperation(async () => {
23504
23614
  return recursiveToCamel(await get(_this10.fetch, `${_this10.url}/object/info/${_path}`, { headers: _this10.headers }));
23505
23615
  });
@@ -23519,9 +23629,9 @@ var StorageFileApi = class extends BaseApiClient {
23519
23629
  * .exists('folder/avatar1.png')
23520
23630
  * ```
23521
23631
  */
23522
- async exists(path12) {
23632
+ async exists(path13) {
23523
23633
  var _this11 = this;
23524
- const _path = _this11._getFinalPath(path12);
23634
+ const _path = _this11._getFinalPath(path13);
23525
23635
  try {
23526
23636
  await head(_this11.fetch, `${_this11.url}/object/${_path}`, { headers: _this11.headers });
23527
23637
  return {
@@ -23598,8 +23708,8 @@ var StorageFileApi = class extends BaseApiClient {
23598
23708
  * - `objects` table permissions: none
23599
23709
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23600
23710
  */
23601
- getPublicUrl(path12, options) {
23602
- const _path = this._getFinalPath(path12);
23711
+ getPublicUrl(path13, options) {
23712
+ const _path = this._getFinalPath(path13);
23603
23713
  const _queryString = [];
23604
23714
  const downloadQueryParam = (options === null || options === void 0 ? void 0 : options.download) ? `download=${options.download === true ? "" : options.download}` : "";
23605
23715
  if (downloadQueryParam !== "") _queryString.push(downloadQueryParam);
@@ -23738,10 +23848,10 @@ var StorageFileApi = class extends BaseApiClient {
23738
23848
  * - `objects` table permissions: `select`
23739
23849
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23740
23850
  */
23741
- async list(path12, options, parameters) {
23851
+ async list(path13, options, parameters) {
23742
23852
  var _this13 = this;
23743
23853
  return _this13.handleOperation(async () => {
23744
- const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix: path12 || "" });
23854
+ const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix: path13 || "" });
23745
23855
  return await post(_this13.fetch, `${_this13.url}/object/list/${_this13.bucketId}`, body, { headers: _this13.headers }, parameters);
23746
23856
  });
23747
23857
  }
@@ -23805,11 +23915,11 @@ var StorageFileApi = class extends BaseApiClient {
23805
23915
  if (typeof Buffer !== "undefined") return Buffer.from(data).toString("base64");
23806
23916
  return btoa(data);
23807
23917
  }
23808
- _getFinalPath(path12) {
23809
- return `${this.bucketId}/${path12.replace(/^\/+/, "")}`;
23918
+ _getFinalPath(path13) {
23919
+ return `${this.bucketId}/${path13.replace(/^\/+/, "")}`;
23810
23920
  }
23811
- _removeEmptyFolders(path12) {
23812
- return path12.replace(/^\/|\/$/g, "").replace(/\/+/g, "/");
23921
+ _removeEmptyFolders(path13) {
23922
+ return path13.replace(/^\/|\/$/g, "").replace(/\/+/g, "/");
23813
23923
  }
23814
23924
  transformOptsToQueryString(transform) {
23815
23925
  const params = [];
@@ -33085,13 +33195,20 @@ async function createHookCollabApiClient() {
33085
33195
  });
33086
33196
  }
33087
33197
 
33198
+ // src/hook-diagnostics.ts
33199
+ var import_node_crypto2 = require("crypto");
33200
+ var import_promises19 = __toESM(require("fs/promises"), 1);
33201
+ var import_node_os5 = __toESM(require("os"), 1);
33202
+ var import_node_path7 = __toESM(require("path"), 1);
33203
+
33088
33204
  // src/hook-state.ts
33089
33205
  var import_promises18 = __toESM(require("fs/promises"), 1);
33090
33206
  var import_node_os4 = __toESM(require("os"), 1);
33091
33207
  var import_node_path6 = __toESM(require("path"), 1);
33092
33208
  var import_node_crypto = require("crypto");
33093
33209
  function stateRoot() {
33094
- return import_node_path6.default.join(import_node_os4.default.tmpdir(), "remix-claude-plugin-hooks");
33210
+ const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_STATE_ROOT?.trim();
33211
+ return configured || import_node_path6.default.join(import_node_os4.default.tmpdir(), "remix-claude-plugin-hooks");
33095
33212
  }
33096
33213
  function statePath(sessionId) {
33097
33214
  return import_node_path6.default.join(stateRoot(), `${sessionId}.json`);
@@ -33156,6 +33273,7 @@ async function tryRemoveStaleStateLock(sessionId) {
33156
33273
  async function acquireStateLock(sessionId) {
33157
33274
  const lockPath = stateLockPath(sessionId);
33158
33275
  const deadline = Date.now() + STATE_LOCK_WAIT_MS;
33276
+ await import_promises18.default.mkdir(stateRoot(), { recursive: true });
33159
33277
  while (true) {
33160
33278
  try {
33161
33279
  await import_promises18.default.mkdir(lockPath);
@@ -33404,7 +33522,7 @@ async function clearPendingTurnState(sessionId) {
33404
33522
  // package.json
33405
33523
  var package_default = {
33406
33524
  name: "@remixhq/claude-plugin",
33407
- version: "0.1.11",
33525
+ version: "0.1.14",
33408
33526
  description: "Claude Code plugin for Remix collaboration workflows",
33409
33527
  homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
33410
33528
  license: "MIT",
@@ -33435,8 +33553,8 @@ var package_default = {
33435
33553
  prepack: "npm run build"
33436
33554
  },
33437
33555
  dependencies: {
33438
- "@remixhq/core": "^0.1.8",
33439
- "@remixhq/mcp": "^0.1.8"
33556
+ "@remixhq/core": "^0.1.9",
33557
+ "@remixhq/mcp": "^0.1.9"
33440
33558
  },
33441
33559
  devDependencies: {
33442
33560
  "@types/node": "^25.4.0",
@@ -33455,9 +33573,90 @@ var pluginMetadata = {
33455
33573
  agentName: "remix-collab"
33456
33574
  };
33457
33575
 
33576
+ // src/hook-diagnostics.ts
33577
+ var MAX_LOG_BYTES = 512 * 1024;
33578
+ function resolveClaudeRoot() {
33579
+ const configured = process.env.CLAUDE_CONFIG_DIR?.trim();
33580
+ return configured || import_node_path7.default.join(import_node_os5.default.homedir(), ".claude");
33581
+ }
33582
+ function resolvePluginDataDirName() {
33583
+ return `${pluginMetadata.pluginId}-${pluginMetadata.pluginId}`;
33584
+ }
33585
+ function getHookDiagnosticsDirPath() {
33586
+ const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_DIAGNOSTICS_DIR?.trim();
33587
+ return configured || import_node_path7.default.join(resolveClaudeRoot(), "plugins", "data", resolvePluginDataDirName());
33588
+ }
33589
+ function getHookDiagnosticsLogPath() {
33590
+ return import_node_path7.default.join(getHookDiagnosticsDirPath(), "hooks.ndjson");
33591
+ }
33592
+ function toFieldValue(value) {
33593
+ if (value === null) return null;
33594
+ if (typeof value === "string") return value;
33595
+ if (typeof value === "number" && Number.isFinite(value)) return value;
33596
+ if (typeof value === "boolean") return value;
33597
+ return void 0;
33598
+ }
33599
+ function normalizeFields(fields) {
33600
+ if (!fields) return {};
33601
+ const normalizedEntries = Object.entries(fields).map(([key, value]) => {
33602
+ const normalized = toFieldValue(value);
33603
+ return normalized === void 0 ? null : [key, normalized];
33604
+ }).filter((entry) => entry !== null);
33605
+ return Object.fromEntries(normalizedEntries);
33606
+ }
33607
+ async function rotateLogIfNeeded(logPath) {
33608
+ const stat = await import_promises19.default.stat(logPath).catch(() => null);
33609
+ if (!stat || stat.size < MAX_LOG_BYTES) {
33610
+ return;
33611
+ }
33612
+ const rotatedPath = `${logPath}.1`;
33613
+ await import_promises19.default.rm(rotatedPath, { force: true }).catch(() => void 0);
33614
+ await import_promises19.default.rename(logPath, rotatedPath).catch(() => void 0);
33615
+ }
33616
+ function summarizeText(value) {
33617
+ if (typeof value !== "string" || !value.trim()) {
33618
+ return {
33619
+ present: false,
33620
+ length: 0,
33621
+ sha256Prefix: null
33622
+ };
33623
+ }
33624
+ const trimmed = value.trim();
33625
+ return {
33626
+ present: true,
33627
+ length: trimmed.length,
33628
+ sha256Prefix: (0, import_node_crypto2.createHash)("sha256").update(trimmed).digest("hex").slice(0, 12)
33629
+ };
33630
+ }
33631
+ async function appendHookDiagnosticsEvent(params) {
33632
+ try {
33633
+ const logPath = getHookDiagnosticsLogPath();
33634
+ await import_promises19.default.mkdir(import_node_path7.default.dirname(logPath), { recursive: true });
33635
+ await rotateLogIfNeeded(logPath);
33636
+ const event = {
33637
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
33638
+ hook: params.hook,
33639
+ pluginVersion: pluginMetadata.version,
33640
+ pid: process.pid,
33641
+ sessionId: params.sessionId?.trim() || null,
33642
+ turnId: params.turnId?.trim() || null,
33643
+ stage: params.stage.trim(),
33644
+ result: params.result,
33645
+ reason: params.reason?.trim() || null,
33646
+ toolName: params.toolName?.trim() || null,
33647
+ repoRoot: params.repoRoot?.trim() || null,
33648
+ message: params.message?.trim() || null,
33649
+ fields: normalizeFields(params.fields)
33650
+ };
33651
+ await import_promises19.default.appendFile(logPath, `${JSON.stringify(event)}
33652
+ `, "utf8");
33653
+ } catch {
33654
+ }
33655
+ }
33656
+
33458
33657
  // src/hook-utils.ts
33459
- var import_promises19 = __toESM(require("fs/promises"), 1);
33460
- var import_node_path7 = __toESM(require("path"), 1);
33658
+ var import_promises20 = __toESM(require("fs/promises"), 1);
33659
+ var import_node_path8 = __toESM(require("path"), 1);
33461
33660
  async function readJsonStdin() {
33462
33661
  const chunks = [];
33463
33662
  for await (const chunk of process.stdin) {
@@ -33481,6 +33680,11 @@ function extractToolInput(payload) {
33481
33680
  function extractToolResponse(payload) {
33482
33681
  return getNestedRecord(payload.tool_response) ?? getNestedRecord(payload.toolResponse);
33483
33682
  }
33683
+ function extractToolStructuredData(payload) {
33684
+ const toolResponse = extractToolResponse(payload);
33685
+ const structuredContent = getNestedRecord(toolResponse?.structuredContent) ?? getNestedRecord(payload.structuredContent);
33686
+ return getNestedRecord(toolResponse?.data) ?? getNestedRecord(structuredContent?.data) ?? structuredContent;
33687
+ }
33484
33688
  function extractAssistantResponse(payload) {
33485
33689
  const candidateKeys = [
33486
33690
  "last_assistant_message",
@@ -33492,7 +33696,7 @@ function extractAssistantResponse(payload) {
33492
33696
  "response",
33493
33697
  "message"
33494
33698
  ];
33495
- return extractString(payload, candidateKeys) ?? extractString(extractToolResponse(payload) ?? {}, candidateKeys) ?? extractString(extractToolInput(payload), candidateKeys);
33699
+ return extractString(payload, candidateKeys) ?? extractString(extractToolResponse(payload) ?? {}, candidateKeys) ?? extractString(extractToolStructuredData(payload) ?? {}, candidateKeys) ?? extractString(extractToolInput(payload), candidateKeys);
33496
33700
  }
33497
33701
  function extractString(input, keys) {
33498
33702
  for (const key of keys) {
@@ -33514,16 +33718,16 @@ function extractBoolean(input, keys) {
33514
33718
  }
33515
33719
  async function findBoundRepo(startPath) {
33516
33720
  if (!startPath) return null;
33517
- let current = import_node_path7.default.resolve(startPath);
33518
- let stats = await import_promises19.default.stat(current).catch(() => null);
33721
+ let current = import_node_path8.default.resolve(startPath);
33722
+ let stats = await import_promises20.default.stat(current).catch(() => null);
33519
33723
  if (stats?.isFile()) {
33520
- current = import_node_path7.default.dirname(current);
33724
+ current = import_node_path8.default.dirname(current);
33521
33725
  }
33522
33726
  while (true) {
33523
- const bindingPath = import_node_path7.default.join(current, ".remix", "config.json");
33524
- const bindingStats = await import_promises19.default.stat(bindingPath).catch(() => null);
33727
+ const bindingPath = import_node_path8.default.join(current, ".remix", "config.json");
33728
+ const bindingStats = await import_promises20.default.stat(bindingPath).catch(() => null);
33525
33729
  if (bindingStats?.isFile()) return current;
33526
- const parent = import_node_path7.default.dirname(current);
33730
+ const parent = import_node_path8.default.dirname(current);
33527
33731
  if (parent === current) return null;
33528
33732
  current = parent;
33529
33733
  }
@@ -33552,52 +33756,52 @@ function getErrorDetails(error) {
33552
33756
  if (error instanceof Error) {
33553
33757
  const hint = typeof error.hint === "string" ? String(error.hint) : null;
33554
33758
  return {
33555
- message: error.message || "Automatic Remix turn recording failed.",
33759
+ message: error.message || "Fallback Remix turn recording failed.",
33556
33760
  hint
33557
33761
  };
33558
33762
  }
33559
- const message = typeof error === "string" && error.trim() ? error.trim() : "Automatic Remix turn recording failed.";
33763
+ const message = typeof error === "string" && error.trim() ? error.trim() : "Fallback Remix turn recording failed.";
33560
33764
  return { message, hint: null };
33561
33765
  }
33562
33766
  function getRecordingBlockedMessage(status, repoRoot) {
33563
33767
  switch (status.status) {
33564
33768
  case "not_git_repo":
33565
33769
  return {
33566
- message: "Automatic Remix turn recording failed because the repository is no longer inside a git repository.",
33770
+ message: "Fallback Remix turn recording failed because the repository is no longer inside a git repository.",
33567
33771
  hint: status.hint || `Repo root: ${repoRoot}`
33568
33772
  };
33569
33773
  case "not_bound":
33570
33774
  return {
33571
- message: "Automatic Remix turn recording failed because the repository is no longer bound to Remix.",
33775
+ message: "Fallback Remix turn recording failed because the repository is no longer bound to Remix.",
33572
33776
  hint: status.hint || `Repo root: ${repoRoot}`
33573
33777
  };
33574
33778
  case "missing_head":
33575
33779
  return {
33576
- message: "Automatic Remix turn recording failed because the repository HEAD could not be resolved.",
33780
+ message: "Fallback Remix turn recording failed because the repository HEAD could not be resolved.",
33577
33781
  hint: status.hint || `Repo root: ${repoRoot}`
33578
33782
  };
33579
33783
  case "branch_mismatch":
33580
33784
  return {
33581
- message: "Automatic Remix turn recording was blocked by the checkout's preferred-branch policy.",
33785
+ message: "Fallback Remix turn recording was blocked by the checkout's preferred-branch policy.",
33582
33786
  hint: status.hint || `Repo root: ${repoRoot}`
33583
33787
  };
33584
33788
  case "metadata_conflict":
33585
33789
  return {
33586
- message: "Automatic Remix turn recording was blocked because local repository metadata conflicts with the bound Remix app.",
33790
+ message: "Fallback Remix turn recording was blocked because local repository metadata conflicts with the bound Remix app.",
33587
33791
  hint: status.hint || `Repo root: ${repoRoot}`
33588
33792
  };
33589
33793
  case "reconcile_required":
33590
33794
  return {
33591
- message: "Automatic Remix turn recording was blocked because the repository must be reconciled before recording can continue safely.",
33795
+ message: "Fallback Remix turn recording was blocked because the repository must be reconciled before recording can continue safely.",
33592
33796
  hint: status.hint || `Repo root: ${repoRoot}`
33593
33797
  };
33594
33798
  default:
33595
33799
  return null;
33596
33800
  }
33597
33801
  }
33598
- function buildRepoIdempotencyKey(turnId, repo, mode) {
33802
+ function buildRepoIdempotencyKey(turnId, repo) {
33599
33803
  const repoToken = repo.currentAppId?.trim() || repo.repoRoot;
33600
- return `${turnId}:${repoToken}:${mode}`;
33804
+ return `${turnId}:${repoToken}:finalize_turn`;
33601
33805
  }
33602
33806
  function shouldSkipStopRecording(repo) {
33603
33807
  if (repo.stopRecorded) {
@@ -33650,21 +33854,64 @@ function createFallbackTouchedRepo(params) {
33650
33854
  };
33651
33855
  }
33652
33856
  async function recordTouchedRepo(params) {
33653
- const { sessionId, turnId, repo, prompt, assistantResponse, api } = params;
33857
+ const { hook, sessionId, turnId, repo, prompt, assistantResponse, api } = params;
33654
33858
  await markTouchedRepoStopAttempted(sessionId, repo.repoRoot);
33859
+ await appendHookDiagnosticsEvent({
33860
+ hook,
33861
+ sessionId,
33862
+ turnId,
33863
+ stage: "recording_started",
33864
+ result: "info",
33865
+ repoRoot: repo.repoRoot,
33866
+ fields: {
33867
+ hasObservedWrite: repo.hasObservedWrite,
33868
+ manuallyRecorded: repo.manuallyRecorded,
33869
+ stopAttempted: true
33870
+ }
33871
+ });
33655
33872
  try {
33656
33873
  const binding = await readCollabBinding(repo.repoRoot).catch(() => null);
33657
33874
  if (!binding) {
33658
33875
  await markTouchedRepoRecordingFailure(sessionId, repo.repoRoot, {
33659
- message: "Automatic Remix turn recording failed because the repository is no longer bound to Remix.",
33876
+ message: "Fallback Remix turn recording failed because the repository is no longer bound to Remix.",
33660
33877
  hint: `Repo root: ${repo.repoRoot}`
33661
33878
  });
33879
+ await appendHookDiagnosticsEvent({
33880
+ hook,
33881
+ sessionId,
33882
+ turnId,
33883
+ stage: "binding_lookup",
33884
+ result: "error",
33885
+ reason: "repo_not_bound",
33886
+ repoRoot: repo.repoRoot
33887
+ });
33662
33888
  return false;
33663
33889
  }
33664
33890
  const workspaceDiff = await getWorkspaceDiff(repo.repoRoot);
33665
33891
  if (workspaceDiff.diff.trim()) {
33892
+ await appendHookDiagnosticsEvent({
33893
+ hook,
33894
+ sessionId,
33895
+ turnId,
33896
+ stage: "workspace_diff_checked",
33897
+ result: "info",
33898
+ repoRoot: repo.repoRoot,
33899
+ fields: {
33900
+ hasWorkspaceDiff: true,
33901
+ diffLength: workspaceDiff.diff.length
33902
+ }
33903
+ });
33666
33904
  if (repo.manualRemoteChangeRecordedAt && !hasNewObservedWriteSince(repo.manualRemoteChangeRecordedAt, repo)) {
33667
33905
  await markTouchedRepoStopRecorded(sessionId, repo.repoRoot, { mode: "changed_turn" });
33906
+ await appendHookDiagnosticsEvent({
33907
+ hook,
33908
+ sessionId,
33909
+ turnId,
33910
+ stage: "recording_skipped",
33911
+ result: "success",
33912
+ reason: "manual_recording_already_covers_diff",
33913
+ repoRoot: repo.repoRoot
33914
+ });
33668
33915
  return true;
33669
33916
  }
33670
33917
  const recordingPreflight2 = await collabRecordingPreflight({
@@ -33674,6 +33921,19 @@ async function recordTouchedRepo(params) {
33674
33921
  const blocked2 = getRecordingBlockedMessage(recordingPreflight2, repo.repoRoot);
33675
33922
  if (blocked2) {
33676
33923
  await markTouchedRepoRecordingFailure(sessionId, repo.repoRoot, blocked2);
33924
+ await appendHookDiagnosticsEvent({
33925
+ hook,
33926
+ sessionId,
33927
+ turnId,
33928
+ stage: "recording_preflight",
33929
+ result: "error",
33930
+ reason: recordingPreflight2.status,
33931
+ repoRoot: repo.repoRoot,
33932
+ message: blocked2.message,
33933
+ fields: {
33934
+ hint: blocked2.hint
33935
+ }
33936
+ });
33677
33937
  return false;
33678
33938
  }
33679
33939
  await collabAdd({
@@ -33682,12 +33942,33 @@ async function recordTouchedRepo(params) {
33682
33942
  prompt,
33683
33943
  assistantResponse,
33684
33944
  diffSource: "worktree",
33685
- idempotencyKey: buildRepoIdempotencyKey(turnId, repo, "changed_turn"),
33945
+ idempotencyKey: buildRepoIdempotencyKey(turnId, repo),
33686
33946
  actor: HOOK_ACTOR
33687
33947
  });
33688
33948
  await markTouchedRepoStopRecorded(sessionId, repo.repoRoot, { mode: "changed_turn" });
33949
+ await appendHookDiagnosticsEvent({
33950
+ hook,
33951
+ sessionId,
33952
+ turnId,
33953
+ stage: "recording_completed",
33954
+ result: "success",
33955
+ reason: "changed_turn_recorded",
33956
+ repoRoot: repo.repoRoot
33957
+ });
33689
33958
  return true;
33690
33959
  }
33960
+ await appendHookDiagnosticsEvent({
33961
+ hook,
33962
+ sessionId,
33963
+ turnId,
33964
+ stage: "workspace_diff_checked",
33965
+ result: "info",
33966
+ repoRoot: repo.repoRoot,
33967
+ fields: {
33968
+ hasWorkspaceDiff: false,
33969
+ diffLength: 0
33970
+ }
33971
+ });
33691
33972
  const recordingPreflight = await collabRecordingPreflight({
33692
33973
  api,
33693
33974
  cwd: repo.repoRoot
@@ -33695,6 +33976,19 @@ async function recordTouchedRepo(params) {
33695
33976
  const blocked = getRecordingBlockedMessage(recordingPreflight, repo.repoRoot);
33696
33977
  if (blocked) {
33697
33978
  await markTouchedRepoRecordingFailure(sessionId, repo.repoRoot, blocked);
33979
+ await appendHookDiagnosticsEvent({
33980
+ hook,
33981
+ sessionId,
33982
+ turnId,
33983
+ stage: "recording_preflight",
33984
+ result: "error",
33985
+ reason: recordingPreflight.status,
33986
+ repoRoot: repo.repoRoot,
33987
+ message: blocked.message,
33988
+ fields: {
33989
+ hint: blocked.hint
33990
+ }
33991
+ });
33698
33992
  return false;
33699
33993
  }
33700
33994
  await collabRecordTurn({
@@ -33702,36 +33996,116 @@ async function recordTouchedRepo(params) {
33702
33996
  cwd: repo.repoRoot,
33703
33997
  prompt,
33704
33998
  assistantResponse,
33705
- idempotencyKey: buildRepoIdempotencyKey(turnId, repo, "no_diff_turn"),
33999
+ idempotencyKey: buildRepoIdempotencyKey(turnId, repo),
33706
34000
  actor: HOOK_ACTOR
33707
34001
  });
33708
34002
  await markTouchedRepoStopRecorded(sessionId, repo.repoRoot, { mode: "no_diff_turn" });
34003
+ await appendHookDiagnosticsEvent({
34004
+ hook,
34005
+ sessionId,
34006
+ turnId,
34007
+ stage: "recording_completed",
34008
+ result: "success",
34009
+ reason: "no_diff_turn_recorded",
34010
+ repoRoot: repo.repoRoot
34011
+ });
33709
34012
  return true;
33710
34013
  } catch (error) {
33711
34014
  const details = getErrorDetails(error);
33712
34015
  await markTouchedRepoRecordingFailure(sessionId, repo.repoRoot, details);
34016
+ await appendHookDiagnosticsEvent({
34017
+ hook,
34018
+ sessionId,
34019
+ turnId,
34020
+ stage: "recording_failed",
34021
+ result: "error",
34022
+ reason: "exception",
34023
+ repoRoot: repo.repoRoot,
34024
+ message: details.message,
34025
+ fields: {
34026
+ hint: details.hint
34027
+ }
34028
+ });
33713
34029
  return false;
33714
34030
  }
33715
34031
  }
33716
- async function main() {
33717
- const payload = await readJsonStdin();
34032
+ async function runHookStopCollab(payload) {
34033
+ const hook = "Stop";
33718
34034
  if (extractBoolean(payload, ["stop_hook_active"])) {
34035
+ await appendHookDiagnosticsEvent({
34036
+ hook,
34037
+ stage: "reentrancy_guard",
34038
+ result: "skip",
34039
+ reason: "stop_hook_active"
34040
+ });
33719
34041
  return;
33720
34042
  }
33721
34043
  const sessionId = extractString(payload, ["session_id"]);
34044
+ await appendHookDiagnosticsEvent({
34045
+ hook,
34046
+ sessionId,
34047
+ stage: "payload_received",
34048
+ result: "start",
34049
+ fields: {
34050
+ hasSessionId: Boolean(sessionId),
34051
+ hasStopHookActive: Boolean(extractBoolean(payload, ["stop_hook_active"])),
34052
+ hasAssistantResponse: summarizeText(extractAssistantResponse(payload)).present
34053
+ }
34054
+ });
33722
34055
  if (!sessionId) {
34056
+ await appendHookDiagnosticsEvent({
34057
+ hook,
34058
+ stage: "payload_validation",
34059
+ result: "skip",
34060
+ reason: "missing_session_id"
34061
+ });
33723
34062
  return;
33724
34063
  }
33725
34064
  const state = await loadPendingTurnState(sessionId);
33726
34065
  if (!state) {
34066
+ await appendHookDiagnosticsEvent({
34067
+ hook,
34068
+ sessionId,
34069
+ stage: "state_lookup",
34070
+ result: "skip",
34071
+ reason: "pending_state_not_found"
34072
+ });
33727
34073
  return;
33728
34074
  }
34075
+ await appendHookDiagnosticsEvent({
34076
+ hook,
34077
+ sessionId,
34078
+ turnId: state.turnId,
34079
+ stage: "state_loaded",
34080
+ result: "info",
34081
+ fields: {
34082
+ consultedMemory: state.consultedMemory,
34083
+ touchedRepoCount: Object.keys(state.touchedRepos).length,
34084
+ hasTurnFailure: Boolean(state.turnFailureMessage)
34085
+ }
34086
+ });
33729
34087
  try {
33730
34088
  let touchedRepos = await listTouchedRepos(sessionId);
33731
34089
  if (touchedRepos.length === 0) {
33732
34090
  const fallbackRepo = await resolveBoundRepoSummary(state.initialCwd);
33733
34091
  if (!fallbackRepo) {
33734
34092
  await clearPendingTurnState(sessionId);
34093
+ await appendHookDiagnosticsEvent({
34094
+ hook,
34095
+ sessionId,
34096
+ turnId: state.turnId,
34097
+ stage: "fallback_repo_lookup",
34098
+ result: "skip",
34099
+ reason: "no_bound_repo_for_fallback"
34100
+ });
34101
+ await appendHookDiagnosticsEvent({
34102
+ hook,
34103
+ sessionId,
34104
+ turnId: state.turnId,
34105
+ stage: "state_cleanup",
34106
+ result: "success",
34107
+ reason: "cleared_without_bound_repo"
34108
+ });
33735
34109
  return;
33736
34110
  }
33737
34111
  await upsertTouchedRepo(sessionId, {
@@ -33746,22 +34120,80 @@ async function main() {
33746
34120
  if (touchedRepos.length === 0) {
33747
34121
  touchedRepos = [createFallbackTouchedRepo(fallbackRepo)];
33748
34122
  }
34123
+ await appendHookDiagnosticsEvent({
34124
+ hook,
34125
+ sessionId,
34126
+ turnId: state.turnId,
34127
+ stage: "fallback_repo_lookup",
34128
+ result: "info",
34129
+ repoRoot: fallbackRepo.repoRoot,
34130
+ fields: {
34131
+ touchedRepoCount: touchedRepos.length
34132
+ }
34133
+ });
33749
34134
  }
33750
34135
  const prompt = state.prompt.trim();
33751
34136
  const assistantResponse = (extractAssistantResponse(payload) || "").trim();
34137
+ const promptSummary = summarizeText(prompt);
34138
+ const assistantResponseSummary = summarizeText(assistantResponse);
34139
+ await appendHookDiagnosticsEvent({
34140
+ hook,
34141
+ sessionId,
34142
+ turnId: state.turnId,
34143
+ stage: "response_summary",
34144
+ result: "info",
34145
+ fields: {
34146
+ promptLength: promptSummary.length,
34147
+ promptHash: promptSummary.sha256Prefix,
34148
+ assistantResponseLength: assistantResponseSummary.length,
34149
+ assistantResponseHash: assistantResponseSummary.sha256Prefix
34150
+ }
34151
+ });
33752
34152
  if (!prompt || !assistantResponse) {
33753
34153
  await markPendingTurnFailure(sessionId, {
33754
- message: "Automatic Remix turn recording failed because the prompt or assistant response was missing."
34154
+ message: "Fallback Remix turn recording failed because the prompt or assistant response was missing."
34155
+ });
34156
+ await appendHookDiagnosticsEvent({
34157
+ hook,
34158
+ sessionId,
34159
+ turnId: state.turnId,
34160
+ stage: "response_validation",
34161
+ result: "error",
34162
+ reason: !prompt ? "missing_prompt" : "missing_assistant_response"
33755
34163
  });
33756
34164
  return;
33757
34165
  }
33758
34166
  const api = await createHookCollabApiClient();
34167
+ await appendHookDiagnosticsEvent({
34168
+ hook,
34169
+ sessionId,
34170
+ turnId: state.turnId,
34171
+ stage: "api_client_created",
34172
+ result: "success",
34173
+ fields: {
34174
+ touchedRepoCount: touchedRepos.length
34175
+ }
34176
+ });
33759
34177
  let hadFailure = false;
33760
34178
  for (const repo of touchedRepos) {
33761
34179
  if (shouldSkipStopRecording(repo)) {
34180
+ await appendHookDiagnosticsEvent({
34181
+ hook,
34182
+ sessionId,
34183
+ turnId: state.turnId,
34184
+ stage: "recording_skipped",
34185
+ result: "info",
34186
+ reason: "skip_stop_recording",
34187
+ repoRoot: repo.repoRoot,
34188
+ fields: {
34189
+ manuallyRecorded: repo.manuallyRecorded,
34190
+ stopRecorded: repo.stopRecorded
34191
+ }
34192
+ });
33762
34193
  continue;
33763
34194
  }
33764
34195
  const recorded = await recordTouchedRepo({
34196
+ hook,
33765
34197
  sessionId,
33766
34198
  turnId: state.turnId,
33767
34199
  repo,
@@ -33775,16 +34207,60 @@ async function main() {
33775
34207
  }
33776
34208
  if (!hadFailure) {
33777
34209
  await clearPendingTurnState(sessionId);
34210
+ await appendHookDiagnosticsEvent({
34211
+ hook,
34212
+ sessionId,
34213
+ turnId: state.turnId,
34214
+ stage: "state_cleanup",
34215
+ result: "success",
34216
+ reason: "cleared_after_success"
34217
+ });
34218
+ return;
33778
34219
  }
34220
+ await appendHookDiagnosticsEvent({
34221
+ hook,
34222
+ sessionId,
34223
+ turnId: state.turnId,
34224
+ stage: "state_cleanup",
34225
+ result: "info",
34226
+ reason: "retained_after_failure"
34227
+ });
33779
34228
  } catch (error) {
33780
34229
  const details = getErrorDetails(error);
33781
34230
  await markPendingTurnFailure(sessionId, details);
34231
+ await appendHookDiagnosticsEvent({
34232
+ hook,
34233
+ sessionId,
34234
+ turnId: state.turnId,
34235
+ stage: "turn_failed",
34236
+ result: "error",
34237
+ reason: "exception",
34238
+ message: details.message,
34239
+ fields: {
34240
+ hint: details.hint
34241
+ }
34242
+ });
33782
34243
  }
33783
34244
  }
34245
+ async function main() {
34246
+ const payload = await readJsonStdin();
34247
+ await runHookStopCollab(payload);
34248
+ }
33784
34249
  main().catch((error) => {
33785
34250
  const message = error instanceof Error ? error.message : String(error);
34251
+ void appendHookDiagnosticsEvent({
34252
+ hook: "Stop",
34253
+ stage: "unhandled_error",
34254
+ result: "error",
34255
+ reason: "exception",
34256
+ message
34257
+ });
33786
34258
  process.stderr.write(`${message}
33787
34259
  `);
33788
34260
  process.exitCode = 0;
33789
34261
  });
34262
+ // Annotate the CommonJS export names for ESM import in node:
34263
+ 0 && (module.exports = {
34264
+ runHookStopCollab
34265
+ });
33790
34266
  //# sourceMappingURL=hook-stop-collab.cjs.map