@comergehq/cli 0.1.9 → 0.1.10

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 (3) hide show
  1. package/dist/cli.js +1032 -248
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -2,61 +2,17 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { Command } from "commander";
5
- import pc10 from "picocolors";
5
+ import pc12 from "picocolors";
6
6
  import { createRequire } from "module";
7
7
 
8
8
  // src/commands/login.ts
9
9
  import pc from "picocolors";
10
10
 
11
11
  // src/errors/cliError.ts
12
- var CliError = class extends Error {
13
- exitCode;
14
- hint;
15
- constructor(message, opts) {
16
- super(message);
17
- this.name = "CliError";
18
- this.exitCode = opts?.exitCode ?? 1;
19
- this.hint = opts?.hint ?? null;
20
- }
21
- };
12
+ import { CliError, ComergeError } from "@comergehq/core/errors";
22
13
 
23
14
  // src/config/config.ts
24
- import { z } from "zod";
25
- var API_URL = "https://api.remix.one";
26
- var SUPABASE_URL = "https://xtfxwbckjpfmqubnsusu.supabase.co";
27
- var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh0Znh3YmNranBmbXF1Ym5zdXN1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjA2MDEyMzAsImV4cCI6MjA3NjE3NzIzMH0.dzWGAWrK4CvrmHVHzf8w7JlUZohdap0ZPnLZnABMV8s";
28
- function isValidUrl(value) {
29
- try {
30
- new URL(value);
31
- return true;
32
- } catch {
33
- return false;
34
- }
35
- }
36
- var urlSchema = z.string().min(1).transform((s) => s.trim()).refine((s) => isValidUrl(s), "Invalid URL.").transform((s) => s.replace(/\/+$/, ""));
37
- var configSchema = z.object({
38
- apiUrl: urlSchema,
39
- supabaseUrl: urlSchema,
40
- supabaseAnonKey: z.string().min(1)
41
- });
42
- var cachedConfig = null;
43
- async function resolveConfig(_opts) {
44
- if (cachedConfig) return cachedConfig;
45
- const cfgRaw = {
46
- apiUrl: API_URL,
47
- supabaseUrl: SUPABASE_URL,
48
- supabaseAnonKey: SUPABASE_ANON_KEY
49
- };
50
- const parsedCfg = configSchema.safeParse(cfgRaw);
51
- if (!parsedCfg.success) {
52
- throw new CliError("Invalid CLI configuration.", {
53
- exitCode: 1,
54
- hint: parsedCfg.error.issues.map((i) => `${i.path.join(".") || "config"}: ${i.message}`).join("; ")
55
- });
56
- }
57
- cachedConfig = parsedCfg.data;
58
- return parsedCfg.data;
59
- }
15
+ import { configSchema, resolveConfig } from "@comergehq/core/config";
60
16
 
61
17
  // src/services/oauthCallbackServer.ts
62
18
  import http from "http";
@@ -172,90 +128,13 @@ ${err?.message ?? String(err)}`
172
128
  }
173
129
 
174
130
  // src/clients/supabase.ts
175
- import { createClient } from "@supabase/supabase-js";
176
- function createInMemoryStorage() {
177
- const map = /* @__PURE__ */ new Map();
178
- return {
179
- getItem: (k) => map.get(k) ?? null,
180
- setItem: (k, v) => {
181
- map.set(k, v);
182
- },
183
- removeItem: (k) => {
184
- map.delete(k);
185
- }
186
- };
187
- }
188
- function createSupabaseClient(config, storage) {
189
- return createClient(config.supabaseUrl, config.supabaseAnonKey, {
190
- auth: {
191
- flowType: "pkce",
192
- persistSession: false,
193
- autoRefreshToken: false,
194
- detectSessionInUrl: false,
195
- storage
196
- },
197
- global: {
198
- headers: {
199
- "X-Requested-By": "comerge-cli"
200
- }
201
- }
202
- });
203
- }
204
- function toStoredSession(session) {
205
- if (!session.access_token || !session.refresh_token || !session.expires_at) {
206
- throw new CliError("Supabase session is missing required fields.", { exitCode: 1 });
207
- }
208
- return {
209
- access_token: session.access_token,
210
- refresh_token: session.refresh_token,
211
- expires_at: session.expires_at,
212
- token_type: session.token_type ?? void 0,
213
- user: session.user ? { id: session.user.id, email: session.user.email ?? null } : void 0
214
- };
215
- }
216
- function createSupabaseAuthHelpers(config) {
217
- const storage = createInMemoryStorage();
218
- const supabase = createSupabaseClient(config, storage);
219
- return {
220
- async startGoogleLogin(params) {
221
- const { data, error } = await supabase.auth.signInWithOAuth({
222
- provider: "google",
223
- options: {
224
- redirectTo: params.redirectTo,
225
- skipBrowserRedirect: true,
226
- queryParams: {
227
- access_type: "offline",
228
- prompt: "consent"
229
- }
230
- }
231
- });
232
- if (error) throw error;
233
- if (!data?.url) throw new CliError("Supabase did not return an OAuth URL.", { exitCode: 1 });
234
- return { url: data.url };
235
- },
236
- async exchangeCode(params) {
237
- const { data, error } = await supabase.auth.exchangeCodeForSession(params.code);
238
- if (error) throw error;
239
- if (!data?.session) throw new CliError("Supabase did not return a session.", { exitCode: 1 });
240
- return toStoredSession(data.session);
241
- },
242
- async refreshWithStoredSession(params) {
243
- const { data, error } = await supabase.auth.setSession({
244
- access_token: params.session.access_token,
245
- refresh_token: params.session.refresh_token
246
- });
247
- if (error) throw error;
248
- if (!data?.session) throw new CliError("No session returned after refresh.", { exitCode: 1 });
249
- return toStoredSession(data.session);
250
- }
251
- };
252
- }
131
+ import { createSupabaseAuthHelpers } from "@comergehq/core/auth";
253
132
 
254
133
  // src/services/sessionStore.ts
255
134
  import fs2 from "fs/promises";
256
135
  import os from "os";
257
136
  import path2 from "path";
258
- import { z as z2 } from "zod";
137
+ import { storedSessionSchema } from "@comergehq/core/auth";
259
138
 
260
139
  // src/utils/fs.ts
261
140
  import fs from "fs/promises";
@@ -301,16 +180,6 @@ async function writeTextAtomic(filePath, contents) {
301
180
  }
302
181
 
303
182
  // src/services/sessionStore.ts
304
- var storedSessionSchema = z2.object({
305
- access_token: z2.string().min(1),
306
- refresh_token: z2.string().min(1),
307
- expires_at: z2.number().int().positive(),
308
- token_type: z2.string().min(1).optional(),
309
- user: z2.object({
310
- id: z2.string().min(1),
311
- email: z2.string().email().optional().nullable()
312
- }).optional()
313
- });
314
183
  var KEYTAR_SERVICE = "comerge-cli";
315
184
  var KEYTAR_ACCOUNT = "default";
316
185
  function xdgConfigHome() {
@@ -347,6 +216,11 @@ async function ensurePathPermissions(filePath) {
347
216
  } catch {
348
217
  }
349
218
  }
219
+ async function writeSessionFileFallback(session) {
220
+ const fp = sessionFilePath();
221
+ await writeJsonAtomic(fp, session);
222
+ await ensurePathPermissions(fp);
223
+ }
350
224
  async function getSession() {
351
225
  const keytar = await maybeLoadKeytar();
352
226
  if (keytar) {
@@ -380,17 +254,13 @@ async function setSession(session) {
380
254
  const keytar = await maybeLoadKeytar();
381
255
  if (keytar) {
382
256
  await keytar.setPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT, JSON.stringify(parsed.data));
383
- return;
384
257
  }
385
- const fp = sessionFilePath();
386
- await writeJsonAtomic(fp, parsed.data);
387
- await ensurePathPermissions(fp);
258
+ await writeSessionFileFallback(parsed.data);
388
259
  }
389
260
  async function clearSession() {
390
261
  const keytar = await maybeLoadKeytar();
391
262
  if (keytar) {
392
263
  await keytar.deletePassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);
393
- return;
394
264
  }
395
265
  const fp = sessionFilePath();
396
266
  await fs2.rm(fp, { force: true }).catch(() => {
@@ -398,110 +268,24 @@ async function clearSession() {
398
268
  }
399
269
 
400
270
  // src/clients/api.ts
401
- function shouldRefreshSoon(session, skewSeconds = 60) {
402
- const nowSec = Math.floor(Date.now() / 1e3);
403
- return session.expires_at <= nowSec + skewSeconds;
404
- }
405
- async function readJsonSafe(res) {
406
- const ct = res.headers.get("content-type") ?? "";
407
- if (!ct.toLowerCase().includes("application/json")) return null;
408
- try {
409
- return await res.json();
410
- } catch {
411
- return null;
412
- }
413
- }
414
- async function getAuthToken(config) {
415
- const envToken = process.env.COMERGE_ACCESS_TOKEN;
416
- if (typeof envToken === "string" && envToken.trim().length > 0) {
417
- return { token: envToken.trim(), session: null, fromEnv: true };
418
- }
419
- let session = await getSession();
420
- if (!session) {
421
- throw new CliError("Not signed in.", {
422
- exitCode: 2,
423
- hint: "Run `comerge login`, or set COMERGE_ACCESS_TOKEN for CI."
424
- });
425
- }
426
- if (shouldRefreshSoon(session)) {
427
- try {
428
- const supabase = createSupabaseAuthHelpers(config);
429
- session = await supabase.refreshWithStoredSession({ session });
430
- await setSession(session);
431
- } catch (err) {
432
- void err;
433
- }
434
- }
435
- return { token: session.access_token, session, fromEnv: false };
436
- }
271
+ import {
272
+ createApiClient as createCoreApiClient,
273
+ createStoredSessionTokenProvider,
274
+ createSupabaseAuthHelpers as createSupabaseAuthHelpers2
275
+ } from "@comergehq/core";
437
276
  function createApiClient(config, opts) {
438
- const cfg = config;
439
- const apiKey = (opts?.apiKey ?? "").trim();
440
- const CLIENT_KEY_HEADER = "x-comerge-api-key";
441
- async function request(path13, init) {
442
- const { token, session, fromEnv } = await getAuthToken(cfg);
443
- const url = new URL(path13, cfg.apiUrl).toString();
444
- const doFetch = async (bearer) => {
445
- const res2 = await fetch(url, {
446
- ...init,
447
- headers: {
448
- Accept: "application/json",
449
- "Content-Type": "application/json",
450
- ...init?.headers ?? {},
451
- Authorization: `Bearer ${bearer}`,
452
- ...apiKey ? { [CLIENT_KEY_HEADER]: apiKey } : {}
453
- }
454
- });
455
- return res2;
456
- };
457
- let res = await doFetch(token);
458
- if (res.status === 401 && !fromEnv && session?.refresh_token) {
459
- const supabase = createSupabaseAuthHelpers(cfg);
460
- const refreshed = await supabase.refreshWithStoredSession({ session });
461
- await setSession(refreshed);
462
- res = await doFetch(refreshed.access_token);
277
+ const tokenProvider = createStoredSessionTokenProvider({
278
+ config,
279
+ sessionStore: { getSession, setSession },
280
+ refreshStoredSession: async ({ config: refreshConfig, session }) => {
281
+ const supabase = createSupabaseAuthHelpers2(refreshConfig);
282
+ return supabase.refreshWithStoredSession({ session });
463
283
  }
464
- if (!res.ok) {
465
- const body = await readJsonSafe(res);
466
- const msg = (body && typeof body === "object" && body && "message" in body && typeof body.message === "string" ? body.message : null) ?? `Request failed (status ${res.status})`;
467
- throw new CliError(msg, { exitCode: 1, hint: body ? JSON.stringify(body, null, 2) : null });
468
- }
469
- const json = await readJsonSafe(res);
470
- return json ?? null;
471
- }
472
- return {
473
- getMe: () => request("/v1/me", { method: "GET" }),
474
- listOrganizations: () => request("/v1/organizations", { method: "GET" }),
475
- autoEnableDeveloper: () => request("/v1/developer/auto-enable", { method: "POST" }),
476
- listClientApps: (params) => {
477
- const qs = params?.orgId ? `?orgId=${encodeURIComponent(params.orgId)}` : "";
478
- return request(`/v1/developer/client-apps${qs}`, { method: "GET" });
479
- },
480
- createClientApp: (payload) => request("/v1/developer/client-apps", { method: "POST", body: JSON.stringify(payload) }),
481
- createClientAppKey: (clientAppId, payload) => request(`/v1/developer/client-apps/${encodeURIComponent(clientAppId)}/keys`, {
482
- method: "POST",
483
- body: JSON.stringify(payload ?? {})
484
- }),
485
- getApp: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}`, { method: "GET" }),
486
- presignImportUpload: (payload) => request("/v1/apps/import/upload/presign", { method: "POST", body: JSON.stringify(payload) }),
487
- importFromUpload: (payload) => request("/v1/apps/import/upload", { method: "POST", body: JSON.stringify(payload) }),
488
- initiateBundle: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/bundles`, { method: "POST", body: JSON.stringify(payload) }),
489
- getBundle: (appId, bundleId) => request(`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}`, { method: "GET" }),
490
- getBundleDownloadUrl: (appId, bundleId, options) => request(
491
- `/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/download?redirect=${options?.redirect ?? false}`,
492
- { method: "GET" }
493
- ),
494
- getBundleAssetsDownloadUrl: (appId, bundleId, options) => {
495
- const qs = new URLSearchParams({
496
- redirect: String(options?.redirect ?? false),
497
- kind: options?.kind ?? "metro-assets"
498
- });
499
- return request(
500
- `/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/assets/download?${qs.toString()}`,
501
- { method: "GET" }
502
- );
503
- }
504
- };
284
+ });
285
+ return createCoreApiClient(config, {
286
+ apiKey: opts?.apiKey,
287
+ tokenProvider
288
+ });
505
289
  }
506
290
 
507
291
  // src/commands/login.ts
@@ -1478,9 +1262,11 @@ function isRetryableNetworkError(e) {
1478
1262
  const err = e;
1479
1263
  const code = typeof err?.code === "string" ? err.code : "";
1480
1264
  const message = typeof err?.message === "string" ? err.message : "";
1481
- if (code === "ERR_NETWORK" || code === "ECONNABORTED" || code === "ETIMEDOUT") return true;
1265
+ if (code === "ERR_NETWORK" || code === "ECONNABORTED") return true;
1482
1266
  if (message.toLowerCase().includes("network error")) return true;
1483
1267
  if (message.toLowerCase().includes("timeout")) return true;
1268
+ const status = typeof err?.response?.status === "number" ? err.response.status : void 0;
1269
+ if (status && (status === 429 || status >= 500)) return true;
1484
1270
  return false;
1485
1271
  }
1486
1272
  async function withRetry(fn, opts) {
@@ -2236,6 +2022,1003 @@ function registerInitCommands(program) {
2236
2022
  });
2237
2023
  }
2238
2024
 
2025
+ // src/services/collab.ts
2026
+ import fs15 from "fs/promises";
2027
+ import path13 from "path";
2028
+ import pc10 from "picocolors";
2029
+ import {
2030
+ collabAdd as collabAddCore,
2031
+ collabRecordTurn as collabRecordTurnCore,
2032
+ collabApprove as collabApproveCore,
2033
+ collabInbox as collabInboxCore,
2034
+ collabInit as collabInitCore,
2035
+ collabInvite as collabInviteCore,
2036
+ collabList as collabListCore,
2037
+ collabReconcile as collabReconcileCore,
2038
+ collabReject as collabRejectCore,
2039
+ collabRemix as collabRemixCore,
2040
+ collabRequestMerge as collabRequestMergeCore,
2041
+ collabStatus as collabStatusCore,
2042
+ collabSync as collabSyncCore,
2043
+ collabSyncUpstream as collabSyncUpstreamCore,
2044
+ collabView as collabViewCore
2045
+ } from "@comergehq/core/collab";
2046
+ async function readStdin() {
2047
+ const chunks = [];
2048
+ for await (const chunk of process.stdin) {
2049
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
2050
+ }
2051
+ return Buffer.concat(chunks).toString("utf8");
2052
+ }
2053
+ function logMaybe(json, message) {
2054
+ if (!json) console.log(message);
2055
+ }
2056
+ function collectResultWarnings(value) {
2057
+ if (!value || typeof value !== "object") return [];
2058
+ const warnings = value.warnings;
2059
+ if (!Array.isArray(warnings)) return [];
2060
+ return warnings.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
2061
+ }
2062
+ function printResultWarnings(json, value) {
2063
+ if (json) return;
2064
+ for (const warning of collectResultWarnings(value)) {
2065
+ console.log(pc10.yellow(warning));
2066
+ }
2067
+ }
2068
+ async function createCollabApi(params) {
2069
+ await ensureAuth({ yes: params.yes, json: params.json });
2070
+ const cfg = await resolveConfig({ yes: params.yes });
2071
+ return createApiClient(cfg);
2072
+ }
2073
+ async function resolveBoundCollabConfig(cwd) {
2074
+ let current = path13.resolve(cwd);
2075
+ while (true) {
2076
+ const candidate = path13.join(current, ".comerge", "config.json");
2077
+ try {
2078
+ const raw = await fs15.readFile(candidate, "utf8");
2079
+ const parsed = JSON.parse(raw);
2080
+ if (parsed.schemaVersion === 1 && parsed.projectId && parsed.currentAppId) {
2081
+ return {
2082
+ schemaVersion: 1,
2083
+ projectId: parsed.projectId,
2084
+ currentAppId: parsed.currentAppId
2085
+ };
2086
+ }
2087
+ } catch {
2088
+ }
2089
+ const parent = path13.dirname(current);
2090
+ if (parent === current) return null;
2091
+ current = parent;
2092
+ }
2093
+ }
2094
+ async function resolveInviteTargetId(params) {
2095
+ if (params.targetId?.trim()) return params.targetId.trim();
2096
+ const binding = await resolveBoundCollabConfig(params.cwd);
2097
+ if (!binding) {
2098
+ throw new CliError("Repository is not bound to Comerge and no explicit target id was provided.", { exitCode: 2 });
2099
+ }
2100
+ if (params.scope === "project") return binding.projectId;
2101
+ if (params.scope === "app") return binding.currentAppId;
2102
+ const project = await params.api.getProject(binding.projectId);
2103
+ const organizationId = project.responseObject?.organizationId ?? null;
2104
+ if (!organizationId) {
2105
+ throw new CliError("Could not resolve the organization for the current repository binding.", { exitCode: 2 });
2106
+ }
2107
+ return organizationId;
2108
+ }
2109
+ async function runInviteList(params) {
2110
+ const response = params.scope === "organization" ? await params.api.listOrganizationInvites(params.targetId) : params.scope === "project" ? await params.api.listProjectInvites(params.targetId) : await params.api.listAppInvites(params.targetId);
2111
+ return response.responseObject ?? response;
2112
+ }
2113
+ async function runInviteResend(params) {
2114
+ const response = params.scope === "organization" ? await params.api.resendOrganizationInvite(params.targetId, params.inviteId, { ttlDays: params.ttlDays }) : params.scope === "project" ? await params.api.resendProjectInvite(params.targetId, params.inviteId, { ttlDays: params.ttlDays }) : await params.api.resendAppInvite(params.targetId, params.inviteId, { ttlDays: params.ttlDays });
2115
+ return response.responseObject ?? response;
2116
+ }
2117
+ async function runInviteRevoke(params) {
2118
+ const response = params.scope === "organization" ? await params.api.revokeOrganizationInvite(params.targetId, params.inviteId) : params.scope === "project" ? await params.api.revokeProjectInvite(params.targetId, params.inviteId) : await params.api.revokeAppInvite(params.targetId, params.inviteId);
2119
+ return response.responseObject ?? response;
2120
+ }
2121
+ async function collabInit(params) {
2122
+ const api = await createCollabApi(params);
2123
+ const result = await collabInitCore({
2124
+ api,
2125
+ cwd: params.cwd,
2126
+ appName: params.appName,
2127
+ forceNew: params.forceNew
2128
+ });
2129
+ if (!params.json) {
2130
+ if (result.reused) {
2131
+ console.log(pc10.green(`Bound existing Comerge project. appId=${result.appId}`));
2132
+ } else {
2133
+ console.log(pc10.green(`Initialized repository in Comerge. appId=${result.appId}`));
2134
+ }
2135
+ printResultWarnings(params.json, result);
2136
+ }
2137
+ return result;
2138
+ }
2139
+ async function collabList(params) {
2140
+ const api = await createCollabApi(params);
2141
+ const result = await collabListCore({ api });
2142
+ if (!params.json) {
2143
+ for (const app of result.apps) {
2144
+ console.log(`${app.id} ${app.name} status=${app.status} project=${app.projectId}${app.forkedFromAppId ? ` forkOf=${app.forkedFromAppId}` : ""}`);
2145
+ }
2146
+ }
2147
+ return result;
2148
+ }
2149
+ async function collabStatus(params) {
2150
+ const localStatus = await collabStatusCore({
2151
+ api: null,
2152
+ cwd: params.cwd
2153
+ });
2154
+ let result = localStatus;
2155
+ if (localStatus.repo.isGitRepo && localStatus.binding.isBound) {
2156
+ try {
2157
+ result = await collabStatusCore({
2158
+ api: await createCollabApi(params),
2159
+ cwd: params.cwd
2160
+ });
2161
+ } catch (err) {
2162
+ const detail = err instanceof CliError ? [err.message, err.hint].filter(Boolean).join("\n\n") || "Remote collab checks are unavailable." : err instanceof Error ? err.message || "Remote collab checks are unavailable." : "Remote collab checks are unavailable.";
2163
+ result = {
2164
+ ...localStatus,
2165
+ remote: {
2166
+ ...localStatus.remote,
2167
+ error: detail
2168
+ },
2169
+ sync: {
2170
+ ...localStatus.sync,
2171
+ error: detail,
2172
+ blockedReasons: Array.from(/* @__PURE__ */ new Set([...localStatus.sync.blockedReasons, "remote_error"]))
2173
+ },
2174
+ reconcile: {
2175
+ ...localStatus.reconcile,
2176
+ error: detail,
2177
+ blockedReasons: Array.from(/* @__PURE__ */ new Set([...localStatus.reconcile.blockedReasons, "remote_error"]))
2178
+ },
2179
+ recommendedAction: "no_action",
2180
+ warnings: Array.from(new Set([...localStatus.warnings, detail].filter(Boolean)))
2181
+ };
2182
+ }
2183
+ }
2184
+ if (!params.json) {
2185
+ printCollabStatus(result);
2186
+ }
2187
+ return result;
2188
+ }
2189
+ function printCollabStatus(result) {
2190
+ if (!result.repo.isGitRepo) {
2191
+ console.log(pc10.yellow("Repository: not inside a git repository"));
2192
+ return;
2193
+ }
2194
+ console.log(pc10.bold("Repository"));
2195
+ console.log(`${result.repo.repoRoot}`);
2196
+ console.log(`branch=${result.repo.branch ?? "(detached)"}`);
2197
+ console.log(`branchMismatch=${result.repo.branchMismatch ? "yes" : "no"}`);
2198
+ console.log(`head=${result.repo.headCommitHash ?? "unknown"}`);
2199
+ console.log(
2200
+ `worktree=${result.repo.worktree.isClean ? "clean" : "dirty"} entries=${result.repo.worktree.entryCount} tracked=${result.repo.worktree.hasTrackedChanges ? "yes" : "no"} untracked=${result.repo.worktree.hasUntrackedFiles ? "yes" : "no"}`
2201
+ );
2202
+ console.log("");
2203
+ console.log(pc10.bold("Binding"));
2204
+ if (!result.binding.isBound) {
2205
+ console.log("bound=no");
2206
+ } else {
2207
+ console.log(`bound=yes appId=${result.binding.currentAppId} projectId=${result.binding.projectId}`);
2208
+ console.log(`remix=${result.binding.isRemix ? "yes" : "no"} upstreamAppId=${result.binding.upstreamAppId}`);
2209
+ console.log(`preferredBranch=${result.binding.preferredBranch ?? "(unset)"} defaultBranch=${result.binding.defaultBranch ?? "(unknown)"}`);
2210
+ }
2211
+ console.log("");
2212
+ console.log(pc10.bold("Remote"));
2213
+ console.log(`appStatus=${result.remote.appStatus ?? "unknown"}`);
2214
+ console.log(
2215
+ `incomingOpenMergeRequests=${result.remote.incomingOpenMergeRequestCount ?? "unknown"} outgoingOpenMergeRequests=${result.remote.outgoingOpenMergeRequestCount ?? "unknown"}`
2216
+ );
2217
+ console.log("");
2218
+ console.log(pc10.bold("Preflight"));
2219
+ console.log(
2220
+ `sync.status=${result.sync.status} canApply=${result.sync.canApply ? "yes" : "no"} blocked=${result.sync.blockedReasons.join(",") || "none"}`
2221
+ );
2222
+ console.log(
2223
+ `reconcile.status=${result.reconcile.status} canApply=${result.reconcile.canApply ? "yes" : "no"} blocked=${result.reconcile.blockedReasons.join(",") || "none"}`
2224
+ );
2225
+ console.log("");
2226
+ console.log(pc10.bold("Recommendation"));
2227
+ console.log(result.recommendedAction);
2228
+ if (result.warnings.length > 0) {
2229
+ console.log("");
2230
+ console.log(pc10.bold("Warnings"));
2231
+ for (const warning of result.warnings) {
2232
+ console.log(`- ${warning}`);
2233
+ }
2234
+ }
2235
+ }
2236
+ async function collabRemix(params) {
2237
+ const api = await createCollabApi(params);
2238
+ const result = await collabRemixCore({
2239
+ api,
2240
+ cwd: params.cwd,
2241
+ outputDir: params.outputDir ?? null,
2242
+ appId: params.appId,
2243
+ name: params.name
2244
+ });
2245
+ if (!params.json) console.log(pc10.green(`Created remix. appId=${result.appId} path=${result.repoRoot}`));
2246
+ return result;
2247
+ }
2248
+ async function collabAdd(params) {
2249
+ if (params.promptStdin && params.diffStdin) {
2250
+ throw new CliError("Cannot read both prompt and diff from stdin in one invocation.", {
2251
+ exitCode: 2,
2252
+ hint: "Use `--prompt-file` or `--diff-file` for one of the inputs."
2253
+ });
2254
+ }
2255
+ let prompt = params.prompt?.trim() || "";
2256
+ if (params.promptFile) prompt = (await fs15.readFile(path13.resolve(params.promptFile), "utf8")).trim();
2257
+ if (params.promptStdin) prompt = (await readStdin()).trim();
2258
+ if (!prompt) throw new CliError("Prompt is required.", { exitCode: 2 });
2259
+ let assistantResponse = params.assistantResponse?.trim() || "";
2260
+ if (params.assistantResponseFile) {
2261
+ assistantResponse = (await fs15.readFile(path13.resolve(params.assistantResponseFile), "utf8")).trim();
2262
+ }
2263
+ let diff = null;
2264
+ let diffSource = "worktree";
2265
+ if (params.diffFile) {
2266
+ diff = await fs15.readFile(path13.resolve(params.diffFile), "utf8");
2267
+ diffSource = "external";
2268
+ } else if (params.diffStdin) {
2269
+ diff = await readStdin();
2270
+ diffSource = "external";
2271
+ }
2272
+ const api = await createCollabApi(params);
2273
+ const result = await collabAddCore({
2274
+ api,
2275
+ cwd: params.cwd,
2276
+ prompt,
2277
+ assistantResponse: assistantResponse || null,
2278
+ diff,
2279
+ diffSource,
2280
+ sync: params.sync,
2281
+ allowBranchMismatch: params.allowBranchMismatch,
2282
+ idempotencyKey: params.idempotencyKey,
2283
+ actor: {
2284
+ type: process.env.COMERGE_AGENT_TYPE || "agent",
2285
+ name: process.env.COMERGE_AGENT_NAME || void 0,
2286
+ version: process.env.COMERGE_AGENT_VERSION || void 0,
2287
+ provider: process.env.COMERGE_AGENT_PROVIDER || void 0
2288
+ }
2289
+ });
2290
+ if (!params.json) {
2291
+ console.log(pc10.green(`Recorded change step. id=${result.id}`));
2292
+ if (params.sync !== false && diffSource === "external") {
2293
+ console.log(
2294
+ pc10.yellow(
2295
+ "Skipping automatic local discard+sync because the submitted diff came from --diff-file/--diff-stdin and may not match the current worktree."
2296
+ )
2297
+ );
2298
+ }
2299
+ printResultWarnings(params.json, result);
2300
+ }
2301
+ return result;
2302
+ }
2303
+ async function collabRecordTurn(params) {
2304
+ let prompt = params.prompt?.trim() || "";
2305
+ if (params.promptFile) prompt = (await fs15.readFile(path13.resolve(params.promptFile), "utf8")).trim();
2306
+ if (params.promptStdin) prompt = (await readStdin()).trim();
2307
+ if (!prompt) throw new CliError("Prompt is required.", { exitCode: 2 });
2308
+ let assistantResponse = params.assistantResponse?.trim() || "";
2309
+ if (params.assistantResponseFile) {
2310
+ assistantResponse = (await fs15.readFile(path13.resolve(params.assistantResponseFile), "utf8")).trim();
2311
+ }
2312
+ if (!assistantResponse) {
2313
+ throw new CliError("Assistant response is required.", {
2314
+ exitCode: 2,
2315
+ hint: "Pass `--assistant-response` or `--assistant-response-file`."
2316
+ });
2317
+ }
2318
+ const api = await createCollabApi(params);
2319
+ const result = await collabRecordTurnCore({
2320
+ api,
2321
+ cwd: params.cwd,
2322
+ prompt,
2323
+ assistantResponse,
2324
+ allowBranchMismatch: params.allowBranchMismatch,
2325
+ idempotencyKey: params.idempotencyKey,
2326
+ actor: {
2327
+ type: process.env.COMERGE_AGENT_TYPE || "agent",
2328
+ name: process.env.COMERGE_AGENT_NAME || void 0,
2329
+ version: process.env.COMERGE_AGENT_VERSION || void 0,
2330
+ provider: process.env.COMERGE_AGENT_PROVIDER || void 0
2331
+ }
2332
+ });
2333
+ if (!params.json) {
2334
+ console.log(pc10.green(`Recorded collaboration turn. id=${result.id}`));
2335
+ }
2336
+ return result;
2337
+ }
2338
+ async function collabSync(params) {
2339
+ const api = await createCollabApi(params);
2340
+ const result = await collabSyncCore({
2341
+ api,
2342
+ cwd: params.cwd,
2343
+ dryRun: params.dryRun,
2344
+ allowBranchMismatch: params.allowBranchMismatch
2345
+ });
2346
+ if (!params.json) {
2347
+ if (result.status === "up_to_date") {
2348
+ logMaybe(params.json, pc10.green(`Already in sync. head=${result.targetCommitHash}`));
2349
+ } else if (result.dryRun) {
2350
+ logMaybe(
2351
+ params.json,
2352
+ pc10.green(
2353
+ `Fast-forward preview ready. target=${result.targetCommitHash} files=${result.stats.changedFilesCount} +${result.stats.insertions} -${result.stats.deletions}`
2354
+ )
2355
+ );
2356
+ } else if (result.applied) {
2357
+ logMaybe(
2358
+ params.json,
2359
+ pc10.green(
2360
+ `Fast-forward synced local repository. branch=${result.branch} head=${result.localCommitHash} files=${result.stats.changedFilesCount}`
2361
+ )
2362
+ );
2363
+ }
2364
+ printResultWarnings(params.json, result);
2365
+ }
2366
+ return result;
2367
+ }
2368
+ async function collabSyncUpstream(params) {
2369
+ const api = await createCollabApi(params);
2370
+ const result = await collabSyncUpstreamCore({
2371
+ api,
2372
+ cwd: params.cwd
2373
+ });
2374
+ if (!params.json) {
2375
+ if (result.status === "up-to-date") {
2376
+ logMaybe(params.json, pc10.green("Upstream is already synced for the current remix."));
2377
+ } else {
2378
+ logMaybe(
2379
+ params.json,
2380
+ pc10.green(
2381
+ `Upstream sync completed for remix. appId=${result.appId} mergeRequestId=${result.mergeRequestId ?? "unknown"}`
2382
+ )
2383
+ );
2384
+ }
2385
+ printResultWarnings(params.json, result);
2386
+ }
2387
+ return result;
2388
+ }
2389
+ async function collabReconcile(params) {
2390
+ const api = await createCollabApi(params);
2391
+ const result = await collabReconcileCore({
2392
+ api,
2393
+ cwd: params.cwd,
2394
+ dryRun: params.dryRun,
2395
+ allowBranchMismatch: params.allowBranchMismatch
2396
+ });
2397
+ if (!params.json) {
2398
+ if (result.status === "ready_to_reconcile" && result.dryRun) {
2399
+ logMaybe(params.json, pc10.green(`Reconcile preview ready. target=${result.targetHeadCommitHash} local=${result.localHeadCommitHash}`));
2400
+ } else if (result.status === "succeeded" && result.applied) {
2401
+ logMaybe(
2402
+ params.json,
2403
+ pc10.green(
2404
+ `Reconciled local repository. branch=${result.branch} head=${result.localCommitHash} backup=${result.backupBranchName}`
2405
+ )
2406
+ );
2407
+ } else if (result.targetCommitHash && !result.dryRun && result.applied) {
2408
+ logMaybe(
2409
+ params.json,
2410
+ pc10.green(
2411
+ `Fast-forward synced local repository. branch=${result.branch} head=${result.localCommitHash} files=${result.stats.changedFilesCount}`
2412
+ )
2413
+ );
2414
+ }
2415
+ printResultWarnings(params.json, result);
2416
+ }
2417
+ return result;
2418
+ }
2419
+ async function collabRequestMerge(params) {
2420
+ const api = await createCollabApi(params);
2421
+ const result = await collabRequestMergeCore({
2422
+ api,
2423
+ cwd: params.cwd
2424
+ });
2425
+ if (!params.json) console.log(pc10.green(`Opened merge request. id=${result.id}`));
2426
+ return result;
2427
+ }
2428
+ async function collabInbox(params) {
2429
+ const api = await createCollabApi(params);
2430
+ const result = await collabInboxCore({ api });
2431
+ if (!params.json) {
2432
+ const items = Array.isArray(result.mergeRequests) ? result.mergeRequests : result.mergeRequests ? Object.values(result.mergeRequests).flat() : [];
2433
+ for (const mr of items) {
2434
+ console.log(`${mr.id} ${mr.status} ${mr.title ?? "(untitled)"}`);
2435
+ }
2436
+ }
2437
+ return result;
2438
+ }
2439
+ async function collabView(params) {
2440
+ const api = await createCollabApi(params);
2441
+ const result = await collabViewCore({
2442
+ api,
2443
+ mrId: params.mrId
2444
+ });
2445
+ if (!params.json) {
2446
+ console.log(pc10.bold(`${result.mergeRequest?.title ?? "Merge request"} (${result.mergeRequest?.status ?? "unknown"})`));
2447
+ console.log(result.unifiedDiff ?? "");
2448
+ }
2449
+ return result;
2450
+ }
2451
+ async function collabApprove(params) {
2452
+ const mode = params.remoteOnly === params.syncTargetRepo ? null : params.remoteOnly ? "remote-only" : "sync-target-repo";
2453
+ if (!mode) {
2454
+ throw new CliError("Choose exactly one approval mode.", {
2455
+ exitCode: 2,
2456
+ hint: "Pass either `--remote-only` or `--sync-target-repo`."
2457
+ });
2458
+ }
2459
+ const api = await createCollabApi(params);
2460
+ const result = await collabApproveCore({
2461
+ api,
2462
+ mrId: params.mrId,
2463
+ mode,
2464
+ cwd: params.cwd,
2465
+ allowBranchMismatch: params.allowBranchMismatch
2466
+ });
2467
+ if (!params.json) {
2468
+ if (result.mode === "remote-only") {
2469
+ console.log(pc10.green(`Merge approval completed remotely. id=${result.mergeRequestId} status=${result.terminalStatus}`));
2470
+ } else {
2471
+ console.log(
2472
+ pc10.green(
2473
+ `Merge approval completed and synced local repo. id=${result.mergeRequestId} branch=${result.localSync?.branch} head=${result.localSync?.localCommitHash}`
2474
+ )
2475
+ );
2476
+ }
2477
+ printResultWarnings(params.json, result);
2478
+ }
2479
+ return result;
2480
+ }
2481
+ async function collabReject(params) {
2482
+ const api = await createCollabApi(params);
2483
+ const result = await collabRejectCore({
2484
+ api,
2485
+ mrId: params.mrId
2486
+ });
2487
+ if (!params.json) console.log(pc10.green(`Rejected merge request. id=${result.id}`));
2488
+ return result;
2489
+ }
2490
+ async function collabInvite(params) {
2491
+ const api = await createCollabApi(params);
2492
+ const result = await collabInviteCore({
2493
+ api,
2494
+ cwd: params.cwd,
2495
+ email: params.email,
2496
+ role: params.role,
2497
+ ttlDays: params.ttlDays,
2498
+ scope: params.scope ?? "project",
2499
+ targetId: params.targetId ?? null
2500
+ });
2501
+ if (!params.json) {
2502
+ const inviteId = result.id ?? "unknown";
2503
+ console.log(pc10.green(`Created ${result.scopeType} invite. id=${inviteId}`));
2504
+ }
2505
+ return result;
2506
+ }
2507
+ async function collabInviteList(params) {
2508
+ const api = await createCollabApi(params);
2509
+ const targetId = await resolveInviteTargetId({
2510
+ api,
2511
+ cwd: params.cwd,
2512
+ scope: params.scope,
2513
+ targetId: params.targetId
2514
+ });
2515
+ const result = await runInviteList({
2516
+ api,
2517
+ scope: params.scope,
2518
+ targetId
2519
+ });
2520
+ if (!params.json && Array.isArray(result)) {
2521
+ for (const invite of result) {
2522
+ console.log(
2523
+ `${String(invite.id ?? "")} ${String(invite.email ?? "")} role=${String(invite.role ?? "")} state=${String(
2524
+ invite.state ?? ""
2525
+ )}`
2526
+ );
2527
+ }
2528
+ }
2529
+ return result;
2530
+ }
2531
+ async function collabInviteResend(params) {
2532
+ const api = await createCollabApi(params);
2533
+ const targetId = await resolveInviteTargetId({
2534
+ api,
2535
+ cwd: params.cwd,
2536
+ scope: params.scope,
2537
+ targetId: params.targetId
2538
+ });
2539
+ const result = await runInviteResend({
2540
+ api,
2541
+ scope: params.scope,
2542
+ targetId,
2543
+ inviteId: params.inviteId,
2544
+ ttlDays: params.ttlDays
2545
+ });
2546
+ if (!params.json) console.log(pc10.green(`Resent ${params.scope} invite. id=${params.inviteId}`));
2547
+ return result;
2548
+ }
2549
+ async function collabInviteRevoke(params) {
2550
+ const api = await createCollabApi(params);
2551
+ const targetId = await resolveInviteTargetId({
2552
+ api,
2553
+ cwd: params.cwd,
2554
+ scope: params.scope,
2555
+ targetId: params.targetId
2556
+ });
2557
+ const result = await runInviteRevoke({
2558
+ api,
2559
+ scope: params.scope,
2560
+ targetId,
2561
+ inviteId: params.inviteId
2562
+ });
2563
+ if (!params.json) console.log(pc10.green(`Revoked ${params.scope} invite. id=${params.inviteId}`));
2564
+ return result;
2565
+ }
2566
+
2567
+ // src/services/memory.ts
2568
+ import pc11 from "picocolors";
2569
+ import { readCollabBinding } from "@comergehq/core/binding";
2570
+ import { findGitRoot } from "@comergehq/core/repo";
2571
+ async function createMemoryApi(params) {
2572
+ await ensureAuth({ yes: params.yes, json: params.json });
2573
+ const cfg = await resolveConfig({ yes: params.yes });
2574
+ return createApiClient(cfg);
2575
+ }
2576
+ function unwrapResponseObject3(resp, label) {
2577
+ const obj = resp?.responseObject;
2578
+ if (obj === void 0 || obj === null) {
2579
+ throw new CliError(typeof resp?.message === "string" && resp.message.trim() ? resp.message : `Missing ${label} response.`, {
2580
+ exitCode: 1
2581
+ });
2582
+ }
2583
+ return obj;
2584
+ }
2585
+ async function maybeFindGitRoot(cwd) {
2586
+ try {
2587
+ return await findGitRoot(cwd);
2588
+ } catch {
2589
+ return null;
2590
+ }
2591
+ }
2592
+ async function resolveMemoryTarget(params) {
2593
+ const explicitAppId = params.appId?.trim();
2594
+ if (explicitAppId) {
2595
+ return {
2596
+ appId: explicitAppId,
2597
+ repoRoot: await maybeFindGitRoot(params.cwd)
2598
+ };
2599
+ }
2600
+ const repoRoot = await findGitRoot(params.cwd);
2601
+ const binding = await readCollabBinding(repoRoot);
2602
+ if (!binding) {
2603
+ throw new CliError("Repository is not bound to Comerge.", {
2604
+ exitCode: 2,
2605
+ hint: "Run `comerge collab init` in this repository, or pass `--app-id` for a direct memory read."
2606
+ });
2607
+ }
2608
+ return {
2609
+ appId: binding.currentAppId,
2610
+ repoRoot
2611
+ };
2612
+ }
2613
+ function truncateText(value, maxChars = 180) {
2614
+ const normalized = value.replace(/\s+/g, " ").trim();
2615
+ if (normalized.length <= maxChars) return normalized;
2616
+ return `${normalized.slice(0, maxChars - 3)}...`;
2617
+ }
2618
+ function compactJson(value, maxChars = 180) {
2619
+ try {
2620
+ return truncateText(JSON.stringify(value));
2621
+ } catch {
2622
+ return "[unserializable preview]";
2623
+ }
2624
+ }
2625
+ function previewSummary(preview) {
2626
+ const candidateKeys = ["title", "summary", "prompt", "assistantResponse", "description", "statusError", "message"];
2627
+ for (const key of candidateKeys) {
2628
+ const value = preview[key];
2629
+ if (typeof value === "string" && value.trim()) {
2630
+ return truncateText(value);
2631
+ }
2632
+ }
2633
+ if (Object.keys(preview).length === 0) return null;
2634
+ return compactJson(preview);
2635
+ }
2636
+ function lineageSummary(item) {
2637
+ const pairs = [
2638
+ ["thread", item.threadId],
2639
+ ["commit", item.lineage.commitId],
2640
+ ["mergeRequest", item.lineage.mergeRequestId],
2641
+ ["branch", item.lineage.branch]
2642
+ ].filter(([, value]) => typeof value === "string" && value);
2643
+ if (pairs.length === 0) return null;
2644
+ return pairs.map(([label, value]) => `${label}=${value}`).join(" ");
2645
+ }
2646
+ function printMemoryItems(items, opts) {
2647
+ if (items.length === 0) {
2648
+ console.log(pc11.dim("No memory items found."));
2649
+ return;
2650
+ }
2651
+ for (const item of items) {
2652
+ const search = "search" in item ? item.search : null;
2653
+ const score = opts?.includeScore && search ? ` score=${search.score.toFixed(4)}` : "";
2654
+ console.log(
2655
+ `${pc11.bold(item.kind)} id=${item.id} status=${item.status ?? "unknown"} createdAt=${item.createdAt}${score}`
2656
+ );
2657
+ const preview = previewSummary(item.preview);
2658
+ if (preview) console.log(`preview=${preview}`);
2659
+ const lineage = lineageSummary(item);
2660
+ if (lineage) console.log(lineage);
2661
+ if (search && search.matchedOn.length > 0) console.log(`matchedOn=${search.matchedOn.join(",")}`);
2662
+ console.log("");
2663
+ }
2664
+ }
2665
+ function printSummary(summary, target) {
2666
+ console.log(pc11.bold("App"));
2667
+ console.log(`appId=${summary.app.id}`);
2668
+ console.log(`status=${summary.app.status ?? "unknown"} threadId=${summary.app.threadId ?? "none"}`);
2669
+ console.log(`headCommitId=${summary.app.headCommitId ?? "none"} headCommitHash=${summary.app.headCommitHash ?? "none"}`);
2670
+ if (target.repoRoot) console.log(`repoRoot=${target.repoRoot}`);
2671
+ console.log("");
2672
+ console.log(pc11.bold("Source"));
2673
+ if (!summary.source) {
2674
+ console.log("none");
2675
+ } else {
2676
+ console.log(`remoteUrl=${summary.source.remoteUrl ?? "none"}`);
2677
+ console.log(`defaultBranch=${summary.source.defaultBranch ?? "none"} repoFingerprint=${summary.source.repoFingerprint ?? "none"}`);
2678
+ console.log(`sourceType=${summary.source.sourceType ?? "none"} updatedAt=${summary.source.updatedAt}`);
2679
+ }
2680
+ console.log("");
2681
+ console.log(pc11.bold("Counts"));
2682
+ console.log(
2683
+ `collabTurns=${summary.counts.collabTurnCount} changeSteps=${summary.counts.changeStepCount} mergeRequests=${summary.counts.mergeRequestCount} reconciles=${summary.counts.reconcileCount}`
2684
+ );
2685
+ console.log(
2686
+ `openMergeRequests=${summary.counts.openMergeRequestCount} pendingChangeSteps=${summary.counts.pendingChangeStepCount} failedChangeSteps=${summary.counts.failedChangeStepCount}`
2687
+ );
2688
+ console.log("");
2689
+ console.log(pc11.bold("Latest Reconcile"));
2690
+ if (!summary.latestReconcile) {
2691
+ console.log("none");
2692
+ } else {
2693
+ console.log(`id=${summary.latestReconcile.id} status=${summary.latestReconcile.status ?? "unknown"} createdAt=${summary.latestReconcile.createdAt}`);
2694
+ }
2695
+ console.log("");
2696
+ console.log(pc11.bold("Recent"));
2697
+ console.log(`collabTurns=${summary.recent.collabTurns.length}`);
2698
+ console.log(`changeSteps=${summary.recent.changeSteps.length}`);
2699
+ console.log(`mergeRequests=${summary.recent.mergeRequests.length}`);
2700
+ console.log(`reconciles=${summary.recent.reconciles.length}`);
2701
+ }
2702
+ function printPagination(pagination) {
2703
+ console.log(pc11.dim(`limit=${pagination.limit} offset=${pagination.offset} hasMore=${pagination.hasMore ? "yes" : "no"}`));
2704
+ }
2705
+ async function memorySummary(params) {
2706
+ const target = await resolveMemoryTarget(params);
2707
+ const api = await createMemoryApi(params);
2708
+ const resp = await api.getAgentMemorySummary(target.appId);
2709
+ const result = unwrapResponseObject3(resp, "agent memory summary");
2710
+ if (!params.json) {
2711
+ printSummary(result, target);
2712
+ }
2713
+ return result;
2714
+ }
2715
+ async function memorySearch(params) {
2716
+ const target = await resolveMemoryTarget(params);
2717
+ const api = await createMemoryApi(params);
2718
+ const resp = await api.searchAgentMemory(target.appId, {
2719
+ q: params.query,
2720
+ kinds: params.kinds,
2721
+ limit: params.limit,
2722
+ offset: params.offset,
2723
+ createdAfter: params.createdAfter ?? void 0,
2724
+ createdBefore: params.createdBefore ?? void 0
2725
+ });
2726
+ const result = unwrapResponseObject3(resp, "agent memory search");
2727
+ if (!params.json) {
2728
+ printMemoryItems(result.items, { includeScore: true });
2729
+ printPagination(result.pagination);
2730
+ }
2731
+ return result;
2732
+ }
2733
+ async function memoryTimeline(params) {
2734
+ const target = await resolveMemoryTarget(params);
2735
+ const api = await createMemoryApi(params);
2736
+ const resp = await api.listAgentMemoryTimeline(target.appId, {
2737
+ kinds: params.kinds,
2738
+ limit: params.limit,
2739
+ offset: params.offset,
2740
+ createdAfter: params.createdAfter ?? void 0,
2741
+ createdBefore: params.createdBefore ?? void 0
2742
+ });
2743
+ const result = unwrapResponseObject3(resp, "agent memory timeline");
2744
+ if (!params.json) {
2745
+ printMemoryItems(result.items);
2746
+ printPagination(result.pagination);
2747
+ }
2748
+ return result;
2749
+ }
2750
+ async function memoryDiff(params) {
2751
+ const target = await resolveMemoryTarget(params);
2752
+ const api = await createMemoryApi(params);
2753
+ const resp = await api.getChangeStepDiff(target.appId, params.changeStepId);
2754
+ const result = unwrapResponseObject3(resp, "change step diff");
2755
+ if (!params.json) {
2756
+ console.log(pc11.bold("Change Step Diff"));
2757
+ console.log(`changeStepId=${result.changeStepId} appId=${result.appId}`);
2758
+ console.log(`diffSha256=${result.diffSha256} encoding=${result.encoding} contentType=${result.contentType}`);
2759
+ console.log("");
2760
+ process.stdout.write(result.diff.endsWith("\n") ? result.diff : `${result.diff}
2761
+ `);
2762
+ }
2763
+ return result;
2764
+ }
2765
+
2766
+ // src/commands/collab.ts
2767
+ function parseInteger(value, label) {
2768
+ const parsed = Number.parseInt(value, 10);
2769
+ if (!Number.isInteger(parsed) || parsed < 0) {
2770
+ throw new Error(`${label} must be a non-negative integer.`);
2771
+ }
2772
+ return parsed;
2773
+ }
2774
+ function collectKinds(value, previous) {
2775
+ return [...previous, String(value)];
2776
+ }
2777
+ function toKinds(values) {
2778
+ if (!Array.isArray(values) || values.length === 0) return void 0;
2779
+ return values.map((value) => String(value));
2780
+ }
2781
+ function parseInviteScope(value) {
2782
+ const normalized = String(value).trim().toLowerCase();
2783
+ if (normalized === "org" || normalized === "organization") return "organization";
2784
+ if (normalized === "project") return "project";
2785
+ if (normalized === "app") return "app";
2786
+ throw new Error("scope must be one of: organization, project, app");
2787
+ }
2788
+ function registerCollabCommands(program) {
2789
+ const collab = program.command("collab").description("Generic collaboration workflow for any software repository");
2790
+ const memory = collab.command("memory").description("Inspect collaboration memory for the current bound repository or an explicit app");
2791
+ collab.command("init").description("Import the current repository into Comerge").option("--name <name>", "Override the imported app name").option("--cwd <path>", "Working directory for repository detection").option("--force-new", "Create a new binding even if a matching project exists", false).option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (opts) => {
2792
+ const result = await collabInit({
2793
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2794
+ appName: opts.name ? String(opts.name) : null,
2795
+ forceNew: Boolean(opts.forceNew),
2796
+ json: Boolean(opts.json),
2797
+ yes: Boolean(opts.yes || opts.nonInteractive)
2798
+ });
2799
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2800
+ });
2801
+ collab.command("list").description("List apps available in Comerge").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (opts) => {
2802
+ const result = await collabList({ json: Boolean(opts.json), yes: Boolean(opts.yes || opts.nonInteractive) });
2803
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2804
+ });
2805
+ collab.command("status").description("Summarize binding, worktree, merge request, and sync/reconcile readiness").option("--cwd <path>", "Working directory for repository detection").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (opts) => {
2806
+ const result = await collabStatus({
2807
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2808
+ json: Boolean(opts.json),
2809
+ yes: Boolean(opts.yes || opts.nonInteractive)
2810
+ });
2811
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2812
+ });
2813
+ memory.command("summary").description("Summarize app memory state and recent collaboration activity").option("--cwd <path>", "Working directory for repository detection").option("--app-id <id>", "Read memory for a specific app instead of the current bound repository").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (opts) => {
2814
+ const result = await memorySummary({
2815
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2816
+ appId: opts.appId ? String(opts.appId) : null,
2817
+ json: Boolean(opts.json),
2818
+ yes: Boolean(opts.yes || opts.nonInteractive)
2819
+ });
2820
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2821
+ });
2822
+ memory.command("search <query...>").description("Search collaboration memory for historical context").option("--cwd <path>", "Working directory for repository detection").option("--app-id <id>", "Read memory for a specific app instead of the current bound repository").option("--kind <kind>", "Filter by memory kind", collectKinds, []).option("--limit <n>", "Maximum number of results", (value) => parseInteger(value, "limit")).option("--offset <n>", "Pagination offset", (value) => parseInteger(value, "offset")).option("--created-after <iso8601>", "Only include items created at or after this timestamp").option("--created-before <iso8601>", "Only include items created at or before this timestamp").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (query, opts) => {
2823
+ const result = await memorySearch({
2824
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2825
+ appId: opts.appId ? String(opts.appId) : null,
2826
+ query: Array.isArray(query) ? query.join(" ") : String(query),
2827
+ kinds: toKinds(opts.kind),
2828
+ limit: typeof opts.limit === "number" ? opts.limit : void 0,
2829
+ offset: typeof opts.offset === "number" ? opts.offset : void 0,
2830
+ createdAfter: opts.createdAfter ? String(opts.createdAfter) : null,
2831
+ createdBefore: opts.createdBefore ? String(opts.createdBefore) : null,
2832
+ json: Boolean(opts.json),
2833
+ yes: Boolean(opts.yes || opts.nonInteractive)
2834
+ });
2835
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2836
+ });
2837
+ memory.command("timeline").description("List recent collaboration memory items chronologically").option("--cwd <path>", "Working directory for repository detection").option("--app-id <id>", "Read memory for a specific app instead of the current bound repository").option("--kind <kind>", "Filter by memory kind", collectKinds, []).option("--limit <n>", "Maximum number of results", (value) => parseInteger(value, "limit")).option("--offset <n>", "Pagination offset", (value) => parseInteger(value, "offset")).option("--created-after <iso8601>", "Only include items created at or after this timestamp").option("--created-before <iso8601>", "Only include items created at or before this timestamp").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (opts) => {
2838
+ const result = await memoryTimeline({
2839
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2840
+ appId: opts.appId ? String(opts.appId) : null,
2841
+ kinds: toKinds(opts.kind),
2842
+ limit: typeof opts.limit === "number" ? opts.limit : void 0,
2843
+ offset: typeof opts.offset === "number" ? opts.offset : void 0,
2844
+ createdAfter: opts.createdAfter ? String(opts.createdAfter) : null,
2845
+ createdBefore: opts.createdBefore ? String(opts.createdBefore) : null,
2846
+ json: Boolean(opts.json),
2847
+ yes: Boolean(opts.yes || opts.nonInteractive)
2848
+ });
2849
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2850
+ });
2851
+ memory.command("diff <changeStepId>").description("Fetch the full stored diff for a specific change step").option("--cwd <path>", "Working directory for repository detection").option("--app-id <id>", "Read memory for a specific app instead of the current bound repository").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (changeStepId, opts) => {
2852
+ const result = await memoryDiff({
2853
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2854
+ appId: opts.appId ? String(opts.appId) : null,
2855
+ changeStepId: String(changeStepId),
2856
+ json: Boolean(opts.json),
2857
+ yes: Boolean(opts.yes || opts.nonInteractive)
2858
+ });
2859
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2860
+ });
2861
+ collab.command("remix <appId>").description("Create a remix/fork and materialize a new local checkout").option("--name <name>", "Optional name for the remix").option("--output-dir <path>", "Exact destination path for the new remix checkout").option("--cwd <path>", "Parent directory where the new remix checkout will be created when --output-dir is not provided").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (appId, opts) => {
2862
+ const result = await collabRemix({
2863
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2864
+ outputDir: opts.outputDir ? String(opts.outputDir) : null,
2865
+ appId: appId ? String(appId) : null,
2866
+ name: opts.name ? String(opts.name) : null,
2867
+ json: Boolean(opts.json),
2868
+ yes: Boolean(opts.yes || opts.nonInteractive)
2869
+ });
2870
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2871
+ });
2872
+ collab.command("add [prompt...]").description("Record one collaboration change step").option("--assistant-response <text>", "Attach the final assistant response to the change step").option("--assistant-response-file <path>", "Read the assistant response from a file").option("--prompt-file <path>", "Read the prompt from a file").option("--prompt-stdin", "Read the prompt from stdin", false).option("--diff-file <path>", "Read the unified diff from a file").option("--diff-stdin", "Read the unified diff from stdin", false).option("--no-sync", "Do not auto-discard local tracked changes and sync after a successful add").option("--allow-branch-mismatch", "Allow running from a branch that does not match the checkout's preferred Comerge branch", false).option("--cwd <path>", "Working directory for repository detection").option("--idempotency-key <key>", "Idempotency key for safe retries").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (prompt, opts) => {
2873
+ const result = await collabAdd({
2874
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2875
+ prompt: Array.isArray(prompt) ? prompt.join(" ") : prompt ? String(prompt) : null,
2876
+ assistantResponse: opts.assistantResponse ? String(opts.assistantResponse) : null,
2877
+ assistantResponseFile: opts.assistantResponseFile ? String(opts.assistantResponseFile) : null,
2878
+ promptFile: opts.promptFile ? String(opts.promptFile) : null,
2879
+ promptStdin: Boolean(opts.promptStdin),
2880
+ diffFile: opts.diffFile ? String(opts.diffFile) : null,
2881
+ diffStdin: Boolean(opts.diffStdin),
2882
+ sync: Boolean(opts.sync),
2883
+ allowBranchMismatch: Boolean(opts.allowBranchMismatch),
2884
+ idempotencyKey: opts.idempotencyKey ? String(opts.idempotencyKey) : null,
2885
+ json: Boolean(opts.json),
2886
+ yes: Boolean(opts.yes || opts.nonInteractive)
2887
+ });
2888
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2889
+ });
2890
+ collab.command("record-turn [prompt...]").description("Record one no-diff collaboration turn with prompt and assistant response").option("--assistant-response <text>", "Assistant response text").option("--assistant-response-file <path>", "Read the assistant response from a file").option("--prompt-file <path>", "Read the prompt from a file").option("--prompt-stdin", "Read the prompt from stdin", false).option("--allow-branch-mismatch", "Allow running from a branch that does not match the checkout's preferred Comerge branch", false).option("--cwd <path>", "Working directory for repository detection").option("--idempotency-key <key>", "Idempotency key for safe retries").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (prompt, opts) => {
2891
+ const result = await collabRecordTurn({
2892
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2893
+ prompt: Array.isArray(prompt) ? prompt.join(" ") : prompt ? String(prompt) : null,
2894
+ assistantResponse: opts.assistantResponse ? String(opts.assistantResponse) : null,
2895
+ assistantResponseFile: opts.assistantResponseFile ? String(opts.assistantResponseFile) : null,
2896
+ promptFile: opts.promptFile ? String(opts.promptFile) : null,
2897
+ promptStdin: Boolean(opts.promptStdin),
2898
+ allowBranchMismatch: Boolean(opts.allowBranchMismatch),
2899
+ idempotencyKey: opts.idempotencyKey ? String(opts.idempotencyKey) : null,
2900
+ json: Boolean(opts.json),
2901
+ yes: Boolean(opts.yes || opts.nonInteractive)
2902
+ });
2903
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2904
+ });
2905
+ collab.command("sync").description("Sync the current local repository to the bound Comerge app state").option("--cwd <path>", "Working directory for repository detection").option("--dry-run", "Preview sync status without applying changes", false).option("--allow-branch-mismatch", "Allow running from a branch that does not match the checkout's preferred Comerge branch", false).option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (opts) => {
2906
+ const result = await collabSync({
2907
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2908
+ dryRun: Boolean(opts.dryRun),
2909
+ allowBranchMismatch: Boolean(opts.allowBranchMismatch),
2910
+ json: Boolean(opts.json),
2911
+ yes: Boolean(opts.yes || opts.nonInteractive)
2912
+ });
2913
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2914
+ });
2915
+ collab.command("reconcile").description("Reconcile divergent local history against the bound Comerge app").option("--cwd <path>", "Working directory for repository detection").option("--dry-run", "Preview reconcile readiness without applying changes", false).option("--allow-branch-mismatch", "Allow running from a branch that does not match the checkout's preferred Comerge branch", false).option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (opts) => {
2916
+ const result = await collabReconcile({
2917
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2918
+ dryRun: Boolean(opts.dryRun),
2919
+ allowBranchMismatch: Boolean(opts.allowBranchMismatch),
2920
+ json: Boolean(opts.json),
2921
+ yes: Boolean(opts.yes || opts.nonInteractive)
2922
+ });
2923
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2924
+ });
2925
+ collab.command("sync-upstream").description("Sync upstream changes into the current remix and update the local checkout").option("--cwd <path>", "Working directory for repository detection").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (opts) => {
2926
+ const result = await collabSyncUpstream({
2927
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2928
+ json: Boolean(opts.json),
2929
+ yes: Boolean(opts.yes || opts.nonInteractive)
2930
+ });
2931
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2932
+ });
2933
+ collab.command("request-merge").description("Open a merge request from the current remix to its upstream app").option("--yes", "Run non-interactively", false).option("--cwd <path>", "Working directory for repository detection").option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (opts) => {
2934
+ const result = await collabRequestMerge({
2935
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2936
+ json: Boolean(opts.json),
2937
+ yes: Boolean(opts.yes || opts.nonInteractive)
2938
+ });
2939
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2940
+ });
2941
+ collab.command("inbox").description("List open merge requests available for review").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (opts) => {
2942
+ const result = await collabInbox({ json: Boolean(opts.json), yes: Boolean(opts.yes || opts.nonInteractive) });
2943
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2944
+ });
2945
+ collab.command("view <mrId>").description("View merge request prompts and diffs").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (mrId, opts) => {
2946
+ const result = await collabView({
2947
+ mrId: String(mrId),
2948
+ json: Boolean(opts.json),
2949
+ yes: Boolean(opts.yes || opts.nonInteractive)
2950
+ });
2951
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2952
+ });
2953
+ collab.command("approve <mrId>").description("Approve a merge request").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--remote-only", "Approve remotely and wait for merge completion without touching the local repo", false).option("--sync-target-repo", "Approve, wait for merge completion, and sync the current target repo checkout", false).option("--allow-branch-mismatch", "Allow syncing the target repo from a branch that does not match the checkout's preferred Comerge branch", false).option("--json", "Output JSON", false).action(async (mrId, opts) => {
2954
+ const result = await collabApprove({
2955
+ mrId: String(mrId),
2956
+ cwd: process.cwd(),
2957
+ remoteOnly: Boolean(opts.remoteOnly),
2958
+ syncTargetRepo: Boolean(opts.syncTargetRepo),
2959
+ allowBranchMismatch: Boolean(opts.allowBranchMismatch),
2960
+ json: Boolean(opts.json),
2961
+ yes: Boolean(opts.yes || opts.nonInteractive)
2962
+ });
2963
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2964
+ });
2965
+ collab.command("reject <mrId>").description("Reject a merge request").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (mrId, opts) => {
2966
+ const result = await collabReject({
2967
+ mrId: String(mrId),
2968
+ json: Boolean(opts.json),
2969
+ yes: Boolean(opts.yes || opts.nonInteractive)
2970
+ });
2971
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2972
+ });
2973
+ collab.command("invite <email>").description("Invite a collaborator to an organization, project, or app").option("--scope <scope>", "Invitation scope (organization|project|app)", parseInviteScope, "project").option("--target-id <id>", "Explicit organization/project/app id instead of using the current repository binding").option("--role <role>", "Role for the invite").option("--ttl-days <days>", "Invitation lifetime in days", (value) => parseInteger(value, "ttl-days")).option("--cwd <path>", "Working directory for repository detection").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (email, opts) => {
2974
+ const result = await collabInvite({
2975
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2976
+ email: String(email),
2977
+ role: opts.role ? String(opts.role) : null,
2978
+ ttlDays: typeof opts.ttlDays === "number" ? opts.ttlDays : void 0,
2979
+ scope: parseInviteScope(String(opts.scope ?? "project")),
2980
+ targetId: opts.targetId ? String(opts.targetId) : null,
2981
+ json: Boolean(opts.json),
2982
+ yes: Boolean(opts.yes || opts.nonInteractive)
2983
+ });
2984
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2985
+ });
2986
+ const inviteAdmin = collab.command("invites").description("List, resend, or revoke invitations");
2987
+ inviteAdmin.command("list").description("List invitations for an organization, project, or app").option("--scope <scope>", "Invitation scope (organization|project|app)", parseInviteScope, "project").option("--target-id <id>", "Explicit organization/project/app id instead of using the current repository binding").option("--cwd <path>", "Working directory for repository detection").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (opts) => {
2988
+ const result = await collabInviteList({
2989
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
2990
+ scope: parseInviteScope(String(opts.scope ?? "project")),
2991
+ targetId: opts.targetId ? String(opts.targetId) : null,
2992
+ json: Boolean(opts.json),
2993
+ yes: Boolean(opts.yes || opts.nonInteractive)
2994
+ });
2995
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
2996
+ });
2997
+ inviteAdmin.command("resend <inviteId>").description("Resend an invitation").option("--scope <scope>", "Invitation scope (organization|project|app)", parseInviteScope, "project").option("--target-id <id>", "Explicit organization/project/app id instead of using the current repository binding").option("--ttl-days <days>", "Invitation lifetime in days", (value) => parseInteger(value, "ttl-days")).option("--cwd <path>", "Working directory for repository detection").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (inviteId, opts) => {
2998
+ const result = await collabInviteResend({
2999
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
3000
+ scope: parseInviteScope(String(opts.scope ?? "project")),
3001
+ inviteId: String(inviteId),
3002
+ targetId: opts.targetId ? String(opts.targetId) : null,
3003
+ ttlDays: typeof opts.ttlDays === "number" ? opts.ttlDays : void 0,
3004
+ json: Boolean(opts.json),
3005
+ yes: Boolean(opts.yes || opts.nonInteractive)
3006
+ });
3007
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
3008
+ });
3009
+ inviteAdmin.command("revoke <inviteId>").description("Revoke an invitation").option("--scope <scope>", "Invitation scope (organization|project|app)", parseInviteScope, "project").option("--target-id <id>", "Explicit organization/project/app id instead of using the current repository binding").option("--cwd <path>", "Working directory for repository detection").option("--yes", "Run non-interactively", false).option("--non-interactive", "Run non-interactively", false).option("--json", "Output JSON", false).action(async (inviteId, opts) => {
3010
+ const result = await collabInviteRevoke({
3011
+ cwd: opts.cwd ? String(opts.cwd) : process.cwd(),
3012
+ scope: parseInviteScope(String(opts.scope ?? "project")),
3013
+ inviteId: String(inviteId),
3014
+ targetId: opts.targetId ? String(opts.targetId) : null,
3015
+ json: Boolean(opts.json),
3016
+ yes: Boolean(opts.yes || opts.nonInteractive)
3017
+ });
3018
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
3019
+ });
3020
+ }
3021
+
2239
3022
  // src/cli.ts
2240
3023
  var require2 = createRequire(import.meta.url);
2241
3024
  var { version } = require2("../package.json");
@@ -2246,20 +3029,21 @@ async function main(argv) {
2246
3029
  registerWhoamiCommand(program);
2247
3030
  registerLogoutCommand(program);
2248
3031
  registerInitCommands(program);
3032
+ registerCollabCommands(program);
2249
3033
  program.configureOutput({
2250
- outputError: (str, write) => write(pc10.red(str))
3034
+ outputError: (str, write) => write(pc12.red(str))
2251
3035
  });
2252
3036
  try {
2253
3037
  await program.parseAsync(argv);
2254
3038
  } catch (err) {
2255
3039
  const e = err;
2256
3040
  if (e instanceof CliError) {
2257
- console.error(pc10.red(`Error: ${e.message}`));
2258
- if (e.hint) console.error(pc10.dim(e.hint));
3041
+ console.error(pc12.red(`Error: ${e.message}`));
3042
+ if (e.hint) console.error(pc12.dim(e.hint));
2259
3043
  process.exitCode = e.exitCode;
2260
3044
  return;
2261
3045
  }
2262
- console.error(pc10.red(`Unexpected error: ${e?.message ?? String(e)}`));
3046
+ console.error(pc12.red(`Unexpected error: ${e?.message ?? String(e)}`));
2263
3047
  process.exitCode = 1;
2264
3048
  }
2265
3049
  }