runwork 0.16.1 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -411,6 +411,29 @@ class ApiClient {
411
411
  const res = await this.request("/api/dev/workspaces");
412
412
  return res.data;
413
413
  }
414
+ async listWorkspaceMembers(workspaceId) {
415
+ const res = await this.request(`/api/workspaces/${workspaceId}/members`);
416
+ return res.data.members;
417
+ }
418
+ async rawApiCall(method, path, opts) {
419
+ const targetPath = opts?.query ? `${path}${path.includes("?") ? "&" : "?"}${opts.query}` : path;
420
+ const url = `${this.baseUrl}${targetPath}`;
421
+ const headers = { ...opts?.headers || {} };
422
+ if (this.apiKey) {
423
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
424
+ }
425
+ let body;
426
+ if (opts?.body !== undefined) {
427
+ body = typeof opts.body === "string" ? opts.body : JSON.stringify(opts.body);
428
+ if (!Object.keys(headers).some((h) => h.toLowerCase() === "content-type")) {
429
+ headers["Content-Type"] = "application/json";
430
+ }
431
+ }
432
+ const response = await httpFetch(url, { method: method.toUpperCase(), headers, body });
433
+ const contentType = response.headers.get("content-type") || "";
434
+ const responseBody = contentType.includes("application/json") ? await response.json() : await response.text();
435
+ return { status: response.status, ok: response.ok, contentType, body: responseBody };
436
+ }
414
437
  async listApps(workspaceId) {
415
438
  const query = workspaceId ? `?workspaceId=${workspaceId}` : "";
416
439
  const res = await this.request(`/api/dev/apps${query}`);
@@ -883,20 +906,28 @@ var init_subprocess = () => {};
883
906
 
884
907
  // src/git/credentials.ts
885
908
  import { existsSync as existsSync2 } from "fs";
886
- function buildHelperValue(execPath) {
909
+ function buildHelperValue(execPath, scriptPath) {
887
910
  const normalised = execPath.replace(/\\/g, "/");
911
+ const runtimeName = normalised.split("/").pop()?.toLowerCase() ?? "";
912
+ if (SCRIPT_RUNTIMES.has(runtimeName) && scriptPath) {
913
+ const script = scriptPath.replace(/\\/g, "/");
914
+ return `!"${normalised}" "${script}" git-credential-helper`;
915
+ }
888
916
  return `!"${normalised}" git-credential-helper`;
889
917
  }
890
918
  async function configureGitCredentials(remoteUrl) {
891
919
  const origin = new URL(remoteUrl).origin;
892
- const helperValue = buildHelperValue(process.execPath);
920
+ const key = `credential.${origin}.helper`;
921
+ const helperValue = buildHelperValue(process.execPath, process.argv[1]);
893
922
  try {
894
- execFileSync("git", [
895
- "config",
896
- "--global",
897
- `credential.${origin}.helper`,
898
- helperValue
899
- ], { stdio: "pipe" });
923
+ try {
924
+ execFileSync("git", ["config", "--global", "--unset-all", key], { stdio: "pipe" });
925
+ } catch (unsetErr) {
926
+ if (unsetErr?.code === "ENOENT")
927
+ throw unsetErr;
928
+ }
929
+ execFileSync("git", ["config", "--global", "--add", key, ""], { stdio: "pipe" });
930
+ execFileSync("git", ["config", "--global", "--add", key, helperValue], { stdio: "pipe" });
900
931
  } catch (err) {
901
932
  const code = err?.code;
902
933
  if (code === "ENOENT") {
@@ -915,14 +946,37 @@ function lookupCredentialHelper(origin) {
915
946
  } catch {
916
947
  return { status: "none" };
917
948
  }
918
- const lines = helperConfig.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
919
- const escapedOrigin = origin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
920
- const scopedRegex = new RegExp(`^credential\\.${escapedOrigin}\\.helper\\s+(.+)$`);
921
- const matchedLine = lines.find((line) => scopedRegex.test(line));
922
- if (!matchedLine) {
923
- return { status: "unscoped", otherCount: lines.length };
949
+ const keyName = `credential.${origin}.helper`;
950
+ let value = "";
951
+ let registered = false;
952
+ let hasReset = false;
953
+ let otherCount = 0;
954
+ for (const raw of helperConfig.split(/\r?\n/)) {
955
+ if (!raw.trim())
956
+ continue;
957
+ const m = raw.match(/^(\S+)(?:\s+(.*))?$/);
958
+ if (!m)
959
+ continue;
960
+ const key = m[1];
961
+ const val = (m[2] ?? "").trim();
962
+ if (key === keyName) {
963
+ if (val === "")
964
+ hasReset = true;
965
+ else if (!registered) {
966
+ registered = true;
967
+ value = val;
968
+ }
969
+ } else {
970
+ otherCount++;
971
+ }
972
+ }
973
+ if (registered) {
974
+ return { status: "registered", value, hasReset };
975
+ }
976
+ if (otherCount === 0 && !hasReset) {
977
+ return { status: "none" };
924
978
  }
925
- return { status: "registered", value: scopedRegex.exec(matchedLine)?.[1] ?? "" };
979
+ return { status: "unscoped", otherCount };
926
980
  }
927
981
  function helperBinaryStatus(value) {
928
982
  const absoluteHelperRegex = /^!"?(\/|[A-Za-z]:[\\/])/;
@@ -942,7 +996,7 @@ async function ensureGitCredentialHelper(baseUrl) {
942
996
  return;
943
997
  }
944
998
  const lookup = lookupCredentialHelper(origin);
945
- if (lookup.status === "registered" && helperBinaryStatus(lookup.value).ok) {
999
+ if (lookup.status === "registered" && helperBinaryStatus(lookup.value).ok && lookup.hasReset) {
946
1000
  return;
947
1001
  }
948
1002
  await configureGitCredentials(baseUrl);
@@ -953,7 +1007,7 @@ async function removeGitCredentials(baseUrl) {
953
1007
  execFileSync("git", [
954
1008
  "config",
955
1009
  "--global",
956
- "--unset",
1010
+ "--unset-all",
957
1011
  `credential.${origin}.helper`
958
1012
  ], { stdio: "pipe" });
959
1013
  } catch {}
@@ -996,9 +1050,11 @@ function readStdin() {
996
1050
  resolve(data);
997
1051
  });
998
1052
  }
1053
+ var SCRIPT_RUNTIMES;
999
1054
  var init_credentials = __esm(() => {
1000
1055
  init_subprocess();
1001
1056
  init_store();
1057
+ SCRIPT_RUNTIMES = new Set(["node", "node.exe", "bun", "bun.exe"]);
1002
1058
  });
1003
1059
 
1004
1060
  // src/auth/login-flow.ts
@@ -1429,8 +1485,8 @@ async function resolveWorkspace(client, nameOrId) {
1429
1485
  console.error(`Workspace "${nameOrId}" not found. Available: ${workspaces.map((w) => w.name).join(", ")}`);
1430
1486
  process.exit(1);
1431
1487
  }
1432
- async function resolveApp(client, nameOrId) {
1433
- const apps = await client.listApps();
1488
+ async function resolveApp(client, nameOrId, workspaceId) {
1489
+ const apps = await client.listApps(workspaceId);
1434
1490
  if (apps.length === 0) {
1435
1491
  console.error("No apps found.");
1436
1492
  process.exit(1);
@@ -2422,7 +2478,7 @@ var init_clone = __esm(() => {
2422
2478
  init_remote();
2423
2479
  init_repo_config();
2424
2480
  init_preflight();
2425
- cloneCommand = new Command3("clone").description("Clone a Runwork app to local development").argument("[appId]", "App ID to clone (interactive if omitted)").argument("[directory]", "Target directory").option("--app <name-or-id>", "App name or ID (skips interactive selection)").addHelpText("after", RESTRICTED_FS_HELP).action(async (rawAppId, rawDirectory, options) => {
2481
+ cloneCommand = new Command3("clone").description("Clone a Runwork app to local development").argument("[appId]", "App ID to clone (interactive if omitted)").argument("[directory]", "Target directory").option("--app <name-or-id>", "App name or ID (skips interactive selection)").option("--workspace <name-or-id>", "Workspace to look up the app in (name or ID)").addHelpText("after", RESTRICTED_FS_HELP).action(async (rawAppId, rawDirectory, options) => {
2426
2482
  requireGit("clone");
2427
2483
  const { appId, directory } = normalizeCloneArgs(rawAppId, rawDirectory, options);
2428
2484
  const creds = requireAuth();
@@ -2430,10 +2486,15 @@ var init_clone = __esm(() => {
2430
2486
  const useJson = shouldOutputJson(undefined);
2431
2487
  let app;
2432
2488
  const appRef = options?.app || appId;
2489
+ let workspaceId;
2490
+ if (options?.workspace) {
2491
+ const ws = await resolveWorkspace(client, options.workspace);
2492
+ workspaceId = ws.id;
2493
+ }
2433
2494
  if (appRef) {
2434
- app = await resolveApp(client, appRef);
2495
+ app = await resolveApp(client, appRef, workspaceId);
2435
2496
  } else {
2436
- const apps = await client.listApps();
2497
+ const apps = await client.listApps(workspaceId);
2437
2498
  if (apps.length === 0) {
2438
2499
  if (useJson) {
2439
2500
  jsonOut(buildErrorResponse("clone", "No apps found", "No apps are available to clone.", ["Create an app first with: runwork init my-app"]));
@@ -3307,13 +3368,20 @@ function resolvePollDeps(deps) {
3307
3368
  }
3308
3369
  async function pollForSession(appDir, expectedPid, expectedAppId, opts = {}) {
3309
3370
  const intervalMs = opts.intervalMs ?? 250;
3310
- const timeoutMs = opts.timeoutMs ?? 90000;
3371
+ const timeoutMs = opts.timeoutMs ?? DETACH_READY_TIMEOUT_MS;
3372
+ const heartbeatMs = opts.heartbeatMs ?? DETACH_WAITING_HEARTBEAT_MS;
3311
3373
  const d = resolvePollDeps(opts.deps);
3312
- const deadline = d.now() + timeoutMs;
3374
+ const startedAt = d.now();
3375
+ const deadline = startedAt + timeoutMs;
3376
+ let lastHeartbeatAt = startedAt;
3313
3377
  while (d.now() < deadline) {
3314
3378
  if (opts.isChildAlive && !opts.isChildAlive()) {
3315
3379
  return { result: "child-exited" };
3316
3380
  }
3381
+ if (opts.onWaiting && d.now() - lastHeartbeatAt >= heartbeatMs) {
3382
+ lastHeartbeatAt = d.now();
3383
+ opts.onWaiting(d.now() - startedAt);
3384
+ }
3317
3385
  const file = readSessionFile(appDir);
3318
3386
  if (file && file.previewUrl && file.appId === expectedAppId) {
3319
3387
  if (file.pid === expectedPid) {
@@ -3349,7 +3417,9 @@ async function runAsDetachedParent(opts) {
3349
3417
  intervalMs: opts.intervalMs,
3350
3418
  timeoutMs: opts.timeoutMs,
3351
3419
  deps: opts.pollDeps,
3352
- isChildAlive: child.isAlive
3420
+ isChildAlive: child.isAlive,
3421
+ onWaiting: opts.onWaiting,
3422
+ heartbeatMs: opts.heartbeatMs
3353
3423
  });
3354
3424
  if (outcome.result === "ready") {
3355
3425
  return { result: "started", file: outcome.file };
@@ -3366,22 +3436,27 @@ async function runAsDetachedParent(opts) {
3366
3436
  try {
3367
3437
  removeSessionFileIfOwned(opts.appDir, child.pid);
3368
3438
  } catch {}
3369
- const childLogTail = readChildStderrTail(opts.appDir);
3439
+ const childLogTail = readChildLogTail(opts.appDir);
3370
3440
  if (outcome.result === "child-exited") {
3371
3441
  return { result: "child-exited", ourPid: child.pid, childLogTail };
3372
3442
  }
3373
3443
  return { result: "timeout", ourPid: child.pid, childLogTail };
3374
3444
  }
3375
- function readChildStderrTail(appDir) {
3376
- try {
3377
- const paths = getSessionPaths(appDir);
3378
- if (!fs2.existsSync(paths.stderrLog))
3379
- return;
3380
- const raw = fs2.readFileSync(paths.stderrLog, "utf-8");
3381
- return raw.slice(-2000);
3382
- } catch {
3383
- return;
3445
+ function readChildLogTail(appDir) {
3446
+ const paths = getSessionPaths(appDir);
3447
+ const sections = [];
3448
+ for (const [label, file] of [["stderr", paths.stderrLog], ["stdout", paths.stdoutLog]]) {
3449
+ try {
3450
+ if (!fs2.existsSync(file))
3451
+ continue;
3452
+ const tail = fs2.readFileSync(file, "utf-8").slice(-2000).trim();
3453
+ if (tail)
3454
+ sections.push(`--- ${label} (tail) ---
3455
+ ${tail}`);
3456
+ } catch {}
3384
3457
  }
3458
+ return sections.length > 0 ? sections.join(`
3459
+ `) : undefined;
3385
3460
  }
3386
3461
  function defaultSpawnDetachedChild(childArgs) {
3387
3462
  const cwd = process.cwd();
@@ -3441,7 +3516,7 @@ function buildChildArgs(parentArgv) {
3441
3516
  }
3442
3517
  return [...stripInternalChildFlag(rest), INTERNAL_DETACHED_CHILD_FLAG];
3443
3518
  }
3444
- var INTERNAL_DETACHED_CHILD_FLAG = "--internal-detached-child", realPollDeps;
3519
+ var INTERNAL_DETACHED_CHILD_FLAG = "--internal-detached-child", DETACH_READY_TIMEOUT_MS = 630000, DETACH_WAITING_HEARTBEAT_MS = 15000, realPollDeps;
3445
3520
  var init_detach = __esm(() => {
3446
3521
  init_session();
3447
3522
  realPollDeps = {
@@ -3450,6 +3525,104 @@ var init_detach = __esm(() => {
3450
3525
  };
3451
3526
  });
3452
3527
 
3528
+ // src/dev/boot-await.ts
3529
+ function isPermanentStartError(err) {
3530
+ if (!(err instanceof Error))
3531
+ return false;
3532
+ const match = err.message.match(/^API error (\d{3})/);
3533
+ if (!match)
3534
+ return false;
3535
+ const status = Number.parseInt(match[1], 10);
3536
+ return status >= 400 && status < 500;
3537
+ }
3538
+ async function awaitDevSessionReady(opts) {
3539
+ const now = opts.deps?.now ?? (() => Date.now());
3540
+ const sleep = opts.deps?.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
3541
+ const timeoutMs = opts.timeoutMs ?? DEV_BOOT_TIMEOUT_MS;
3542
+ const heartbeatMs = opts.heartbeatMs ?? DEV_BOOT_HEARTBEAT_MS;
3543
+ const statusPollDelayMs = opts.statusPollDelayMs ?? DEV_BOOT_STATUS_POLL_DELAY_MS;
3544
+ const statusPollIntervalMs = opts.statusPollIntervalMs ?? DEV_BOOT_STATUS_POLL_INTERVAL_MS;
3545
+ const startedAt = now();
3546
+ const elapsed = () => now() - startedAt;
3547
+ const st = {
3548
+ startResult: null,
3549
+ startError: undefined,
3550
+ startSettled: false,
3551
+ permanentFailure: null,
3552
+ statusReady: null,
3553
+ statusInFlight: false
3554
+ };
3555
+ opts.start().then((session) => {
3556
+ st.startResult = session;
3557
+ st.startSettled = true;
3558
+ }, (err) => {
3559
+ st.startError = err;
3560
+ st.startSettled = true;
3561
+ if (isPermanentStartError(err)) {
3562
+ st.permanentFailure = err;
3563
+ } else {
3564
+ opts.onStartError?.(err);
3565
+ }
3566
+ });
3567
+ let lastStatusPollAt = -Infinity;
3568
+ let lastHeartbeatAt = startedAt;
3569
+ const readyResult = () => {
3570
+ if (st.startResult && st.startResult.previewUrl) {
3571
+ return { session: st.startResult, source: "start", elapsedMs: elapsed() };
3572
+ }
3573
+ if (st.statusReady) {
3574
+ return { session: st.statusReady, source: "status", elapsedMs: elapsed() };
3575
+ }
3576
+ return null;
3577
+ };
3578
+ while (elapsed() < timeoutMs) {
3579
+ if (st.permanentFailure)
3580
+ throw st.permanentFailure;
3581
+ const ready2 = readyResult();
3582
+ if (ready2)
3583
+ return ready2;
3584
+ const startFailedOrEmpty = st.startSettled && !(st.startResult && st.startResult.previewUrl);
3585
+ const pollingActive = elapsed() >= statusPollDelayMs || startFailedOrEmpty;
3586
+ if (pollingActive && !st.statusInFlight && now() - lastStatusPollAt >= statusPollIntervalMs) {
3587
+ st.statusInFlight = true;
3588
+ lastStatusPollAt = now();
3589
+ opts.pollStatus().then((session) => {
3590
+ st.statusInFlight = false;
3591
+ if (session.previewUrl)
3592
+ st.statusReady = session;
3593
+ }, () => {
3594
+ st.statusInFlight = false;
3595
+ });
3596
+ }
3597
+ if (now() - lastHeartbeatAt >= heartbeatMs) {
3598
+ lastHeartbeatAt = now();
3599
+ opts.onWaiting?.(elapsed());
3600
+ }
3601
+ await sleep(TICK_MS);
3602
+ }
3603
+ if (st.permanentFailure)
3604
+ throw st.permanentFailure;
3605
+ const ready = readyResult();
3606
+ if (ready)
3607
+ return ready;
3608
+ throw new DevBootTimeoutError(elapsed(), st.startError);
3609
+ }
3610
+ var DEV_BOOT_TIMEOUT_MS = 600000, DEV_BOOT_HEARTBEAT_MS = 15000, DEV_BOOT_STATUS_POLL_DELAY_MS = 45000, DEV_BOOT_STATUS_POLL_INTERVAL_MS = 1e4, DevBootTimeoutError, TICK_MS = 250;
3611
+ var init_boot_await = __esm(() => {
3612
+ DevBootTimeoutError = class DevBootTimeoutError extends Error {
3613
+ elapsedMs;
3614
+ startError;
3615
+ constructor(elapsedMs, startError) {
3616
+ const base = `Dev session did not become ready within ${Math.round(elapsedMs / 1000)}s`;
3617
+ const cause = startError instanceof Error ? ` (boot call failed: ${startError.message})` : "";
3618
+ super(`${base}${cause}`);
3619
+ this.name = "DevBootTimeoutError";
3620
+ this.elapsedMs = elapsedMs;
3621
+ this.startError = startError;
3622
+ }
3623
+ };
3624
+ });
3625
+
3453
3626
  // src/dev/stop.ts
3454
3627
  import * as fs3 from "fs";
3455
3628
  async function stopSession(appDir, expectedAppId, deps = {}) {
@@ -5652,6 +5825,13 @@ export interface JobRunRecord {
5652
5825
  completedAt?: number;
5653
5826
  result?: JobResult;
5654
5827
  error?: string;
5828
+ /**
5829
+ * Stack trace of the failure. Stored because the error MESSAGE alone made
5830
+ * the 2026-07 "Could not serialize object of type ..." production failures
5831
+ * undiagnosable: the message names the workerd serializer, not the framework
5832
+ * call site that hit it (AGENTCLI-6).
5833
+ */
5834
+ errorStack?: string;
5655
5835
  }
5656
5836
  export interface ScheduleState {
5657
5837
  name: string;
@@ -7381,7 +7561,7 @@ function createKeyboardListener() {
7381
7561
  }
7382
7562
 
7383
7563
  // src/generated/version.ts
7384
- var VERSION = "0.16.1";
7564
+ var VERSION = "0.17.1";
7385
7565
 
7386
7566
  // src/commands/dev.ts
7387
7567
  var exports_dev = {};
@@ -7515,7 +7695,64 @@ async function execDev(options) {
7515
7695
  } else {
7516
7696
  console.log(dim("Starting dev session..."));
7517
7697
  }
7518
- const session = await client.startDevSession(config.appId);
7698
+ let session;
7699
+ try {
7700
+ const boot = await awaitDevSessionReady({
7701
+ start: () => client.startDevSession(config.appId),
7702
+ pollStatus: () => client.getDevStatus(config.appId),
7703
+ onWaiting: (elapsedMs) => {
7704
+ const seconds = Math.round(elapsedMs / 1000);
7705
+ if (useJson) {
7706
+ jsonLine({
7707
+ event: "startup",
7708
+ phase: "dev_session_waiting",
7709
+ elapsedSeconds: seconds,
7710
+ timestamp: ts(),
7711
+ note: "Sandbox is booting. Cold boots can take a few minutes; keep waiting."
7712
+ });
7713
+ } else {
7714
+ console.log(dim(` Still starting the sandbox... (${seconds}s elapsed; cold boots can take a few minutes)`));
7715
+ }
7716
+ },
7717
+ onStartError: (err) => {
7718
+ const message = err instanceof Error ? err.message : String(err);
7719
+ if (useJson) {
7720
+ jsonLine({
7721
+ event: "startup",
7722
+ phase: "dev_session_retrying",
7723
+ timestamp: ts(),
7724
+ warning: `Boot call failed (${message}). The sandbox may still be booting server-side; watching status until it becomes ready.`
7725
+ });
7726
+ } else {
7727
+ console.warn(yellow(` Boot call failed (${message}); watching sandbox status in case the boot completes server-side...`));
7728
+ }
7729
+ }
7730
+ });
7731
+ session = boot.session;
7732
+ } catch (error) {
7733
+ const isTimeout = error instanceof DevBootTimeoutError;
7734
+ const message = error instanceof Error ? error.message : String(error);
7735
+ if (useJson) {
7736
+ jsonLine({
7737
+ event: "error",
7738
+ phase: "dev_session",
7739
+ timestamp: ts(),
7740
+ error: {
7741
+ message,
7742
+ diagnosis: isTimeout ? "The preview sandbox did not become ready within the boot deadline. The sandbox provider may be degraded, or the app failed to boot server-side." : "The dev session boot call failed.",
7743
+ suggestions: [
7744
+ "Re-run `runwork dev --detach --json` in about a minute; if the sandbox finished booting server-side it will be picked up quickly",
7745
+ "Run `runwork logs` to inspect sandbox logs for boot errors",
7746
+ "Run `runwork doctor` to verify auth and connectivity"
7747
+ ]
7748
+ }
7749
+ });
7750
+ } else {
7751
+ console.error(red(`Failed to start dev session: ${message}`));
7752
+ console.error(dim(" Re-run `runwork dev` in about a minute; if the sandbox finished booting server-side it will be picked up quickly."));
7753
+ }
7754
+ process.exit(1);
7755
+ }
7519
7756
  await populateSkill(cwd, client, config.appId);
7520
7757
  if (useJson)
7521
7758
  jsonLine({ event: "startup", phase: "skill_fetched", timestamp: ts() });
@@ -7804,7 +8041,21 @@ async function runDevDetachParent(opts) {
7804
8041
  const outcome = await runAsDetachedParent({
7805
8042
  appDir: cwd,
7806
8043
  expectedAppId: config.appId,
7807
- childArgs
8044
+ childArgs,
8045
+ onWaiting: (elapsedMs) => {
8046
+ const seconds = Math.round(elapsedMs / 1000);
8047
+ if (opts.json) {
8048
+ jsonLine({
8049
+ event: "waiting",
8050
+ phase: "sandbox_boot",
8051
+ elapsedSeconds: seconds,
8052
+ timestamp: ts(),
8053
+ note: "Sandbox is booting. Cold boots can take a few minutes; keep waiting for session_started."
8054
+ });
8055
+ } else {
8056
+ console.log(dim(` Still starting... (${seconds}s elapsed; cold boots can take a few minutes)`));
8057
+ }
8058
+ }
7808
8059
  });
7809
8060
  switch (outcome.result) {
7810
8061
  case "started": {
@@ -7862,9 +8113,10 @@ async function runDevDetachParent(opts) {
7862
8113
  timestamp: ts(),
7863
8114
  error: {
7864
8115
  message,
7865
- diagnosis: "The detached child process exited (typically due to missing auth, missing git, network failure, or a sync conflict) before it could publish a preview URL.",
8116
+ diagnosis: "The detached child process exited (typically due to missing auth, missing git, network failure, a sync conflict, or a sandbox boot timeout) before it could publish a preview URL.",
7866
8117
  suggestions: [
7867
- "Inspect the child stderr at .runwork/dev-stderr.log",
8118
+ "Check childLogTail below; the child logs structured errors to .runwork/dev-stdout.log (stderr carries crash traces)",
8119
+ "If the child hit a sandbox boot timeout, re-run `runwork dev --detach --json` in about a minute; a boot that completed server-side is picked up quickly",
7868
8120
  "Run `runwork doctor` to verify auth and connectivity",
7869
8121
  "Try `runwork dev` (foreground) to see the failure inline"
7870
8122
  ],
@@ -7873,7 +8125,7 @@ async function runDevDetachParent(opts) {
7873
8125
  });
7874
8126
  } else {
7875
8127
  console.error(yellow(message));
7876
- console.error(dim(" Inspect .runwork/dev-stderr.log or run `runwork dev` to see what failed."));
8128
+ console.error(dim(" Inspect .runwork/dev-stdout.log and dev-stderr.log, or run `runwork dev` to see what failed."));
7877
8129
  if (outcome.childLogTail) {
7878
8130
  console.error(dim(" Tail of child stderr:"));
7879
8131
  for (const line of outcome.childLogTail.split(`
@@ -7887,7 +8139,8 @@ async function runDevDetachParent(opts) {
7887
8139
  return;
7888
8140
  }
7889
8141
  case "timeout": {
7890
- const message = `Detached dev session did not become ready within 90s.`;
8142
+ const timeoutSeconds = Math.round(DETACH_READY_TIMEOUT_MS / 1000);
8143
+ const message = `Detached dev session did not become ready within ${timeoutSeconds}s.`;
7891
8144
  if (opts.json) {
7892
8145
  jsonLine({
7893
8146
  event: "error",
@@ -7895,9 +8148,10 @@ async function runDevDetachParent(opts) {
7895
8148
  timestamp: ts(),
7896
8149
  error: {
7897
8150
  message,
7898
- diagnosis: "The detached child process was spawned but never wrote a session file with a preview URL. The sandbox boot may have failed.",
8151
+ diagnosis: "The detached child process was spawned but never wrote a session file with a preview URL. The sandbox boot may have failed or the sandbox provider may be degraded.",
7899
8152
  suggestions: [
7900
- "Inspect the child stderr at .runwork/dev-stderr.log",
8153
+ "The sandbox may still finish booting server-side: re-run `runwork dev --detach --json` in about a minute; a completed boot is picked up quickly",
8154
+ "Inspect the child logs at .runwork/dev-stdout.log and .runwork/dev-stderr.log",
7901
8155
  "Run `runwork doctor` to verify auth and connectivity",
7902
8156
  "Try `runwork dev` (foreground) to see the failure inline"
7903
8157
  ],
@@ -7906,7 +8160,7 @@ async function runDevDetachParent(opts) {
7906
8160
  });
7907
8161
  } else {
7908
8162
  console.error(yellow(message));
7909
- console.error(dim(" Inspect .runwork/dev-stderr.log or run `runwork dev` to see what failed."));
8163
+ console.error(dim(" Inspect .runwork/dev-stdout.log and dev-stderr.log, or run `runwork dev` to see what failed."));
7910
8164
  if (outcome.childLogTail) {
7911
8165
  console.error(dim(" Tail of child stderr:"));
7912
8166
  for (const line of outcome.childLogTail.split(`
@@ -7956,6 +8210,7 @@ var init_dev = __esm(() => {
7956
8210
  init_tailer();
7957
8211
  init_session();
7958
8212
  init_detach();
8213
+ init_boot_await();
7959
8214
  init_stop();
7960
8215
  init_attach();
7961
8216
  init_types_manager();
@@ -8291,7 +8546,7 @@ var init_welcome = __esm(() => {
8291
8546
  });
8292
8547
 
8293
8548
  // src/index.ts
8294
- import { Command as Command34 } from "commander";
8549
+ import { Command as Command36 } from "commander";
8295
8550
 
8296
8551
  // src/commands/login.ts
8297
8552
  init_login_flow();
@@ -8394,8 +8649,23 @@ function getDeploySummary(cwd) {
8394
8649
  }
8395
8650
 
8396
8651
  // src/deploy/deploy-status.ts
8652
+ init_session();
8397
8653
  import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync12 } from "fs";
8398
8654
  import { dirname as dirname4, join as join17 } from "path";
8655
+ function evaluateDeployStatus(status, deps = {}) {
8656
+ if (status.state !== "in-progress") {
8657
+ return { status, effectiveState: status.state };
8658
+ }
8659
+ const bootTime = deps.bootTime ?? currentBootTime;
8660
+ const pidAlive = deps.pidAlive ?? isPidAlive;
8661
+ if (typeof status.bootTime === "number" && Math.abs(bootTime() - status.bootTime) > BOOT_TIME_TOLERANCE_MS) {
8662
+ return { status, effectiveState: "stale", staleReason: "boot-time-mismatch" };
8663
+ }
8664
+ if (typeof status.pid === "number" && !pidAlive(status.pid)) {
8665
+ return { status, effectiveState: "stale", staleReason: "process-gone" };
8666
+ }
8667
+ return { status, effectiveState: "in-progress" };
8668
+ }
8399
8669
  function statusPath(cwd) {
8400
8670
  return join17(cwd, ".runwork", "deploy-status.json");
8401
8671
  }
@@ -8425,6 +8695,9 @@ function readDeployStatus(cwd) {
8425
8695
  }
8426
8696
  }
8427
8697
 
8698
+ // src/commands/deploy.ts
8699
+ init_session();
8700
+
8428
8701
  // src/deploy/detach.ts
8429
8702
  init_detach();
8430
8703
  import * as fs5 from "fs";
@@ -8536,7 +8809,7 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
8536
8809
  if (opts.detach && !isChild) {
8537
8810
  const startedAt = new Date().toISOString();
8538
8811
  const handle = spawnDetachedDeploy(buildDeployChildArgs(process.argv), cwd);
8539
- writeDeployStatus(cwd, { state: "in-progress", startedAt, pid: handle.pid });
8812
+ writeDeployStatus(cwd, { state: "in-progress", startedAt, pid: handle.pid, bootTime: currentBootTime() });
8540
8813
  if (useJson) {
8541
8814
  jsonLine({ event: "deploy_started", detached: true, pid: handle.pid, logPath: deployLogPath(cwd), startedAt, hint: "Poll `runwork deploy --status` for completion." });
8542
8815
  } else {
@@ -8639,7 +8912,7 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
8639
8912
  const deployed = deploymentUrl.length > 0;
8640
8913
  if (!deployed) {
8641
8914
  if (useJson) {
8642
- jsonOut(buildErrorResponse("deploy", "Deployment did not produce a URL", "The deploy API returned no deployment URL. The deploy may have been a no-op (no changes) or failed server-side.", ["Confirm your changes were committed and pushed (the sync step above succeeded)", "Run runwork info to check the deployed state", "Run runwork logs --production to inspect server-side errors", "Retry runwork deploy"]));
8915
+ jsonOut(buildErrorResponse("deploy", "Deployment did not produce a URL", "The deploy API returned no deployment URL. The deploy may have been a no-op (no changes) or failed server-side (e.g. a build error).", ['Run `runwork logs --events --once` and look for a "Build failed" error in recent deployment events', "If a build failed on stale sandbox files, run `runwork dev --restart --detach` to resync, then retry the deploy", "Confirm your changes were committed and pushed (the sync step above succeeded)", "Run runwork info to check the deployed state", "Retry runwork deploy"]));
8643
8916
  process.exit(1);
8644
8917
  }
8645
8918
  console.error("Deploy did not return a URL. The deploy may have been a no-op or failed server-side.");
@@ -8679,14 +8952,24 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
8679
8952
  function printDeployStatus(cwd, useJson) {
8680
8953
  const status = readDeployStatus(cwd);
8681
8954
  const summary = getDeploySummary(cwd);
8955
+ const evaluated = status ? evaluateDeployStatus(status) : null;
8956
+ const staleHint = "The deploy process died before recording an outcome. The deploy may still have completed server-side (the push precedes the deploy call). Verify with `runwork logs --production --once` or `runwork apps info`, then re-run `runwork deploy` if needed.";
8682
8957
  if (useJson) {
8683
- jsonOut({ success: true, command: "deploy", result: { status, deploy: summary } });
8958
+ jsonOut({
8959
+ success: true,
8960
+ command: "deploy",
8961
+ result: {
8962
+ status: evaluated ? { ...evaluated.status, state: evaluated.effectiveState, staleReason: evaluated.staleReason } : null,
8963
+ deploy: summary,
8964
+ hint: evaluated?.effectiveState === "stale" ? staleHint : undefined
8965
+ }
8966
+ });
8684
8967
  return;
8685
8968
  }
8686
- if (!status) {
8969
+ if (!status || !evaluated) {
8687
8970
  console.log("No deploy has been started from this machine yet.");
8688
8971
  } else {
8689
- const label = status.state === "succeeded" ? green("succeeded") : status.state === "failed" ? yellow("failed") : "in-progress";
8972
+ const label = evaluated.effectiveState === "succeeded" ? green("succeeded") : evaluated.effectiveState === "failed" ? yellow("failed") : evaluated.effectiveState === "stale" ? yellow(`stale (${evaluated.staleReason})`) : "in-progress";
8690
8973
  console.log(`Last deploy: ${label}`);
8691
8974
  console.log(dim(` started: ${status.startedAt}`));
8692
8975
  if (status.finishedAt)
@@ -8695,8 +8978,10 @@ function printDeployStatus(cwd, useJson) {
8695
8978
  console.log(dim(` url: ${status.url}`));
8696
8979
  if (status.error)
8697
8980
  console.log(yellow(` error: ${status.error}`));
8698
- if (status.state === "in-progress")
8981
+ if (evaluated.effectiveState === "in-progress")
8699
8982
  console.log(dim(` logs: ${deployLogPath(cwd)}`));
8983
+ if (evaluated.effectiveState === "stale")
8984
+ console.log(yellow(` ${staleHint}`));
8700
8985
  }
8701
8986
  if (summary.deployedShortSha) {
8702
8987
  const sync = summary.inSync === true ? green("in sync") : summary.inSync === false ? yellow("local has undeployed commits") : dim("unknown");
@@ -9280,9 +9565,14 @@ import { readFileSync as readFileSync18, existsSync as existsSync21 } from "fs";
9280
9565
  init_store();
9281
9566
  init_prompt();
9282
9567
  import { existsSync as existsSync20, readFileSync as readFileSync17 } from "fs";
9568
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
9283
9569
  async function resolveWorkspace2(client, options = {}) {
9284
9570
  if (options.workspace) {
9285
- return { workspaceId: options.workspace, workspaceName: "", source: "flag" };
9571
+ if (UUID_RE.test(options.workspace)) {
9572
+ return { workspaceId: options.workspace, workspaceName: "", source: "flag" };
9573
+ }
9574
+ const ws2 = await resolveWorkspace(client, options.workspace);
9575
+ return { workspaceId: ws2.id, workspaceName: ws2.name, source: "flag" };
9286
9576
  }
9287
9577
  if (process.env.WORKSPACE_ID) {
9288
9578
  return { workspaceId: process.env.WORKSPACE_ID, workspaceName: "", source: "flag" };
@@ -9317,6 +9607,10 @@ async function resolveWorkspace2(client, options = {}) {
9317
9607
  saveDefaultWorkspace(ws2.id, ws2.name);
9318
9608
  return { workspaceId: ws2.id, workspaceName: ws2.name, source: "prompt" };
9319
9609
  }
9610
+ if (!process.stdout.isTTY) {
9611
+ console.error(`Workspace required. Pass --workspace <name-or-id>. Available: ${workspaces.map((w) => w.name).join(", ")}`);
9612
+ process.exit(1);
9613
+ }
9320
9614
  const choices = workspaces.map((ws2) => ({ label: ws2.name, value: ws2.id }));
9321
9615
  const chosen = await promptSelect("Select a workspace:", choices);
9322
9616
  const ws = workspaces.find((w) => w.id === chosen.value);
@@ -9545,7 +9839,13 @@ async function parseCurlToRequest(curlStr) {
9545
9839
  }
9546
9840
 
9547
9841
  // src/commands/integrations.ts
9548
- var searchCommand = new Command10("search").description("Search available integrations from the platform catalog").argument("<query>", 'Search query (e.g., "google drive", "hubspot", "slack")').option("--limit <n>", "Maximum results to show", "20").option("--workspace <id>", "Workspace ID (enables team usage info)").action(async (query, opts, command) => {
9842
+ function resolveIntegrationConnection(integrations, integration) {
9843
+ const match = integrations.find((i) => (i.canonicalId || i.integrationId) === integration || i.provider === integration);
9844
+ if (!match)
9845
+ return null;
9846
+ return { connectionId: match.id || match.integrationId, match };
9847
+ }
9848
+ var searchCommand = new Command10("search").description("Search available integrations from the platform catalog").argument("<query>", 'Search query (e.g., "google drive", "hubspot", "slack")').option("--limit <n>", "Maximum results to show", "20").option("--workspace <name-or-id>", "Workspace name or ID (enables team usage info)").action(async (query, opts, command) => {
9549
9849
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
9550
9850
  const credentials = requireAuth();
9551
9851
  const client = new ApiClient(credentials);
@@ -9604,7 +9904,7 @@ Usage: Add the integration ID to APP_INTEGRATION_REQUIREMENTS in worker/integrat
9604
9904
  process.exit(1);
9605
9905
  }
9606
9906
  });
9607
- var listCommand = new Command10("list").description("List connected workspace integrations").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
9907
+ var listCommand = new Command10("list").description("List connected workspace integrations").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
9608
9908
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
9609
9909
  const credentials = requireAuth();
9610
9910
  const client = new ApiClient(credentials);
@@ -9657,7 +9957,7 @@ Used by your team (${teamOnly.length} more):
9657
9957
  process.exit(1);
9658
9958
  }
9659
9959
  });
9660
- var callCommand = new Command10("call").description("Make a proxy call to a connected integration").argument("<integration>", "Integration name (e.g., hubspot, slack)").argument("[method]", "HTTP method (GET, POST, PUT, DELETE)").argument("[path]", "API path (e.g., /crm/v3/contacts)").option("--workspace <id>", "Workspace ID").option("--body <json>", "Request body JSON").option("--header <header>", "Request header (repeatable)", (val, prev) => [...prev, val], []).option("--query <query>", "Query string (e.g., limit=10&offset=0)").option("--curl <command>", 'Parse a curl command (paste from Chrome DevTools "Copy as cURL")').option("--curl-file <file>", "Read curl command from a file").action(async (integration, method, path2, opts, command) => {
9960
+ var callCommand = new Command10("call").description("Make a proxy call to a connected integration").argument("<integration>", "Integration name (e.g., hubspot, slack)").argument("[method]", "HTTP method (GET, POST, PUT, DELETE)").argument("[path]", "API path (e.g., /crm/v3/contacts)").option("--workspace <name-or-id>", "Workspace name or ID").option("--body <json>", "Request body JSON").option("--header <header>", "Request header (repeatable)", (val, prev) => [...prev, val], []).option("--query <query>", "Query string (e.g., limit=10&offset=0)").option("--curl <command>", 'Parse a curl command (paste from Chrome DevTools "Copy as cURL")').option("--curl-file <file>", "Read curl command from a file").action(async (integration, method, path2, opts, command) => {
9661
9961
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
9662
9962
  const credentials = requireAuth();
9663
9963
  const client = new ApiClient(credentials);
@@ -9711,12 +10011,12 @@ var callCommand = new Command10("call").description("Make a proxy call to a conn
9711
10011
  }
9712
10012
  try {
9713
10013
  const integrations = await client.listConnectedIntegrations(workspaceId);
9714
- const match = integrations.find((i) => (i.canonicalId || i.integrationId) === integration || i.provider === integration);
9715
- if (!match) {
10014
+ const resolved = resolveIntegrationConnection(integrations, integration);
10015
+ if (!resolved) {
9716
10016
  console.error(`Integration "${integration}" not found. Run "runwork integrations list" to see connected integrations.`);
9717
10017
  process.exit(1);
9718
10018
  }
9719
- const result = await client.callIntegrationProxy(match.integrationId, finalMethod, finalPath, { body, headers: Object.keys(headers).length > 0 ? headers : undefined, query });
10019
+ const result = await client.callIntegrationProxy(resolved.connectionId, finalMethod, finalPath, { body, headers: Object.keys(headers).length > 0 ? headers : undefined, query });
9720
10020
  if (useJson) {
9721
10021
  jsonOut(result);
9722
10022
  return;
@@ -10345,7 +10645,7 @@ function truncate(text2, max) {
10345
10645
  return first;
10346
10646
  return first.slice(0, max - 3) + "...";
10347
10647
  }
10348
- var listCommand2 = new Command13("list").description("List workspace skills (app, external, MCP-generated)").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
10648
+ var listCommand2 = new Command13("list").description("List workspace skills (app, external, MCP-generated)").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
10349
10649
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
10350
10650
  const credentials = requireAuth();
10351
10651
  const client = new ApiClient(credentials);
@@ -10402,7 +10702,7 @@ async function readStdin2() {
10402
10702
  }
10403
10703
  return Buffer.concat(chunks).toString("utf-8");
10404
10704
  }
10405
- var pushCommand = new Command13("push").description("Upload a local skill file to workspace (upsert by name). Accepts piped content via stdin.").argument("[first]", "Skill name (if piping content) or file path (name from frontmatter)").argument("[second]", "File path (when first arg is the skill name)").option("--workspace <id>", "Workspace ID").action(async (first, second, opts, command) => {
10705
+ var pushCommand = new Command13("push").description("Upload a local skill file to workspace (upsert by name). Accepts piped content via stdin.").argument("[first]", "Skill name (if piping content) or file path (name from frontmatter)").argument("[second]", "File path (when first arg is the skill name)").option("--workspace <name-or-id>", "Workspace name or ID").action(async (first, second, opts, command) => {
10406
10706
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
10407
10707
  const credentials = requireAuth();
10408
10708
  const client = new ApiClient(credentials);
@@ -10497,7 +10797,7 @@ var pushCommand = new Command13("push").description("Upload a local skill file t
10497
10797
  process.exit(1);
10498
10798
  }
10499
10799
  });
10500
- var pullCommand = new Command13("pull").alias("download").description("Download a skill from the workspace and print to stdout (or save to file)").argument("<name>", "Skill name or ID").option("--workspace <id>", "Workspace ID").option("-o, --output <file>", "Save to file instead of stdout").action(async (nameOrId, opts, command) => {
10800
+ var pullCommand = new Command13("pull").alias("download").description("Download a skill from the workspace and print to stdout (or save to file)").argument("<name>", "Skill name or ID").option("--workspace <name-or-id>", "Workspace name or ID").option("-o, --output <file>", "Save to file instead of stdout").action(async (nameOrId, opts, command) => {
10501
10801
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
10502
10802
  const credentials = requireAuth();
10503
10803
  const client = new ApiClient(credentials);
@@ -10600,7 +10900,7 @@ var searchCommand2 = new Command13("search").description("Search community skill
10600
10900
  process.exit(1);
10601
10901
  }
10602
10902
  });
10603
- var installCommand = new Command13("install").description("Install a community skill from skills.sh into your workspace").argument("<identifier>", "Skill identifier: source/skill-id (e.g. vercel-labs/skills/find-skills)").option("--workspace <id>", "Workspace ID").action(async (identifier, opts, command) => {
10903
+ var installCommand = new Command13("install").description("Install a community skill from skills.sh into your workspace").argument("<identifier>", "Skill identifier: source/skill-id (e.g. vercel-labs/skills/find-skills)").option("--workspace <name-or-id>", "Workspace name or ID").action(async (identifier, opts, command) => {
10604
10904
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
10605
10905
  const credentials = requireAuth();
10606
10906
  const client = new ApiClient(credentials);
@@ -11118,6 +11418,33 @@ function formatCombinedDigest(sessions, opts = { days: 7 }) {
11118
11418
  // src/agents/utils/json-config.ts
11119
11419
  import { readFileSync as readFileSync22, writeFileSync as writeFileSync14, mkdirSync as mkdirSync13, existsSync as existsSync25 } from "fs";
11120
11420
  import { dirname as dirname5 } from "path";
11421
+
11422
+ // src/sync/hash.ts
11423
+ import { createHash as createHash2 } from "crypto";
11424
+ function contentHash(content) {
11425
+ return "sha256:" + createHash2("sha256").update(content).digest("hex");
11426
+ }
11427
+ function stableStringify(value) {
11428
+ return JSON.stringify(sortValue(value));
11429
+ }
11430
+ function sortValue(value) {
11431
+ if (Array.isArray(value))
11432
+ return value.map(sortValue);
11433
+ if (value !== null && typeof value === "object") {
11434
+ const obj = value;
11435
+ const sorted = {};
11436
+ for (const key of Object.keys(obj).sort()) {
11437
+ sorted[key] = sortValue(obj[key]);
11438
+ }
11439
+ return sorted;
11440
+ }
11441
+ return value;
11442
+ }
11443
+ function configHash(value) {
11444
+ return contentHash(stableStringify(value));
11445
+ }
11446
+
11447
+ // src/agents/utils/json-config.ts
11121
11448
  function isRunworkManagedKey(key) {
11122
11449
  return key === RUNWORK_WORKSPACE_MCP_NAME || key.startsWith(RUNWORK_MCP_PREFIX) || key.startsWith(RUNWORK_MCP_PREFIX_LEGACY);
11123
11450
  }
@@ -11156,6 +11483,14 @@ function removeRunworkMcpServers(filePath, topKey) {
11156
11483
  function mergeJsonMcpServers(filePath, servers, topKey) {
11157
11484
  const config = readJsonConfig(filePath);
11158
11485
  const existing = config[topKey] || {};
11486
+ const managedBefore = {};
11487
+ for (const key of Object.keys(existing)) {
11488
+ if (isRunworkManagedKey(key))
11489
+ managedBefore[key] = existing[key];
11490
+ }
11491
+ const changed = configHash(managedBefore) !== configHash(servers);
11492
+ if (!changed)
11493
+ return false;
11159
11494
  for (const key of Object.keys(existing)) {
11160
11495
  if (isRunworkManagedKey(key)) {
11161
11496
  delete existing[key];
@@ -11164,6 +11499,7 @@ function mergeJsonMcpServers(filePath, servers, topKey) {
11164
11499
  Object.assign(existing, servers);
11165
11500
  config[topKey] = existing;
11166
11501
  writeJsonConfig(filePath, config);
11502
+ return true;
11167
11503
  }
11168
11504
 
11169
11505
  // src/agents/utils/instruction-hint.ts
@@ -11487,6 +11823,19 @@ function resolveAgentDefaults(input) {
11487
11823
  };
11488
11824
  }
11489
11825
 
11826
+ // src/ui/verbosity.ts
11827
+ var verbose = false;
11828
+ function setVerbose(value) {
11829
+ verbose = value;
11830
+ }
11831
+ function isVerbose() {
11832
+ return verbose;
11833
+ }
11834
+ function vlog(...args) {
11835
+ if (verbose)
11836
+ console.log(...args);
11837
+ }
11838
+
11490
11839
  // src/agents/claude-code.ts
11491
11840
  var PLUGIN_NAME = "runwork";
11492
11841
  var PLUGIN_VERSION = "1.0.0";
@@ -11589,7 +11938,7 @@ class ClaudeCodeAdapter {
11589
11938
  ]
11590
11939
  };
11591
11940
  writeFileSync16(join21(hooksDir, "hooks.json"), JSON.stringify(hooksManifest, null, 2));
11592
- console.log(` [Claude Code] Installed SessionStart hook (${label}) -> ${scriptPath}`);
11941
+ vlog(` [Claude Code] Installed SessionStart hook (${label}) -> ${scriptPath}`);
11593
11942
  }
11594
11943
  async writeSkills(skills, scope) {
11595
11944
  const baseDir = scope === "project" ? join21(process.cwd(), ".claude", "skills") : join21(homedir5(), ".claude", "skills");
@@ -11622,6 +11971,7 @@ class ClaudeCodeAdapter {
11622
11971
  }
11623
11972
  this.registerPlugin(pluginDir);
11624
11973
  }
11974
+ return skills.length;
11625
11975
  }
11626
11976
  async writeBuiltInHooks(scope) {
11627
11977
  if (scope !== "user")
@@ -12116,7 +12466,7 @@ ${instructions}`;
12116
12466
  lastUpdated: new Date().toISOString()
12117
12467
  };
12118
12468
  writeJsonConfig(marketplacesPath, marketplaces);
12119
- console.log(` [Claude Code] ${wasRegistered ? "Refreshed" : "Registered"} plugin ` + `${pluginKey} v${PLUGIN_VERSION} -> ${installedPath}`);
12469
+ vlog(` [Claude Code] ${wasRegistered ? "Refreshed" : "Registered"} plugin ` + `${pluginKey} v${PLUGIN_VERSION} -> ${installedPath}`);
12120
12470
  const settingsPath = join21(homedir5(), ".claude", "settings.json");
12121
12471
  const settings = readJsonConfig(settingsPath);
12122
12472
  if (!settings["enabledPlugins"]) {
@@ -12416,12 +12766,12 @@ class ClaudeDesktopAdapter {
12416
12766
  if (rpm) {
12417
12767
  writePluginMetadata(rpm.pluginPath, getPluginMetadata());
12418
12768
  writePluginSkills(rpm.pluginPath, skills);
12419
- return;
12769
+ return skills.length;
12420
12770
  }
12421
12771
  const pluginsDir = findCoworkPluginsDir();
12422
12772
  if (!pluginsDir) {
12423
12773
  console.warn(" [Claude Desktop] Cowork plugins directory not found. Open Claude Desktop at least once first.");
12424
- return;
12774
+ return 0;
12425
12775
  }
12426
12776
  const cacheDir = join23(pluginsDir, "cache", "runwork", PLUGIN_NAME2, PLUGIN_VERSION2);
12427
12777
  const marketRoot = join23(pluginsDir, "marketplaces", "runwork");
@@ -12456,6 +12806,7 @@ class ClaudeDesktopAdapter {
12456
12806
  };
12457
12807
  writeJsonConfig(marketplacesPath, marketplaces);
12458
12808
  setCoworkPluginEnabled(pluginsDir, true);
12809
+ return skills.length;
12459
12810
  }
12460
12811
  async writeInstructionHint(hint, _scope) {
12461
12812
  for (const path2 of getCoworkMemoryClaudeMdPaths()) {
@@ -12946,7 +13297,7 @@ class CursorAdapter {
12946
13297
  }
12947
13298
  async writeSkills(skills, scope) {
12948
13299
  if (scope === "user")
12949
- return;
13300
+ return 0;
12950
13301
  const rulesDir = join24(process.cwd(), ".cursor", "rules");
12951
13302
  mkdirSync18(rulesDir, { recursive: true });
12952
13303
  for (const skill of skills) {
@@ -12958,6 +13309,7 @@ alwaysApply: false
12958
13309
  ${skill.content}`;
12959
13310
  writeFileSync19(join24(rulesDir, `${skill.filename}.mdc`), mdcContent);
12960
13311
  }
13312
+ return skills.length;
12961
13313
  }
12962
13314
  async writeInstructionHint(hint, scope) {
12963
13315
  const filePath = scope === "project" ? join24(process.cwd(), ".cursor", "rules", "runwork.mdc") : join24(homedir7(), ".cursor", "rules", "runwork.mdc");
@@ -13210,7 +13562,7 @@ class WindsurfAdapter {
13210
13562
  }
13211
13563
  async writeSkills(skills, scope) {
13212
13564
  if (scope === "user")
13213
- return;
13565
+ return 0;
13214
13566
  const rulesDir = join25(process.cwd(), ".windsurf", "rules");
13215
13567
  mkdirSync19(rulesDir, { recursive: true });
13216
13568
  for (const skill of skills) {
@@ -13221,6 +13573,7 @@ trigger: manual
13221
13573
  ${skill.content}`;
13222
13574
  writeFileSync20(join25(rulesDir, `${skill.filename}.md`), content);
13223
13575
  }
13576
+ return skills.length;
13224
13577
  }
13225
13578
  async writeTeamInstructions(instructions, scope) {
13226
13579
  const filePath = scope === "project" ? join25(process.cwd(), ".windsurf", "rules", "runwork-team.md") : join25(getWindsurfDataDir(), "rules", "runwork-team.md");
@@ -13464,14 +13817,15 @@ var AGENT_REGISTRY = [
13464
13817
  method: "any",
13465
13818
  target: [
13466
13819
  { method: "path", target: { macos: "/Applications/Codex.app" } },
13467
- { method: "windows-appx", target: "OpenAI.Codex" }
13820
+ { method: "macos-bundle-id", target: "com.openai.codex" },
13821
+ { method: "windows-appx", target: ["OpenAI.Codex"] }
13468
13822
  ]
13469
13823
  },
13470
- launch: { app: { macos: "Codex", windows: "Codex" } },
13471
- logo: "openai",
13824
+ launch: { app: { macos: "Codex", windows: "Codex" }, bundleId: { macos: "com.openai.codex" } },
13825
+ logo: "codex",
13472
13826
  downloadUrl: "https://openai.com/codex/",
13473
13827
  skillsPaths: { global: ".codex/skills", project: ".agents/skills" },
13474
- instructionFile: { global: ".codex/instructions.md", project: "AGENTS.md" },
13828
+ instructionFile: { global: ".codex/AGENTS.md", project: "AGENTS.md" },
13475
13829
  firstClass: true,
13476
13830
  resumeCapability: {
13477
13831
  mode: "file-drop-only",
@@ -13493,10 +13847,10 @@ var AGENT_REGISTRY = [
13493
13847
  category: "cli",
13494
13848
  detection: { method: "binary", target: "codex" },
13495
13849
  launch: { cli: "codex", cliAcceptsPrompt: true },
13496
- logo: "openai",
13850
+ logo: "codex",
13497
13851
  downloadUrl: "https://github.com/openai/codex",
13498
13852
  skillsPaths: { global: ".codex/skills", project: ".agents/skills" },
13499
- instructionFile: { global: ".codex/instructions.md", project: "AGENTS.md" },
13853
+ instructionFile: { global: ".codex/AGENTS.md", project: "AGENTS.md" },
13500
13854
  firstClass: true,
13501
13855
  resumeCapability: {
13502
13856
  mode: "cli-resume",
@@ -13590,10 +13944,11 @@ var AGENT_REGISTRY = [
13590
13944
  method: "any",
13591
13945
  target: [
13592
13946
  { method: "path", target: { macos: "/Applications/ChatGPT.app" } },
13593
- { method: "windows-appx", target: "OpenAI.ChatGPT" }
13947
+ { method: "macos-bundle-id", target: "com.openai.chat" },
13948
+ { method: "windows-appx", target: ["OpenAI.ChatGPT"] }
13594
13949
  ]
13595
13950
  },
13596
- launch: { app: { macos: "ChatGPT", windows: "ChatGPT" }, url: "https://chatgpt.com/?prompt={prompt}" },
13951
+ launch: { app: { macos: "ChatGPT", windows: "ChatGPT" }, bundleId: { macos: "com.openai.chat" }, url: "https://chatgpt.com/?prompt={prompt}" },
13597
13952
  logo: "openai",
13598
13953
  downloadUrl: "https://chatgpt.com/download",
13599
13954
  firstClass: true,
@@ -13883,14 +14238,28 @@ function resolveToAbsolute(ps, scope) {
13883
14238
  return scope === "global" ? join26(homedir9(), resolved) : join26(process.cwd(), resolved);
13884
14239
  }
13885
14240
 
14241
+ // src/agents/detection-probes.ts
14242
+ function powershellQuote(value) {
14243
+ return `'${value.replace(/'/g, "''")}'`;
14244
+ }
14245
+ function isValidBundleId(id) {
14246
+ return /^[A-Za-z0-9][A-Za-z0-9.-]*$/.test(id);
14247
+ }
14248
+ function macosBundleIdProbeScript(id) {
14249
+ return `p=$(mdfind "kMDItemCFBundleIdentifier == '${id}'" 2>/dev/null | head -1); ` + `if [ -n "$p" ]; then exit 0; fi; ` + `for a in /Applications/*.app "$HOME"/Applications/*.app; do ` + `[ -e "$a" ] || continue; ` + `if [ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$a/Contents/Info.plist" 2>/dev/null)" = "${id}" ]; then exit 0; fi; ` + `done; exit 1`;
14250
+ }
14251
+ function appxPackageProbeScript(pkg) {
14252
+ return `$p = Get-AppxPackage -Name ${powershellQuote(pkg)} -ErrorAction SilentlyContinue; if ($null -ne $p) { exit 0 } exit 1`;
14253
+ }
14254
+ function startAppProbeScript(pattern) {
14255
+ return `$a = Get-StartApps -Name ${powershellQuote(pattern)} -ErrorAction SilentlyContinue | Select-Object -First 1; if ($null -ne $a) { exit 0 } exit 1`;
14256
+ }
14257
+
13886
14258
  // src/agents/detection.ts
13887
14259
  var execFileAsync = promisify(execFile);
13888
14260
  function isWindows() {
13889
14261
  return platform7() === "win32";
13890
14262
  }
13891
- function powershellQuote(value) {
13892
- return `'${value.replace(/'/g, "''")}'`;
13893
- }
13894
14263
  function toList(value) {
13895
14264
  return Array.isArray(value) ? value : [value];
13896
14265
  }
@@ -13910,23 +14279,30 @@ function checkPath(target) {
13910
14279
  return existsSync32(resolved);
13911
14280
  return existsSync32(join27(homedir10(), resolved));
13912
14281
  }
14282
+ async function checkMacosBundleId(target) {
14283
+ if (platform7() !== "darwin")
14284
+ return false;
14285
+ for (const id of toList(target)) {
14286
+ if (!isValidBundleId(id))
14287
+ continue;
14288
+ try {
14289
+ await execFileAsync("sh", ["-c", macosBundleIdProbeScript(id)]);
14290
+ return true;
14291
+ } catch {}
14292
+ }
14293
+ return false;
14294
+ }
13913
14295
  async function checkWindowsAppxPackage(target) {
13914
14296
  if (!isWindows())
13915
14297
  return false;
13916
- const probes = toList(target).map((pkg) => {
13917
- const script = `$p = Get-AppxPackage -Name ${powershellQuote(pkg)} -ErrorAction SilentlyContinue; if ($null -ne $p) { exit 0 } exit 1`;
13918
- return runPowerShell(script);
13919
- });
14298
+ const probes = toList(target).map((pkg) => runPowerShell(appxPackageProbeScript(pkg)));
13920
14299
  const results = await Promise.all(probes);
13921
14300
  return results.some(Boolean);
13922
14301
  }
13923
14302
  async function checkWindowsStartApp(target) {
13924
14303
  if (!isWindows())
13925
14304
  return false;
13926
- const probes = toList(target).map((pattern) => {
13927
- const script = `$a = Get-StartApps -Name ${powershellQuote(pattern)} -ErrorAction SilentlyContinue | Select-Object -First 1; if ($null -ne $a) { exit 0 } exit 1`;
13928
- return runPowerShell(script);
13929
- });
14305
+ const probes = toList(target).map((pattern) => runPowerShell(startAppProbeScript(pattern)));
13930
14306
  const results = await Promise.all(probes);
13931
14307
  return results.some(Boolean);
13932
14308
  }
@@ -13942,6 +14318,8 @@ async function runAgentDetection(detection) {
13942
14318
  return checkWindowsAppxPackage(detection.target);
13943
14319
  case "windows-start-app":
13944
14320
  return checkWindowsStartApp(detection.target);
14321
+ case "macos-bundle-id":
14322
+ return checkMacosBundleId(detection.target);
13945
14323
  case "any": {
13946
14324
  const probes = await Promise.all(detection.target.map((p) => runAgentDetection(p)));
13947
14325
  return probes.some(Boolean);
@@ -14016,6 +14394,7 @@ class CodexAdapter {
14016
14394
  mkdirSync20(skillDir, { recursive: true });
14017
14395
  writeFileSync21(join28(skillDir, "SKILL.md"), buildSkillMd2(skill));
14018
14396
  }
14397
+ return skills.length;
14019
14398
  }
14020
14399
  async writeInstructionHint(hint, scope) {
14021
14400
  const filePath = scope === "project" ? join28(process.cwd(), "AGENTS.md") : join28(homedir11(), ".codex", "AGENTS.md");
@@ -14052,6 +14431,26 @@ class CodexAdapter {
14052
14431
  }
14053
14432
  }
14054
14433
  }
14434
+ if (config.networkAllowlist?.length) {
14435
+ const features = parsed.features && typeof parsed.features === "object" ? parsed.features : undefined;
14436
+ const proxy = features?.network_proxy;
14437
+ if (proxy && proxy.enabled === true) {
14438
+ if (!proxy.domains || typeof proxy.domains !== "object")
14439
+ proxy.domains = {};
14440
+ const domains = proxy.domains;
14441
+ for (const host of config.networkAllowlist) {
14442
+ if (!(host in domains))
14443
+ domains[host] = "allow";
14444
+ }
14445
+ } else {
14446
+ if (!parsed.sandbox_workspace_write || typeof parsed.sandbox_workspace_write !== "object") {
14447
+ parsed.sandbox_workspace_write = {};
14448
+ }
14449
+ const sww = parsed.sandbox_workspace_write;
14450
+ if (sww.network_access === undefined)
14451
+ sww.network_access = true;
14452
+ }
14453
+ }
14055
14454
  mkdirSync20(join28(configPath, ".."), { recursive: true });
14056
14455
  writeFileSync21(configPath, stringify(parsed));
14057
14456
  }
@@ -14377,12 +14776,13 @@ class ClineAdapter {
14377
14776
  }
14378
14777
  async writeSkills(skills, scope) {
14379
14778
  if (scope === "user")
14380
- return;
14779
+ return 0;
14381
14780
  const rulesDir = join29(process.cwd(), ".clinerules");
14382
14781
  mkdirSync21(rulesDir, { recursive: true });
14383
14782
  for (const skill of skills) {
14384
14783
  writeFileSync22(join29(rulesDir, `${skill.filename}.md`), buildSkillMd2(skill));
14385
14784
  }
14785
+ return skills.length;
14386
14786
  }
14387
14787
  async writeInstructionHint(hint, scope) {
14388
14788
  if (scope === "user")
@@ -14507,6 +14907,7 @@ class GeminiAdapter {
14507
14907
  mkdirSync22(skillDir, { recursive: true });
14508
14908
  writeFileSync23(join30(skillDir, "SKILL.md"), buildSkillMd2(skill));
14509
14909
  }
14910
+ return skills.length;
14510
14911
  }
14511
14912
  async writeInstructionHint(hint, scope) {
14512
14913
  const filePath = scope === "project" ? join30(process.cwd(), "GEMINI.md") : join30(homedir13(), ".gemini", "GEMINI.md");
@@ -14606,13 +15007,13 @@ class GenericAgentAdapter {
14606
15007
  }
14607
15008
  async writeSkills(skills, scope) {
14608
15009
  if (!this.def.skillsPaths)
14609
- return;
15010
+ return 0;
14610
15011
  const pathTemplate = scope === "project" ? this.def.skillsPaths.project : this.def.skillsPaths.global;
14611
15012
  if (!pathTemplate)
14612
- return;
15013
+ return 0;
14613
15014
  const baseDir = resolveToAbsolute(pathTemplate, scope === "project" ? "project" : "global");
14614
15015
  if (!baseDir)
14615
- return;
15016
+ return 0;
14616
15017
  for (const skill of skills) {
14617
15018
  if (skill.name !== skill.filename) {
14618
15019
  const oldDir = join31(baseDir, skill.name);
@@ -14626,6 +15027,7 @@ class GenericAgentAdapter {
14626
15027
  mkdirSync23(skillDir, { recursive: true });
14627
15028
  writeFileSync24(join31(skillDir, "SKILL.md"), buildSkillMd2(skill));
14628
15029
  }
15030
+ return skills.length;
14629
15031
  }
14630
15032
  async writeInstructionHint(hint, scope) {
14631
15033
  if (!this.def.instructionFile)
@@ -14740,9 +15142,9 @@ function printNoAgentsMessage() {
14740
15142
  }
14741
15143
 
14742
15144
  // src/utils/insight-id.ts
14743
- import { createHash as createHash2 } from "node:crypto";
15145
+ import { createHash as createHash3 } from "node:crypto";
14744
15146
  function computeInsightId(userSeed, localKey) {
14745
- return createHash2("sha256").update(`${userSeed}:${localKey}`).digest("hex").slice(0, 22);
15147
+ return createHash3("sha256").update(`${userSeed}:${localKey}`).digest("hex").slice(0, 22);
14746
15148
  }
14747
15149
  function insightLocalKey(teaches, slug) {
14748
15150
  return `${teaches}:${slug}`.toLowerCase().replace(/[^a-z0-9:_-]+/g, "-").replace(/-+/g, "-");
@@ -15355,7 +15757,7 @@ function parseJson(content, source) {
15355
15757
  }
15356
15758
 
15357
15759
  // src/commands/entities.ts
15358
- var listCommand3 = new Command15("list").description("List entities registered in the workspace").option("--workspace <id>", "Workspace ID").option("--app <id>", "Filter by app name or ID").option("--preview", "Show preview-mode entities only").action(async (opts, command) => {
15760
+ var listCommand3 = new Command15("list").description("List entities registered in the workspace").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <id>", "Filter by app name or ID").option("--preview", "Show preview-mode entities only").action(async (opts, command) => {
15359
15761
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15360
15762
  const credentials = requireAuth();
15361
15763
  const client = new ApiClient(credentials);
@@ -15390,7 +15792,7 @@ Workspace: ${workspaceName || workspaceId}
15390
15792
  process.exit(1);
15391
15793
  }
15392
15794
  });
15393
- var recordsCommand = new Command15("records").description("List records for an entity").argument("<entity>", "Entity name").option("--workspace <id>", "Workspace ID").option("--limit <n>", "Number of records to return", "20").option("--cursor <cursor>", "Pagination cursor").option("--preview", "Query preview-mode data").action(async (entity, opts, command) => {
15795
+ var recordsCommand = new Command15("records").description("List records for an entity").argument("<entity>", "Entity name").option("--workspace <name-or-id>", "Workspace name or ID").option("--limit <n>", "Number of records to return", "20").option("--cursor <cursor>", "Pagination cursor").option("--preview", "Query preview-mode data").action(async (entity, opts, command) => {
15394
15796
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15395
15797
  const credentials = requireAuth();
15396
15798
  const client = new ApiClient(credentials);
@@ -15422,7 +15824,7 @@ Next cursor: ${result.next}`);
15422
15824
  process.exit(1);
15423
15825
  }
15424
15826
  });
15425
- var getCommand = new Command15("get").description("Get a single entity record by ID").argument("<entity>", "Entity name").argument("<id>", "Record ID").option("--workspace <id>", "Workspace ID").action(async (entity, id, opts, command) => {
15827
+ var getCommand = new Command15("get").description("Get a single entity record by ID").argument("<entity>", "Entity name").argument("<id>", "Record ID").option("--workspace <name-or-id>", "Workspace name or ID").action(async (entity, id, opts, command) => {
15426
15828
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15427
15829
  const credentials = requireAuth();
15428
15830
  const client = new ApiClient(credentials);
@@ -15439,7 +15841,7 @@ var getCommand = new Command15("get").description("Get a single entity record by
15439
15841
  process.exit(1);
15440
15842
  }
15441
15843
  });
15442
- var createCommand = new Command15("create").description("Create a new entity record").argument("<entity>", "Entity name").option("--workspace <id>", "Workspace ID").option("--data <json>", "Record data as JSON string, @file.json, or pipe via stdin").option("--preview", "Create in preview-mode data store").action(async (entity, opts, command) => {
15844
+ var createCommand = new Command15("create").description("Create a new entity record").argument("<entity>", "Entity name").option("--workspace <name-or-id>", "Workspace name or ID").option("--data <json>", "Record data as JSON string, @file.json, or pipe via stdin").option("--preview", "Create in preview-mode data store").action(async (entity, opts, command) => {
15443
15845
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15444
15846
  const credentials = requireAuth();
15445
15847
  const client = new ApiClient(credentials);
@@ -15458,7 +15860,7 @@ var createCommand = new Command15("create").description("Create a new entity rec
15458
15860
  process.exit(1);
15459
15861
  }
15460
15862
  });
15461
- var updateCommand = new Command15("update").description("Update an existing entity record").argument("<entity>", "Entity name").argument("<id>", "Record ID").option("--workspace <id>", "Workspace ID").option("--data <json>", "Record data as JSON string, @file.json, or pipe via stdin").action(async (entity, id, opts, command) => {
15863
+ var updateCommand = new Command15("update").description("Update an existing entity record").argument("<entity>", "Entity name").argument("<id>", "Record ID").option("--workspace <name-or-id>", "Workspace name or ID").option("--data <json>", "Record data as JSON string, @file.json, or pipe via stdin").action(async (entity, id, opts, command) => {
15462
15864
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15463
15865
  const credentials = requireAuth();
15464
15866
  const client = new ApiClient(credentials);
@@ -15477,7 +15879,7 @@ var updateCommand = new Command15("update").description("Update an existing enti
15477
15879
  process.exit(1);
15478
15880
  }
15479
15881
  });
15480
- var deleteCommand = new Command15("delete").description("Delete an entity record").argument("<entity>", "Entity name").argument("<id>", "Record ID").option("--workspace <id>", "Workspace ID").option("--yes", "Skip confirmation prompt").action(async (entity, id, opts, command) => {
15882
+ var deleteCommand = new Command15("delete").description("Delete an entity record").argument("<entity>", "Entity name").argument("<id>", "Record ID").option("--workspace <name-or-id>", "Workspace name or ID").option("--yes", "Skip confirmation prompt").action(async (entity, id, opts, command) => {
15481
15883
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15482
15884
  const credentials = requireAuth();
15483
15885
  const client = new ApiClient(credentials);
@@ -15516,7 +15918,7 @@ function truncate2(text2, max) {
15516
15918
  return first;
15517
15919
  return first.slice(0, max - 3) + "...";
15518
15920
  }
15519
- var listCommand4 = new Command16("list").description("List workflows in a workspace").option("--workspace <id>", "Workspace ID").option("--app <name>", "Filter by app name or ID").action(async (opts, command) => {
15921
+ var listCommand4 = new Command16("list").description("List workflows in a workspace").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <name>", "Filter by app name or ID").action(async (opts, command) => {
15520
15922
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15521
15923
  const credentials = requireAuth();
15522
15924
  const client = new ApiClient(credentials);
@@ -15550,7 +15952,7 @@ Workspace: ${workspaceName || workspaceId}
15550
15952
  process.exit(1);
15551
15953
  }
15552
15954
  });
15553
- var triggerCommand = new Command16("trigger").description("Trigger a workflow by name").argument("<name>", "Workflow name").option("--workspace <id>", "Workspace ID").option("--app <name>", "App name or ID (required if not in a project directory)").option("--params <json>", "JSON string of parameters to pass to the workflow").option("--instance-id <id>", "Custom instance ID for the workflow run").action(async (name, opts, command) => {
15955
+ var triggerCommand = new Command16("trigger").description("Trigger a workflow by name").argument("<name>", "Workflow name").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <name>", "App name or ID (required if not in a project directory)").option("--params <json>", "JSON string of parameters to pass to the workflow").option("--instance-id <id>", "Custom instance ID for the workflow run").action(async (name, opts, command) => {
15554
15956
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15555
15957
  const credentials = requireAuth();
15556
15958
  const client = new ApiClient(credentials);
@@ -15584,7 +15986,7 @@ var triggerCommand = new Command16("trigger").description("Trigger a workflow by
15584
15986
  process.exit(1);
15585
15987
  }
15586
15988
  });
15587
- var instancesCommand = new Command16("instances").description("List workflow instances for an app").option("--workspace <id>", "Workspace ID").option("--app <name>", "App name or ID (required if not in a project directory)").option("--workflow <name>", "Filter by workflow name").option("--status <status>", "Filter by instance status").option("--limit <n>", "Maximum number of instances to return").action(async (opts, command) => {
15989
+ var instancesCommand = new Command16("instances").description("List workflow instances for an app").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <name>", "App name or ID (required if not in a project directory)").option("--workflow <name>", "Filter by workflow name").option("--status <status>", "Filter by instance status").option("--limit <n>", "Maximum number of instances to return").action(async (opts, command) => {
15588
15990
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15589
15991
  const credentials = requireAuth();
15590
15992
  const client = new ApiClient(credentials);
@@ -15617,7 +16019,7 @@ var instancesCommand = new Command16("instances").description("List workflow ins
15617
16019
  process.exit(1);
15618
16020
  }
15619
16021
  });
15620
- var instanceCommand = new Command16("instance").description("Get details for a specific workflow instance").argument("<id>", "Workflow instance ID").option("--workspace <id>", "Workspace ID").option("--app <name>", "App name or ID (optional)").action(async (id, opts, command) => {
16022
+ var instanceCommand = new Command16("instance").description("Get details for a specific workflow instance").argument("<id>", "Workflow instance ID").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <name>", "App name or ID (optional)").action(async (id, opts, command) => {
15621
16023
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15622
16024
  const credentials = requireAuth();
15623
16025
  const client = new ApiClient(credentials);
@@ -15645,16 +16047,64 @@ var workflowsCommand = new Command16("workflows").description("Manage workspace
15645
16047
  init_store();
15646
16048
  init_client();
15647
16049
  import { Command as Command17 } from "commander";
15648
- function truncate3(text2, max) {
15649
- if (!text2)
16050
+ // src/utils/table.ts
16051
+ function clip(value, max) {
16052
+ if (max <= 0)
15650
16053
  return "";
15651
- const first = text2.split(`
15652
- `)[0];
15653
- if (first.length <= max)
15654
- return first;
15655
- return first.slice(0, max - 3) + "...";
16054
+ if (value.length <= max)
16055
+ return value;
16056
+ if (max <= 3)
16057
+ return value.slice(0, max);
16058
+ return value.slice(0, max - 3) + "...";
16059
+ }
16060
+ function renderTable(columns, rows, indent = " ") {
16061
+ const gap = " ";
16062
+ const lastIdx = columns.length - 1;
16063
+ const data = rows.map((row) => columns.map((c, i) => {
16064
+ const raw = row[i] == null ? "" : String(row[i]);
16065
+ return c.max !== undefined ? clip(raw, c.max) : raw;
16066
+ }));
16067
+ const widths = columns.map((c, i) => {
16068
+ let w = c.header.length;
16069
+ for (const row of data)
16070
+ w = Math.max(w, row[i].length);
16071
+ return w;
16072
+ });
16073
+ const renderRow = (cells, colorize) => {
16074
+ const parts = cells.map((cell, i) => {
16075
+ const padded = i === lastIdx ? cell : cell.padEnd(widths[i]);
16076
+ const color = columns[i].color;
16077
+ return colorize && color ? color(padded) : padded;
16078
+ });
16079
+ return (indent + parts.join(gap)).replace(/\s+$/, "");
16080
+ };
16081
+ const out = [renderRow(columns.map((c) => c.header), false)];
16082
+ const total = widths.reduce((a, b) => a + b, 0) + gap.length * Math.max(0, columns.length - 1);
16083
+ out.push(indent + "-".repeat(total));
16084
+ for (const row of data)
16085
+ out.push(renderRow(row, true));
16086
+ return out.join(`
16087
+ `);
16088
+ }
16089
+
16090
+ // src/commands/schedules.ts
16091
+ function extractScheduleHistoryRecords(data) {
16092
+ if (!data || !Array.isArray(data.records))
16093
+ return [];
16094
+ return data.records;
16095
+ }
16096
+ function scheduleRowCells(s) {
16097
+ const mode = s.deploymentMode ? ` (${s.deploymentMode})` : "";
16098
+ const status = s.status ? s.status + mode : mode.trim() || "-";
16099
+ return [
16100
+ s.scheduleName || "(unnamed)",
16101
+ s.appName || s.appId || "-",
16102
+ s.schedule || "-",
16103
+ status,
16104
+ s.description || ""
16105
+ ];
15656
16106
  }
15657
- var listCommand5 = new Command17("list").description("List scheduled jobs in the workspace").option("--workspace <id>", "Workspace ID").option("--app <name|id>", "Filter by app name or ID").action(async (opts, command) => {
16107
+ var listCommand5 = new Command17("list").description("List scheduled jobs in the workspace").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <name|id>", "Filter by app name or ID").action(async (opts, command) => {
15658
16108
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15659
16109
  const credentials = requireAuth();
15660
16110
  const client = new ApiClient(credentials);
@@ -15676,20 +16126,20 @@ var listCommand5 = new Command17("list").description("List scheduled jobs in the
15676
16126
  console.log(`
15677
16127
  Workspace: ${workspaceName || workspaceId}
15678
16128
  `);
15679
- console.log(" " + "Name".padEnd(24) + "App".padEnd(20) + "Schedule".padEnd(18) + "Status".padEnd(12) + "Description");
15680
- console.log(" " + "-".repeat(90));
15681
- for (const s of schedules) {
15682
- const mode = s.deploymentMode ? ` (${s.deploymentMode})` : "";
15683
- const status = s.status ? s.status + mode : mode.trim() || "-";
15684
- console.log(" " + s.name.padEnd(24) + (s.appName || s.appId).slice(0, 18).padEnd(20) + (s.schedule || "-").padEnd(18) + status.padEnd(12) + truncate3(s.description, 40));
15685
- }
16129
+ console.log(renderTable([
16130
+ { header: "Name" },
16131
+ { header: "App" },
16132
+ { header: "Schedule" },
16133
+ { header: "Status" },
16134
+ { header: "Description", max: 60 }
16135
+ ], schedules.map(scheduleRowCells)));
15686
16136
  console.log("");
15687
16137
  } catch (err) {
15688
16138
  console.error("Failed to list schedules:", err instanceof Error ? err.message : err);
15689
16139
  process.exit(1);
15690
16140
  }
15691
16141
  });
15692
- var triggerCommand2 = new Command17("trigger").description("Manually trigger a scheduled job").argument("<name>", "Schedule name").option("--workspace <id>", "Workspace ID").option("--app <name|id>", "App name or ID").option("--preview", "Trigger in preview (sandbox) mode instead of production").action(async (name, opts, command) => {
16142
+ var triggerCommand2 = new Command17("trigger").description("Manually trigger a scheduled job").argument("<name>", "Schedule name").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <name|id>", "App name or ID").option("--preview", "Trigger in preview (sandbox) mode instead of production").action(async (name, opts, command) => {
15693
16143
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15694
16144
  const credentials = requireAuth();
15695
16145
  const client = new ApiClient(credentials);
@@ -15715,7 +16165,7 @@ var triggerCommand2 = new Command17("trigger").description("Manually trigger a s
15715
16165
  process.exit(1);
15716
16166
  }
15717
16167
  });
15718
- var historyCommand = new Command17("history").description("Show execution history for scheduled jobs in an app").option("--workspace <id>", "Workspace ID").option("--app <name|id>", "App name or ID (required)").action(async (opts, command) => {
16168
+ var historyCommand = new Command17("history").description("Show execution history for scheduled jobs in an app").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <name|id>", "App name or ID (required)").action(async (opts, command) => {
15719
16169
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15720
16170
  const credentials = requireAuth();
15721
16171
  const client = new ApiClient(credentials);
@@ -15723,25 +16173,34 @@ var historyCommand = new Command17("history").description("Show execution histor
15723
16173
  const { appId, appName } = await resolveApp2(client, workspaceId, opts);
15724
16174
  try {
15725
16175
  const data = await client.getScheduleHistory(workspaceId, appId);
16176
+ const records = extractScheduleHistoryRecords(data);
15726
16177
  if (useJson) {
15727
- jsonOut({ history: data.history, appId, appName });
16178
+ jsonOut({ history: records, appId, appName });
15728
16179
  return;
15729
16180
  }
15730
- if (!data.history || data.history.length === 0) {
16181
+ if (records.length === 0) {
15731
16182
  console.log(`No schedule history found for app "${appName}".`);
15732
16183
  return;
15733
16184
  }
15734
16185
  console.log(`
15735
16186
  Schedule history for app: ${appName}
15736
16187
  `);
15737
- console.log(" " + "Schedule".padEnd(24) + "Executed At".padEnd(26) + "Status".padEnd(12) + "Duration");
15738
- console.log(" " + "-".repeat(72));
15739
- for (const entry of data.history) {
16188
+ const nameW = Math.max("Schedule".length, ...records.map((r) => (r.jobName || "-").length));
16189
+ console.log(" " + "Schedule".padEnd(nameW + 2) + "Started At".padEnd(26) + "Status".padEnd(12) + "Duration");
16190
+ console.log(" " + "-".repeat(nameW + 2 + 26 + 12 + 8));
16191
+ for (const entry of records) {
16192
+ const startedAt = entry.startedAt ? new Date(entry.startedAt).toISOString() : "-";
15740
16193
  const duration = entry.durationMs != null ? `${entry.durationMs}ms` : "-";
15741
- console.log(" " + entry.scheduleName.padEnd(24) + entry.executedAt.padEnd(26) + entry.status.padEnd(12) + duration);
16194
+ console.log(" " + (entry.jobName || "-").padEnd(nameW + 2) + startedAt.padEnd(26) + entry.status.padEnd(12) + duration);
15742
16195
  if (entry.error) {
15743
16196
  console.log(` Error: ${entry.error}`);
15744
16197
  }
16198
+ if (entry.errorStack) {
16199
+ for (const line of entry.errorStack.split(`
16200
+ `).slice(0, 8)) {
16201
+ console.log(` ${line.trim()}`);
16202
+ }
16203
+ }
15745
16204
  }
15746
16205
  console.log("");
15747
16206
  } catch (err) {
@@ -15749,7 +16208,7 @@ Schedule history for app: ${appName}
15749
16208
  process.exit(1);
15750
16209
  }
15751
16210
  });
15752
- var statusCommand = new Command17("status").description("Show runtime status of scheduled jobs for an app").argument("<name>", "Schedule name").option("--workspace <id>", "Workspace ID").option("--app <name|id>", "App name or ID (required)").action(async (name, opts, command) => {
16211
+ var statusCommand = new Command17("status").description("Show runtime status of scheduled jobs for an app").argument("<name>", "Schedule name").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <name|id>", "App name or ID (required)").action(async (name, opts, command) => {
15753
16212
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15754
16213
  const credentials = requireAuth();
15755
16214
  const client = new ApiClient(credentials);
@@ -15771,7 +16230,7 @@ Schedule "${name}" status for app "${appName}":
15771
16230
  process.exit(1);
15772
16231
  }
15773
16232
  });
15774
- var pauseCommand = new Command17("pause").description("Pause a scheduled job").argument("<name>", "Schedule name").option("--workspace <id>", "Workspace ID").option("--app <name|id>", "App name or ID (required)").action(async (name, opts, command) => {
16233
+ var pauseCommand = new Command17("pause").description("Pause a scheduled job").argument("<name>", "Schedule name").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <name|id>", "App name or ID (required)").action(async (name, opts, command) => {
15775
16234
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15776
16235
  const credentials = requireAuth();
15777
16236
  const client = new ApiClient(credentials);
@@ -15789,7 +16248,7 @@ var pauseCommand = new Command17("pause").description("Pause a scheduled job").a
15789
16248
  process.exit(1);
15790
16249
  }
15791
16250
  });
15792
- var resumeCommand = new Command17("resume").description("Resume a paused scheduled job").argument("<name>", "Schedule name").option("--workspace <id>", "Workspace ID").option("--app <name|id>", "App name or ID (required)").action(async (name, opts, command) => {
16251
+ var resumeCommand = new Command17("resume").description("Resume a paused scheduled job").argument("<name>", "Schedule name").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <name|id>", "App name or ID (required)").action(async (name, opts, command) => {
15793
16252
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15794
16253
  const credentials = requireAuth();
15795
16254
  const client = new ApiClient(credentials);
@@ -15814,7 +16273,7 @@ init_store();
15814
16273
  init_client();
15815
16274
  import { Command as Command18 } from "commander";
15816
16275
  init_http();
15817
- function truncate4(text2, max) {
16276
+ function truncate3(text2, max) {
15818
16277
  if (!text2)
15819
16278
  return "";
15820
16279
  const first = text2.split(`
@@ -15823,7 +16282,7 @@ function truncate4(text2, max) {
15823
16282
  return first;
15824
16283
  return first.slice(0, max - 3) + "...";
15825
16284
  }
15826
- var listCommand6 = new Command18("list").description("List workspace public endpoints").option("--workspace <id>", "Workspace ID").option("--app <name>", "Filter by app name or ID").action(async (opts, command) => {
16285
+ var listCommand6 = new Command18("list").description("List workspace public endpoints").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <name>", "Filter by app name or ID").action(async (opts, command) => {
15827
16286
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15828
16287
  const credentials = requireAuth();
15829
16288
  const client = new ApiClient(credentials);
@@ -15851,7 +16310,7 @@ Workspace: ${workspaceName || workspaceId}
15851
16310
  const path2 = e.endpointPath.padEnd(36);
15852
16311
  const app = (e.appName || e.appId).padEnd(20);
15853
16312
  const auth = (e.auth || "").padEnd(10);
15854
- const desc = truncate4(e.description, 40);
16313
+ const desc = truncate3(e.description, 40);
15855
16314
  const mode = e.deploymentMode ? ` [${e.deploymentMode}]` : "";
15856
16315
  console.log(` ${method} ${path2} ${app} ${auth} ${desc}${mode}`);
15857
16316
  }
@@ -15861,7 +16320,7 @@ Workspace: ${workspaceName || workspaceId}
15861
16320
  process.exit(1);
15862
16321
  }
15863
16322
  });
15864
- var callCommand2 = new Command18("call").description("Call a workspace endpoint directly").argument("<method>", "HTTP method (GET, POST, PUT, DELETE, etc.)").argument("<path>", "Endpoint path (e.g. /my-endpoint)").option("--workspace <id>", "Workspace ID").option("--app <name>", "App name or ID (narrows endpoint lookup)").option("--body <json>", "Request body as JSON string").option("--header <header>", 'Request header (format: "Key: Value", repeatable)', (val, prev) => [...prev, val], []).option("--query <string>", 'Query string to append to the URL (e.g. "foo=bar&baz=1")').option("--api-key <key>", "API key for Authorization: Bearer header").action(async (method, path2, opts, command) => {
16323
+ var callCommand2 = new Command18("call").description("Call a workspace endpoint directly").argument("<method>", "HTTP method (GET, POST, PUT, DELETE, etc.)").argument("<path>", "Endpoint path (e.g. /my-endpoint)").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <name>", "App name or ID (narrows endpoint lookup)").option("--body <json>", "Request body as JSON string").option("--header <header>", 'Request header (format: "Key: Value", repeatable)', (val, prev) => [...prev, val], []).option("--query <string>", 'Query string to append to the URL (e.g. "foo=bar&baz=1")').option("--api-key <key>", "API key for Authorization: Bearer header").action(async (method, path2, opts, command) => {
15865
16324
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15866
16325
  const credentials = requireAuth();
15867
16326
  const client = new ApiClient(credentials);
@@ -15992,7 +16451,7 @@ function formatSize(bytes) {
15992
16451
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
15993
16452
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
15994
16453
  }
15995
- var listCommand7 = new Command19("list").description("List file storage buckets in the workspace").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
16454
+ var listCommand7 = new Command19("list").description("List file storage buckets in the workspace").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
15996
16455
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15997
16456
  const credentials = requireAuth();
15998
16457
  const client = new ApiClient(credentials);
@@ -16022,7 +16481,7 @@ Workspace: ${workspaceName || workspaceId}
16022
16481
  process.exit(1);
16023
16482
  }
16024
16483
  });
16025
- var lsCommand = new Command19("ls").description("List files in a bucket").argument("<bucket>", "Bucket name").argument("[prefix]", "Key prefix to filter by").option("--workspace <id>", "Workspace ID").option("--delimiter <char>", "Delimiter for hierarchical listing", "/").action(async (bucket, prefix, opts, command) => {
16484
+ var lsCommand = new Command19("ls").description("List files in a bucket").argument("<bucket>", "Bucket name").argument("[prefix]", "Key prefix to filter by").option("--workspace <name-or-id>", "Workspace name or ID").option("--delimiter <char>", "Delimiter for hierarchical listing", "/").action(async (bucket, prefix, opts, command) => {
16026
16485
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16027
16486
  const credentials = requireAuth();
16028
16487
  const client = new ApiClient(credentials);
@@ -16059,7 +16518,7 @@ var lsCommand = new Command19("ls").description("List files in a bucket").argume
16059
16518
  process.exit(1);
16060
16519
  }
16061
16520
  });
16062
- var downloadCommand = new Command19("download").description("Download a file from a bucket").argument("<bucket>", "Bucket name").argument("<key>", "Object key to download").argument("[output]", "Local output path (defaults to basename of key)").option("--workspace <id>", "Workspace ID").action(async (bucket, key, output, opts, command) => {
16521
+ var downloadCommand = new Command19("download").description("Download a file from a bucket").argument("<bucket>", "Bucket name").argument("<key>", "Object key to download").argument("[output]", "Local output path (defaults to basename of key)").option("--workspace <name-or-id>", "Workspace name or ID").action(async (bucket, key, output, opts, command) => {
16063
16522
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16064
16523
  const credentials = requireAuth();
16065
16524
  const client = new ApiClient(credentials);
@@ -16082,7 +16541,7 @@ var downloadCommand = new Command19("download").description("Download a file fro
16082
16541
  process.exit(1);
16083
16542
  }
16084
16543
  });
16085
- var uploadCommand = new Command19("upload").description("Upload a local file to a bucket").argument("<bucket>", "Bucket name").argument("<localPath>", "Local file path to upload").argument("[key]", "Object key in bucket (defaults to basename of local path)").option("--workspace <id>", "Workspace ID").action(async (bucket, localPath, key, opts, command) => {
16544
+ var uploadCommand = new Command19("upload").description("Upload a local file to a bucket").argument("<bucket>", "Bucket name").argument("<localPath>", "Local file path to upload").argument("[key]", "Object key in bucket (defaults to basename of local path)").option("--workspace <name-or-id>", "Workspace name or ID").action(async (bucket, localPath, key, opts, command) => {
16086
16545
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16087
16546
  const credentials = requireAuth();
16088
16547
  const client = new ApiClient(credentials);
@@ -16105,7 +16564,7 @@ var uploadCommand = new Command19("upload").description("Upload a local file to
16105
16564
  process.exit(1);
16106
16565
  }
16107
16566
  });
16108
- var deleteCommand2 = new Command19("delete").description("Delete a file from a bucket").argument("<bucket>", "Bucket name").argument("<key>", "Object key to delete").option("--workspace <id>", "Workspace ID").option("--yes", "Skip confirmation prompt").action(async (bucket, key, opts, command) => {
16567
+ var deleteCommand2 = new Command19("delete").description("Delete a file from a bucket").argument("<bucket>", "Bucket name").argument("<key>", "Object key to delete").option("--workspace <name-or-id>", "Workspace name or ID").option("--yes", "Skip confirmation prompt").action(async (bucket, key, opts, command) => {
16109
16568
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16110
16569
  const credentials = requireAuth();
16111
16570
  const client = new ApiClient(credentials);
@@ -16135,7 +16594,7 @@ var filesCommand = new Command19("files").description("Manage workspace file sto
16135
16594
  init_store();
16136
16595
  init_client();
16137
16596
  import { Command as Command20 } from "commander";
16138
- var listCommand8 = new Command20("list").description("List components registered in the workspace").option("--workspace <id>", "Workspace ID").option("--app <id>", "Filter by app name or ID").action(async (opts, command) => {
16597
+ var listCommand8 = new Command20("list").description("List components registered in the workspace").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <id>", "Filter by app name or ID").action(async (opts, command) => {
16139
16598
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16140
16599
  const credentials = requireAuth();
16141
16600
  const client = new ApiClient(credentials);
@@ -16174,7 +16633,7 @@ init_store();
16174
16633
  init_client();
16175
16634
  import { Command as Command21 } from "commander";
16176
16635
  init_prompt();
16177
- var listCommand9 = new Command21("list").description("List API keys for the workspace").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
16636
+ var listCommand9 = new Command21("list").description("List API keys for the workspace").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
16178
16637
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16179
16638
  const credentials = requireAuth();
16180
16639
  const client = new ApiClient(credentials);
@@ -16209,7 +16668,7 @@ Workspace: ${workspaceName || workspaceId}
16209
16668
  process.exit(1);
16210
16669
  }
16211
16670
  });
16212
- var createCommand2 = new Command21("create").description("Create a new API key").option("--workspace <id>", "Workspace ID").option("--name <name>", "API key name").option("--app <id>", "Restrict key to a specific app (name or ID)").option("--scopes <scopes>", "Comma-separated list of scopes").action(async (opts, command) => {
16671
+ var createCommand2 = new Command21("create").description("Create a new API key").option("--workspace <name-or-id>", "Workspace name or ID").option("--name <name>", "API key name").option("--app <id>", "Restrict key to a specific app (name or ID)").option("--scopes <scopes>", "Comma-separated list of scopes").action(async (opts, command) => {
16213
16672
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16214
16673
  const credentials = requireAuth();
16215
16674
  const client = new ApiClient(credentials);
@@ -16257,7 +16716,7 @@ var apiKeysCommand = new Command21("api-keys").description("Manage workspace API
16257
16716
  init_store();
16258
16717
  init_client();
16259
16718
  import { Command as Command22 } from "commander";
16260
- function truncate5(text2, max) {
16719
+ function truncate4(text2, max) {
16261
16720
  if (!text2)
16262
16721
  return "";
16263
16722
  const first = text2.split(`
@@ -16266,7 +16725,7 @@ function truncate5(text2, max) {
16266
16725
  return first;
16267
16726
  return first.slice(0, max - 3) + "...";
16268
16727
  }
16269
- var listCommand10 = new Command22("list").description("List agents in workspace").option("--workspace <id>", "Workspace ID").option("--app <id>", "Filter by app name or ID").action(async (opts, command) => {
16728
+ var listCommand10 = new Command22("list").description("List agents in workspace").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <id>", "Filter by app name or ID").action(async (opts, command) => {
16270
16729
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16271
16730
  const credentials = requireAuth();
16272
16731
  const client = new ApiClient(credentials);
@@ -16292,8 +16751,8 @@ Workspace: ${workspaceName || workspaceId}
16292
16751
  for (const a of agents) {
16293
16752
  const name = a.agentName.padEnd(24);
16294
16753
  const type = a.type.padEnd(16);
16295
- const app = truncate5(a.appName, 18).padEnd(20);
16296
- const desc = truncate5(a.description, 40).padEnd(42);
16754
+ const app = truncate4(a.appName, 18).padEnd(20);
16755
+ const desc = truncate4(a.description, 40).padEnd(42);
16297
16756
  const integrations = (a.integrations?.join(", ") || "").padEnd(20);
16298
16757
  const entities = (a.entities?.join(", ") || "").padEnd(20);
16299
16758
  const mode = a.deploymentMode || "";
@@ -16305,7 +16764,7 @@ Workspace: ${workspaceName || workspaceId}
16305
16764
  process.exit(1);
16306
16765
  }
16307
16766
  });
16308
- var statusCommand2 = new Command22("status").description("Get status of a specific agent").argument("<name>", "Agent name").option("--workspace <id>", "Workspace ID").option("--app <id>", "App name or ID (required)").action(async (name, opts, command) => {
16767
+ var statusCommand2 = new Command22("status").description("Get status of a specific agent").argument("<name>", "Agent name").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <id>", "App name or ID (required)").action(async (name, opts, command) => {
16309
16768
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16310
16769
  const credentials = requireAuth();
16311
16770
  const client = new ApiClient(credentials);
@@ -16323,7 +16782,7 @@ var statusCommand2 = new Command22("status").description("Get status of a specif
16323
16782
  process.exit(1);
16324
16783
  }
16325
16784
  });
16326
- var executionsCommand = new Command22("executions").description("List agent execution history for workspace").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
16785
+ var executionsCommand = new Command22("executions").description("List agent execution history for workspace").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
16327
16786
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16328
16787
  const credentials = requireAuth();
16329
16788
  const client = new ApiClient(credentials);
@@ -16342,9 +16801,9 @@ var executionsCommand = new Command22("executions").description("List agent exec
16342
16801
  console.log(" " + "-".repeat(160));
16343
16802
  for (const e of executions) {
16344
16803
  const sessionId = e.sessionId.padEnd(36);
16345
- const agent = truncate5(e.agentName, 20).padEnd(22);
16346
- const app = truncate5(e.appId, 20).padEnd(22);
16347
- const prompt = truncate5(e.prompt, 50).padEnd(52);
16804
+ const agent = truncate4(e.agentName, 20).padEnd(22);
16805
+ const app = truncate4(e.appId, 20).padEnd(22);
16806
+ const prompt = truncate4(e.prompt, 50).padEnd(52);
16348
16807
  const status = e.status.padEnd(12);
16349
16808
  const duration = e.durationMs != null ? `${e.durationMs}ms` : "";
16350
16809
  const usage = e.usage ? `${e.usage.inputTokens}/${e.usage.outputTokens}` : "";
@@ -16356,7 +16815,7 @@ var executionsCommand = new Command22("executions").description("List agent exec
16356
16815
  process.exit(1);
16357
16816
  }
16358
16817
  });
16359
- var executionCommand = new Command22("execution").description("Get execution stream for a specific session").argument("<sessionId>", "Session ID").option("--workspace <id>", "Workspace ID").action(async (sessionId, opts, command) => {
16818
+ var executionCommand = new Command22("execution").description("Get execution stream for a specific session").argument("<sessionId>", "Session ID").option("--workspace <name-or-id>", "Workspace name or ID").action(async (sessionId, opts, command) => {
16360
16819
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16361
16820
  const credentials = requireAuth();
16362
16821
  const client = new ApiClient(credentials);
@@ -16380,7 +16839,7 @@ init_store();
16380
16839
  init_client();
16381
16840
  import { Command as Command23 } from "commander";
16382
16841
  init_prompt();
16383
- var listCommand11 = new Command23("list").description("List workspace MCP servers").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
16842
+ var listCommand11 = new Command23("list").description("List workspace MCP servers").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
16384
16843
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16385
16844
  const credentials = requireAuth();
16386
16845
  const client = new ApiClient(credentials);
@@ -16436,7 +16895,7 @@ Workspace: ${workspaceName || workspaceId}
16436
16895
  process.exit(1);
16437
16896
  }
16438
16897
  });
16439
- var addCommand = new Command23("add").description("Add an MCP server to workspace").argument("<name>", "Server name").argument("<url>", "Server URL (Streamable HTTP or SSE endpoint)").option("--transport <type>", "Transport type: streamable-http or sse", "streamable-http").option("--workspace <id>", "Workspace ID").action(async (name, url, opts, command) => {
16898
+ var addCommand = new Command23("add").description("Add an MCP server to workspace").argument("<name>", "Server name").argument("<url>", "Server URL (Streamable HTTP or SSE endpoint)").option("--transport <type>", "Transport type: streamable-http or sse", "streamable-http").option("--workspace <name-or-id>", "Workspace name or ID").action(async (name, url, opts, command) => {
16440
16899
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16441
16900
  const credentials = requireAuth();
16442
16901
  const client = new ApiClient(credentials);
@@ -16464,7 +16923,7 @@ var addCommand = new Command23("add").description("Add an MCP server to workspac
16464
16923
  process.exit(1);
16465
16924
  }
16466
16925
  });
16467
- var removeCommand = new Command23("remove").description("Remove an MCP server from workspace").argument("<name>", "Server name").option("--yes", "Skip confirmation").option("--workspace <id>", "Workspace ID").action(async (name, opts, command) => {
16926
+ var removeCommand = new Command23("remove").description("Remove an MCP server from workspace").argument("<name>", "Server name").option("--yes", "Skip confirmation").option("--workspace <name-or-id>", "Workspace name or ID").action(async (name, opts, command) => {
16468
16927
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16469
16928
  const credentials = requireAuth();
16470
16929
  const client = new ApiClient(credentials);
@@ -16527,7 +16986,7 @@ var searchCommand3 = new Command23("search").description("Search community MCP s
16527
16986
  process.exit(1);
16528
16987
  }
16529
16988
  });
16530
- var installCommand2 = new Command23("install").description("Install a community MCP server from the MCP Registry into your workspace").argument("<query>", "Server name or search query").option("--workspace <id>", "Workspace ID").action(async (query, opts, command) => {
16989
+ var installCommand2 = new Command23("install").description("Install a community MCP server from the MCP Registry into your workspace").argument("<query>", "Server name or search query").option("--workspace <name-or-id>", "Workspace name or ID").action(async (query, opts, command) => {
16531
16990
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16532
16991
  const credentials = requireAuth();
16533
16992
  const client = new ApiClient(credentials);
@@ -16664,19 +17123,6 @@ async function collectTelemetryEvents(params) {
16664
17123
  const now = params.now ?? new Date().toISOString();
16665
17124
  const events = [];
16666
17125
  const adapterResults = [];
16667
- const previouslyReported = new Set(params.state.reportedAgentSlugs ?? []);
16668
- const newlyReportedAgents = [];
16669
- for (const slug of params.state.configuredAgents) {
16670
- if (!previouslyReported.has(slug)) {
16671
- events.push({
16672
- eventType: "local_agent.detected",
16673
- agentSlug: slug,
16674
- metadata: { installed: true },
16675
- timestamp: now
16676
- });
16677
- newlyReportedAgents.push(slug);
16678
- }
16679
- }
16680
17126
  for (const adapter2 of params.adapters) {
16681
17127
  let stats = "unsupported";
16682
17128
  let skills = "unsupported";
@@ -16768,11 +17214,12 @@ async function collectTelemetryEvents(params) {
16768
17214
  syncedMcpServers: params.syncedMcpServersCount,
16769
17215
  teamInstructionsApplied: params.teamInstructionsApplied,
16770
17216
  agentConfigsApplied: params.agentConfigsApplied,
17217
+ agents: [...params.state.configuredAgents],
16771
17218
  ...params.state.persona?.label ? { persona: params.state.persona.label } : {}
16772
17219
  },
16773
17220
  timestamp: now
16774
17221
  });
16775
- return { events, adapterResults, newlyReportedAgents, healthReported };
17222
+ return { events, adapterResults, healthReported };
16776
17223
  }
16777
17224
  function formatStatsLine(stats) {
16778
17225
  const parts = [];
@@ -16818,10 +17265,6 @@ function printTelemetryVerbose(result) {
16818
17265
  const errSuffix = r.error ? ` (error: ${r.error})` : "";
16819
17266
  console.log(` [${r.name}] stats: ${statsLabel}; skills: ${skillsLabel}${errSuffix}`);
16820
17267
  }
16821
- if (result.newlyReportedAgents.length > 0) {
16822
- console.log(`
16823
- First-time detections: ${result.newlyReportedAgents.join(", ")}`);
16824
- }
16825
17268
  if (result.healthReported) {
16826
17269
  console.log(" Health report: due (daily)");
16827
17270
  }
@@ -17451,12 +17894,6 @@ function buildSaveConversationSkill() {
17451
17894
  };
17452
17895
  }
17453
17896
 
17454
- // src/sync/hash.ts
17455
- import { createHash as createHash3 } from "crypto";
17456
- function contentHash(content) {
17457
- return "sha256:" + createHash3("sha256").update(content).digest("hex");
17458
- }
17459
-
17460
17897
  // src/sync/change-detect.ts
17461
17898
  function computeSyncPlan(input) {
17462
17899
  const { storedHashes, localSkills, remoteSkills } = input;
@@ -17488,6 +17925,16 @@ function computeSyncPlan(input) {
17488
17925
  const localChanged = localHash !== null && localHash !== stored.localHash;
17489
17926
  const remoteChanged = remoteHash !== stored.remoteHash;
17490
17927
  if (!local) {
17928
+ if (remote.source === "app" && !remoteChanged) {
17929
+ plan.skips.push({
17930
+ name: remote.name,
17931
+ action: "skip",
17932
+ source: remote.source,
17933
+ remoteId: remote.remoteId,
17934
+ appId: remote.appId
17935
+ });
17936
+ continue;
17937
+ }
17491
17938
  plan.pulls.push({
17492
17939
  name: remote.name,
17493
17940
  action: "pull",
@@ -17667,6 +18114,47 @@ function resolveConflictNonInteractive(prefer) {
17667
18114
  return prefer;
17668
18115
  }
17669
18116
 
18117
+ // src/sync/summary-format.ts
18118
+ function summarizeSkillPlan(plan) {
18119
+ return {
18120
+ added: plan.pulls.filter((p) => p.action === "new-remote").map((p) => p.name),
18121
+ updated: plan.pulls.filter((p) => p.action === "pull").map((p) => p.name),
18122
+ removed: plan.deletions.map((p) => p.name),
18123
+ pushed: plan.pushes.map((p) => p.name),
18124
+ unchanged: plan.skips.length,
18125
+ conflicts: plan.conflicts.length
18126
+ };
18127
+ }
18128
+ function formatNameList(names, cap2 = 6) {
18129
+ if (names.length <= cap2)
18130
+ return names.join(", ");
18131
+ return `${names.slice(0, cap2).join(", ")}, +${names.length - cap2} more`;
18132
+ }
18133
+ function formatSkillSummaryLine(c) {
18134
+ const parts = [];
18135
+ if (c.added.length)
18136
+ parts.push(`${c.added.length} added (${formatNameList(c.added)})`);
18137
+ if (c.updated.length)
18138
+ parts.push(`${c.updated.length} updated (${formatNameList(c.updated)})`);
18139
+ if (c.removed.length)
18140
+ parts.push(`${c.removed.length} removed (${formatNameList(c.removed)})`);
18141
+ if (c.pushed.length)
18142
+ parts.push(`${c.pushed.length} pushed (${formatNameList(c.pushed)})`);
18143
+ if (c.conflicts)
18144
+ parts.push(`${c.conflicts} conflict${c.conflicts === 1 ? "" : "s"}`);
18145
+ parts.push(`${c.unchanged} up to date`);
18146
+ return parts.join(", ");
18147
+ }
18148
+ var REASON_LABEL = {
18149
+ mcp: "MCP",
18150
+ settings: "settings"
18151
+ };
18152
+ function formatRestartChanges(changes) {
18153
+ if (changes.length === 0)
18154
+ return "none";
18155
+ return changes.map((c) => `${c.name} (${c.reasons.map((r) => REASON_LABEL[r]).join(", ")})`).join("; ");
18156
+ }
18157
+
17670
18158
  // src/sync/executor.ts
17671
18159
  async function executeSyncPlan(plan, resolvedConflicts, ctx) {
17672
18160
  const newHashes = {};
@@ -17682,7 +18170,7 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
17682
18170
  remoteId: action.remoteId,
17683
18171
  appId: action.appId
17684
18172
  };
17685
- console.log(` Pulled: ${action.name}`);
18173
+ vlog(` Pulled: ${action.name}`);
17686
18174
  }
17687
18175
  for (const action of plan.pushes) {
17688
18176
  if (!action.localContent || !action.remoteId)
@@ -17699,7 +18187,7 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
17699
18187
  source: "external",
17700
18188
  remoteId: action.remoteId
17701
18189
  };
17702
- console.log(` Pushed: ${action.name}`);
18190
+ vlog(` Pushed: ${action.name}`);
17703
18191
  }
17704
18192
  for (const action of plan.conflicts) {
17705
18193
  const resolution = resolvedConflicts.get(action.name);
@@ -17713,7 +18201,7 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
17713
18201
  });
17714
18202
  const hash = contentHash(action.localContent);
17715
18203
  newHashes[action.name] = { localHash: hash, remoteHash: hash, source: "external", remoteId: action.remoteId };
17716
- console.log(` Pushed (conflict resolved): ${action.name}`);
18204
+ vlog(` Pushed (conflict resolved): ${action.name}`);
17717
18205
  } else if (resolution === "remote" && action.remoteContent) {
17718
18206
  const skillFile = makeSkillFile(action.name, action.remoteContent);
17719
18207
  await writeSkillToAgents(skillFile, action.source, ctx);
@@ -17723,12 +18211,12 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
17723
18211
  source: "external",
17724
18212
  remoteId: action.remoteId
17725
18213
  };
17726
- console.log(` Pulled (conflict resolved): ${action.name}`);
18214
+ vlog(` Pulled (conflict resolved): ${action.name}`);
17727
18215
  }
17728
18216
  }
17729
18217
  for (const _action of plan.skips) {}
17730
18218
  for (const action of plan.deletions) {
17731
- console.log(` Deleted locally: ${action.name}`);
18219
+ vlog(` Deleted locally: ${action.name}`);
17732
18220
  }
17733
18221
  return newHashes;
17734
18222
  }
@@ -17758,6 +18246,53 @@ function extractDescription(content) {
17758
18246
  return match ? match[1].trim() : "";
17759
18247
  }
17760
18248
 
18249
+ // src/sync/restart-stamp.ts
18250
+ function computeRestartReasons(input) {
18251
+ const settingsSet = new Set(input.settingsChangedSlugs);
18252
+ const out = {};
18253
+ for (const agent of input.agents) {
18254
+ if (agent.connectOnly)
18255
+ continue;
18256
+ const reasons = [];
18257
+ if ((input.mcpChanged || agent.isNew) && agent.receivesMcp)
18258
+ reasons.push("mcp");
18259
+ if (settingsSet.has(agent.slug))
18260
+ reasons.push("settings");
18261
+ if (reasons.length > 0)
18262
+ out[agent.slug] = reasons;
18263
+ }
18264
+ return out;
18265
+ }
18266
+ function summarizeRealSkillChanges(newLocalHashes, priorLocalHashes, materializedDeletions) {
18267
+ const added = [];
18268
+ const updated = [];
18269
+ for (const [name, hash] of Object.entries(newLocalHashes)) {
18270
+ if (!(name in priorLocalHashes))
18271
+ added.push(name);
18272
+ else if (priorLocalHashes[name] !== hash)
18273
+ updated.push(name);
18274
+ }
18275
+ return {
18276
+ added: added.sort(),
18277
+ updated: updated.sort(),
18278
+ removed: [...materializedDeletions].sort()
18279
+ };
18280
+ }
18281
+ function hasRealSkillChange(c) {
18282
+ return c.added.length > 0 || c.updated.length > 0 || c.removed.length > 0;
18283
+ }
18284
+ function sameStringSet(a, b) {
18285
+ const setA = new Set(a ?? []);
18286
+ const setB = new Set(b ?? []);
18287
+ if (setA.size !== setB.size)
18288
+ return false;
18289
+ for (const item of setA) {
18290
+ if (!setB.has(item))
18291
+ return false;
18292
+ }
18293
+ return true;
18294
+ }
18295
+
17761
18296
  // src/commands/sync.ts
17762
18297
  async function printAdoptionHint(credentials, workspaceId) {
17763
18298
  if (!workspaceId)
@@ -17842,6 +18377,7 @@ async function refreshConfiguredAgents(state) {
17842
18377
  }
17843
18378
  async function syncFromState(state, statePath2, credentials, opts) {
17844
18379
  const client = new ApiClient(credentials);
18380
+ setVerbose(!!opts.verbose);
17845
18381
  if (!state.workspaceName || state.workspaceName === state.workspaceId || !state.workspaceSlug) {
17846
18382
  try {
17847
18383
  const workspaces = await client.listWorkspaces();
@@ -17853,11 +18389,12 @@ async function syncFromState(state, statePath2, credentials, opts) {
17853
18389
  } catch {}
17854
18390
  }
17855
18391
  console.log(`Syncing workspace: ${state.workspaceName || state.workspaceId}`);
18392
+ let addedThisSync = [];
17856
18393
  if (shouldRedetect(state, opts)) {
17857
18394
  console.log(" Detecting installed agents...");
17858
- const added = await refreshConfiguredAgents(state);
17859
- if (added.length > 0) {
17860
- console.log(` Detected new agent${added.length > 1 ? "s" : ""}: ${added.join(", ")}`);
18395
+ addedThisSync = await refreshConfiguredAgents(state);
18396
+ if (addedThisSync.length > 0) {
18397
+ console.log(` Detected new agent${addedThisSync.length > 1 ? "s" : ""}: ${addedThisSync.join(", ")}`);
17861
18398
  }
17862
18399
  }
17863
18400
  console.log(" Fetching workspace data...");
@@ -17899,6 +18436,8 @@ async function syncFromState(state, statePath2, credentials, opts) {
17899
18436
  headers: { Authorization: `Bearer ${credentials.apiKey}` },
17900
18437
  description: "Runwork workspace tools: query entities, trigger workflows, manage integrations, run agents, and access shared data across your team's apps."
17901
18438
  });
18439
+ const mcpEntriesHash = configHash([...mcpEntries].sort((a, b) => a.name.localeCompare(b.name)));
18440
+ const mcpChanged = state.lastMcpEntriesHash !== undefined && state.lastMcpEntriesHash !== mcpEntriesHash;
17902
18441
  const remoteSkills = [];
17903
18442
  for (const s of externalSkills) {
17904
18443
  remoteSkills.push({ name: s.name, content: s.content, source: "external", remoteId: s.id });
@@ -17928,12 +18467,9 @@ async function syncFromState(state, statePath2, credentials, opts) {
17928
18467
  plan.skips.push(...plan.conflicts.map((c) => ({ ...c, action: "skip" })));
17929
18468
  plan.conflicts = [];
17930
18469
  }
17931
- const hasChanges = plan.pulls.length > 0 || plan.pushes.length > 0 || plan.conflicts.length > 0 || plan.deletions.length > 0;
17932
- if (hasChanges) {
18470
+ console.log(` Skills: ${formatSkillSummaryLine(summarizeSkillPlan(plan))}`);
18471
+ if (isVerbose())
17933
18472
  printSyncSummary(plan);
17934
- } else {
17935
- console.log(" All skills up to date.");
17936
- }
17937
18473
  const adapters = [];
17938
18474
  for (const slug of state.configuredAgents) {
17939
18475
  const adapter2 = getAdapterBySlug(slug);
@@ -17973,6 +18509,7 @@ async function syncFromState(state, statePath2, credentials, opts) {
17973
18509
  }
17974
18510
  }
17975
18511
  }
18512
+ const settingsChangedSlugs = [];
17976
18513
  const scopes = state.scope === "both" ? ["project", "user"] : [state.scope];
17977
18514
  if (adapters.length > 0) {
17978
18515
  console.log(` Syncing to: ${adapters.map((a) => a.name).join(", ")}`);
@@ -18021,6 +18558,8 @@ async function syncFromState(state, statePath2, credentials, opts) {
18021
18558
  buildSaveConversationSkill()
18022
18559
  ];
18023
18560
  const builtInNames = builtInSkills.map((s) => s.name);
18561
+ const builtInSkillsHash = configHash(builtInSkills.map((s) => s.content));
18562
+ const builtInSkillsChanged = state.builtInSkillsHash !== undefined && state.builtInSkillsHash !== builtInSkillsHash;
18024
18563
  const summary = {
18025
18564
  adaptersProcessed: 0,
18026
18565
  adaptersFailed: 0,
@@ -18030,16 +18569,18 @@ async function syncFromState(state, statePath2, credentials, opts) {
18030
18569
  instructionHintWrites: 0,
18031
18570
  hookInstallCalls: 0
18032
18571
  };
18572
+ const mcpFailedAdapters = new Set;
18573
+ const skillFailedAdapters = new Set;
18033
18574
  for (const adapter2 of adapters) {
18034
18575
  if (isConnectOnlyAgent(getRegistryAgent(adapter2.slug))) {
18035
- console.log(` [${adapter2.name}] Connect-only agent: no local files to sync`);
18576
+ vlog(` [${adapter2.name}] Connect-only agent: no local files to sync`);
18036
18577
  summary.adaptersProcessed++;
18037
18578
  continue;
18038
18579
  }
18039
18580
  let adapterFailedAnyScope = false;
18040
18581
  for (const scope of scopes) {
18041
- try {
18042
- if (adapter2.supportsSkills()) {
18582
+ if (adapter2.supportsSkills()) {
18583
+ try {
18043
18584
  const skipAppSkills = adapter2.mcpProvidesSkills && mcpEntries.length > 0;
18044
18585
  const scopeSkills = remoteSkills.filter((s) => {
18045
18586
  if (builtInNameSet.has(s.name))
@@ -18059,25 +18600,41 @@ async function syncFromState(state, statePath2, credentials, opts) {
18059
18600
  description: s.source === "app" ? buildAppSkillDescription(s.name, registries) || `${s.name} - Runwork workspace application` : `${s.name} - Runwork workspace skill`
18060
18601
  }));
18061
18602
  const allSkillFiles = [...builtInSkills, ...workspaceSkillFiles];
18062
- await adapter2.writeSkills(allSkillFiles, scope);
18063
- summary.skillWrites++;
18064
- summary.skillFilesWritten += allSkillFiles.length;
18065
- console.log(` [${adapter2.name}] Wrote ${allSkillFiles.length} skills (${scope}): ` + `${builtInSkills.length} built-in [${builtInNames.join(", ")}], ` + `${workspaceSkillFiles.length} workspace`);
18066
- } else {
18067
- console.log(` [${adapter2.name}] Skipped skills (${scope}): adapter does not support skills`);
18603
+ const written = await adapter2.writeSkills(allSkillFiles, scope);
18604
+ if (written > 0) {
18605
+ summary.skillWrites++;
18606
+ summary.skillFilesWritten += written;
18607
+ vlog(` [${adapter2.name}] Wrote ${written} skills (${scope}): ` + `${builtInSkills.length} built-in [${builtInNames.join(", ")}], ` + `${workspaceSkillFiles.length} workspace`);
18608
+ } else {
18609
+ vlog(` [${adapter2.name}] Skipped skills (${scope}): adapter writes none at this scope`);
18610
+ }
18611
+ } catch (err) {
18612
+ adapterFailedAnyScope = true;
18613
+ skillFailedAdapters.add(adapter2.slug);
18614
+ console.warn(` [${adapter2.name}] Failed skills (${scope}): ${err instanceof Error ? err.message : err}`);
18068
18615
  }
18069
- if (adapter2.supportsMcpScope(scope) && mcpEntries.length > 0) {
18616
+ } else {
18617
+ vlog(` [${adapter2.name}] Skipped skills (${scope}): adapter does not support skills`);
18618
+ }
18619
+ if (adapter2.supportsMcpScope(scope) && mcpEntries.length > 0) {
18620
+ try {
18070
18621
  await adapter2.writeMcpServers(mcpEntries, scope);
18071
18622
  summary.mcpServerWrites++;
18072
- console.log(` [${adapter2.name}] Updated ${mcpEntries.length} MCP server${mcpEntries.length > 1 ? "s" : ""} (${scope})`);
18073
- } else if (mcpEntries.length === 0) {
18074
- console.log(` [${adapter2.name}] Skipped MCP servers (${scope}): no workspace MCP servers configured`);
18075
- } else if (!adapter2.supportsMcpScope(scope)) {
18076
- console.log(` [${adapter2.name}] Skipped MCP servers (${scope}): adapter does not support MCP at this scope`);
18623
+ vlog(` [${adapter2.name}] Updated ${mcpEntries.length} MCP server${mcpEntries.length > 1 ? "s" : ""} (${scope})`);
18624
+ } catch (err) {
18625
+ adapterFailedAnyScope = true;
18626
+ mcpFailedAdapters.add(adapter2.slug);
18627
+ console.warn(` [${adapter2.name}] Failed MCP servers (${scope}): ${err instanceof Error ? err.message : err}`);
18077
18628
  }
18629
+ } else if (mcpEntries.length === 0) {
18630
+ vlog(` [${adapter2.name}] Skipped MCP servers (${scope}): no workspace MCP servers configured`);
18631
+ } else if (!adapter2.supportsMcpScope(scope)) {
18632
+ vlog(` [${adapter2.name}] Skipped MCP servers (${scope}): adapter does not support MCP at this scope`);
18633
+ }
18634
+ try {
18078
18635
  await adapter2.writeInstructionHint(instructionHint, scope);
18079
18636
  summary.instructionHintWrites++;
18080
- console.log(` [${adapter2.name}] Updated instruction hints (${scope})`);
18637
+ vlog(` [${adapter2.name}] Updated instruction hints (${scope})`);
18081
18638
  if (adapter2.writeBuiltInHooks) {
18082
18639
  await adapter2.writeBuiltInHooks(scope);
18083
18640
  summary.hookInstallCalls++;
@@ -18116,7 +18673,7 @@ async function syncFromState(state, statePath2, credentials, opts) {
18116
18673
  try {
18117
18674
  await adapter2.writeTeamInstructions(fullInstructions, scope);
18118
18675
  teamInstructionsApplied = true;
18119
- console.log(` [${adapter2.name}] Updated team instructions (${scope})`);
18676
+ vlog(` [${adapter2.name}] Updated team instructions (${scope})`);
18120
18677
  } catch {}
18121
18678
  }
18122
18679
  }
@@ -18137,7 +18694,7 @@ async function syncFromState(state, statePath2, credentials, opts) {
18137
18694
  await adapter2.writeAgentConfig(configWithoutInstructions, scope);
18138
18695
  agentConfigsApplied++;
18139
18696
  const configKeys = Object.keys(configWithoutInstructions).join(", ");
18140
- console.log(` [${adapter2.name}] Updated agent config: ${configKeys} (${scope})`);
18697
+ vlog(` [${adapter2.name}] Updated agent config: ${configKeys} (${scope})`);
18141
18698
  } catch {}
18142
18699
  }
18143
18700
  }
@@ -18192,6 +18749,12 @@ async function syncFromState(state, statePath2, credentials, opts) {
18192
18749
  try {
18193
18750
  const baseline = agentState?.lastInjected;
18194
18751
  await adapter2.writeAgentConfig(mergedConfig, "user", baseline);
18752
+ if (baked && !resolved.bootstrapped) {
18753
+ const prev = agentState?.lastInjected;
18754
+ if (!sameStringSet(prev?.allow, resolved.applicableAllow) || !sameStringSet(prev?.deny, resolved.applicableDeny)) {
18755
+ settingsChangedSlugs.push(slug);
18756
+ }
18757
+ }
18195
18758
  if (baked) {
18196
18759
  const nextState = {
18197
18760
  lastInjected: {
@@ -18205,11 +18768,11 @@ async function syncFromState(state, statePath2, credentials, opts) {
18205
18768
  };
18206
18769
  state.agentDefaults[slug] = nextState;
18207
18770
  if (resolved.bootstrapped) {
18208
- console.log(` [${adapter2.name}] Bootstrapped default-rule tracking (defaults apply on next sync)`);
18771
+ vlog(` [${adapter2.name}] Bootstrapped default-rule tracking (defaults apply on next sync)`);
18209
18772
  } else if (resolved.applicableAllow.length || resolved.applicableDeny.length || resolved.removalsThisSync.allow || resolved.removalsThisSync.deny) {
18210
18773
  const totalRemovals = resolved.removalsThisSync.allow + resolved.removalsThisSync.deny;
18211
18774
  const removalNote = totalRemovals > 0 ? ` (${totalRemovals} new opt-out${totalRemovals > 1 ? "s" : ""} honored)` : "";
18212
- console.log(` [${adapter2.name}] Applied defaults: ${resolved.applicableAllow.length} allow, ${resolved.applicableDeny.length} deny${removalNote}`);
18775
+ vlog(` [${adapter2.name}] Applied defaults: ${resolved.applicableAllow.length} allow, ${resolved.applicableDeny.length} deny${removalNote}`);
18213
18776
  }
18214
18777
  }
18215
18778
  if (team)
@@ -18223,11 +18786,12 @@ async function syncFromState(state, statePath2, credentials, opts) {
18223
18786
  const runworkDir = join35(homedir17(), ".runwork");
18224
18787
  const result = adapter2.registerDesktopWorkspace(runworkDir, "Runwork");
18225
18788
  if (result === "written") {
18226
- console.log(` [${adapter2.name}] Registered workspace in Codex desktop app`);
18789
+ vlog(` [${adapter2.name}] Registered workspace in Codex desktop app`);
18227
18790
  }
18228
18791
  break;
18229
18792
  }
18230
18793
  }
18794
+ const prevMcpNames = state.mcpServers ?? [];
18231
18795
  state.lastSyncAt = new Date().toISOString();
18232
18796
  state.mcpServers = mcpEntries.map((e) => e.name);
18233
18797
  state.skills = remoteSkills.map((s) => s.name);
@@ -18235,6 +18799,57 @@ async function syncFromState(state, statePath2, credentials, opts) {
18235
18799
  "runwork",
18236
18800
  ...remoteSkills.map((s) => s.name.toLowerCase().replace(/[^a-z0-9]+/g, "-"))
18237
18801
  ];
18802
+ const priorSkillHashes = state.skillHashes || {};
18803
+ const newLocalHashes = {};
18804
+ for (const [name, h] of Object.entries(newHashes))
18805
+ newLocalHashes[name] = h.localHash;
18806
+ const priorLocalHashes = {};
18807
+ for (const [name, h] of Object.entries(priorSkillHashes))
18808
+ priorLocalHashes[name] = h.localHash;
18809
+ const materializedDeletions = plan.deletions.filter((d) => d.localContent !== undefined).map((d) => d.name);
18810
+ const skillChanges = summarizeRealSkillChanges(newLocalHashes, priorLocalHashes, materializedDeletions);
18811
+ const newMcpNames = mcpEntries.map((e) => e.name);
18812
+ const prevMcpSet = new Set(prevMcpNames);
18813
+ const newMcpSet = new Set(newMcpNames);
18814
+ const mcpAdded = mcpChanged ? newMcpNames.filter((n) => !prevMcpSet.has(n)) : [];
18815
+ const mcpRemoved = mcpChanged ? prevMcpNames.filter((n) => !newMcpSet.has(n)) : [];
18816
+ const addedSet = new Set(addedThisSync);
18817
+ const restartReasons = computeRestartReasons({
18818
+ agents: adapters.map((a) => ({
18819
+ slug: a.slug,
18820
+ receivesMcp: mcpEntries.length > 0 && scopes.some((sc) => a.supportsMcpScope(sc)) && !mcpFailedAdapters.has(a.slug),
18821
+ connectOnly: isConnectOnlyAgent(getRegistryAgent(a.slug)),
18822
+ isNew: !!opts.initialSetup || addedSet.has(a.slug)
18823
+ })),
18824
+ mcpChanged,
18825
+ settingsChangedSlugs
18826
+ });
18827
+ if (Object.keys(restartReasons).length > 0) {
18828
+ if (!state.agentConfigChangedAt)
18829
+ state.agentConfigChangedAt = {};
18830
+ for (const [slug, reasons] of Object.entries(restartReasons)) {
18831
+ state.agentConfigChangedAt[slug] = { changedAt: state.lastSyncAt, reasons };
18832
+ }
18833
+ }
18834
+ if (hasRealSkillChange(skillChanges) || builtInSkillsChanged || mcpChanged || settingsChangedSlugs.length > 0) {
18835
+ state.lastSyncChange = {
18836
+ at: state.lastSyncAt,
18837
+ skills: skillChanges,
18838
+ builtInSkillsChanged,
18839
+ mcp: { changed: mcpChanged, added: mcpAdded, removed: mcpRemoved },
18840
+ settingsSlugs: settingsChangedSlugs
18841
+ };
18842
+ }
18843
+ if (mcpFailedAdapters.size === 0)
18844
+ state.lastMcpEntriesHash = mcpEntriesHash;
18845
+ if (skillFailedAdapters.size === 0)
18846
+ state.builtInSkillsHash = builtInSkillsHash;
18847
+ const nameBySlug = new Map(adapters.map((a) => [a.slug, a.name]));
18848
+ const restartChanges = Object.entries(restartReasons).map(([slug, reasons]) => ({
18849
+ name: nameBySlug.get(slug) ?? slug,
18850
+ reasons
18851
+ }));
18852
+ console.log(` Restart-worthy changes: ${formatRestartChanges(restartChanges)}`);
18238
18853
  const mergedHashes = { ...state.skillHashes || {} };
18239
18854
  for (const [name, hash] of Object.entries(newHashes)) {
18240
18855
  mergedHashes[name] = hash;
@@ -18256,25 +18871,29 @@ async function syncFromState(state, statePath2, credentials, opts) {
18256
18871
  if (opts.verbose)
18257
18872
  printTelemetryVerbose(telemetry);
18258
18873
  await client.reportTelemetry(state.workspaceId, telemetry.events);
18259
- if (telemetry.healthReported)
18874
+ if (telemetry.healthReported) {
18260
18875
  state.lastHealthReportAt = new Date().toISOString();
18261
- state.reportedAgentSlugs = [
18262
- ...new Set([...state.reportedAgentSlugs ?? [], ...telemetry.newlyReportedAgents])
18263
- ];
18264
- writeFileSync29(statePath2, JSON.stringify(state, null, 2));
18876
+ writeFileSync29(statePath2, JSON.stringify(state, null, 2));
18877
+ }
18265
18878
  } catch {}
18266
- const summaryParts = [];
18267
- summaryParts.push(`${summary.adaptersProcessed} adapter${summary.adaptersProcessed === 1 ? "" : "s"}`);
18268
- if (summary.adaptersFailed > 0)
18269
- summaryParts.push(`${summary.adaptersFailed} failed`);
18270
- summaryParts.push(`${summary.skillWrites} skill write${summary.skillWrites === 1 ? "" : "s"} (${summary.skillFilesWritten} files)`);
18271
- if (summary.mcpServerWrites > 0)
18272
- summaryParts.push(`${summary.mcpServerWrites} MCP config write${summary.mcpServerWrites === 1 ? "" : "s"}`);
18273
- if (summary.hookInstallCalls > 0)
18274
- summaryParts.push(`${summary.hookInstallCalls} hook install${summary.hookInstallCalls === 1 ? "" : "s"}`);
18275
- summaryParts.push(`${summary.instructionHintWrites} instruction hint write${summary.instructionHintWrites === 1 ? "" : "s"}`);
18276
- console.log(`
18879
+ const failedNote = summary.adaptersFailed > 0 ? ` (${summary.adaptersFailed} failed)` : "";
18880
+ if (isVerbose()) {
18881
+ const summaryParts = [];
18882
+ summaryParts.push(`${summary.adaptersProcessed} adapter${summary.adaptersProcessed === 1 ? "" : "s"}`);
18883
+ if (summary.adaptersFailed > 0)
18884
+ summaryParts.push(`${summary.adaptersFailed} failed`);
18885
+ summaryParts.push(`${summary.skillWrites} skill write${summary.skillWrites === 1 ? "" : "s"} (${summary.skillFilesWritten} files)`);
18886
+ if (summary.mcpServerWrites > 0)
18887
+ summaryParts.push(`${summary.mcpServerWrites} MCP config write${summary.mcpServerWrites === 1 ? "" : "s"}`);
18888
+ if (summary.hookInstallCalls > 0)
18889
+ summaryParts.push(`${summary.hookInstallCalls} hook install${summary.hookInstallCalls === 1 ? "" : "s"}`);
18890
+ summaryParts.push(`${summary.instructionHintWrites} instruction hint write${summary.instructionHintWrites === 1 ? "" : "s"}`);
18891
+ console.log(`
18277
18892
  Summary: ${summaryParts.join(", ")}.`);
18893
+ } else {
18894
+ console.log(`
18895
+ ${summary.adaptersProcessed} agent${summary.adaptersProcessed === 1 ? "" : "s"} synced${failedNote}.`);
18896
+ }
18278
18897
  try {
18279
18898
  const cadence = loadCadenceState();
18280
18899
  const lastMs = cadence.lastReflectedAt ? new Date(cadence.lastReflectedAt).getTime() : 0;
@@ -18372,7 +18991,7 @@ async function resolveAndPersistWorkspace(client, opts) {
18372
18991
  }
18373
18992
  return { workspaceId, workspaceName, workspaceSlug };
18374
18993
  }
18375
- var setupCommand = new Command25("setup").description("Configure local AI agents with workspace skills and MCP servers").option("--workspace <id>", "Workspace ID").option("--agent <slug>", "Only configure a specific agent (e.g. claude-code, cursor)").option("--dry-run", "Show what would be configured without writing files").option("-y, --yes", "Skip all prompts, configure all detected agents with user scope").option("--persona <level>", "Technical-level persona for agent instructions (1=everyday, 2=curious, 3=engineer)").action(async (opts) => {
18994
+ var setupCommand = new Command25("setup").description("Configure local AI agents with workspace skills and MCP servers").option("--workspace <name-or-id>", "Workspace name or ID").option("--agent <slug>", "Only configure a specific agent (e.g. claude-code, cursor)").option("--dry-run", "Show what would be configured without writing files").option("-y, --yes", "Skip all prompts, configure all detected agents with user scope").option("--persona <level>", "Technical-level persona for agent instructions (1=everyday, 2=curious, 3=engineer)").action(async (opts) => {
18376
18995
  const credentials = requireAuth();
18377
18996
  const client = new ApiClient(credentials);
18378
18997
  const { workspaceId, workspaceName, workspaceSlug } = await resolveAndPersistWorkspace(client, opts);
@@ -18467,7 +19086,8 @@ Syncing workspace data...
18467
19086
  dryRun: false,
18468
19087
  pullOnly: true,
18469
19088
  yes: true,
18470
- prefer: "remote"
19089
+ prefer: "remote",
19090
+ initialSetup: true
18471
19091
  });
18472
19092
  }
18473
19093
  console.log("\nSetup complete. Run `runwork sync` anytime to refresh.");
@@ -18723,7 +19343,7 @@ init_store();
18723
19343
  init_client();
18724
19344
  import { Command as Command28 } from "commander";
18725
19345
  init_init();
18726
- var listCommand12 = new Command28("list").description("List apps in workspace").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
19346
+ var listCommand12 = new Command28("list").description("List apps in workspace").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
18727
19347
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
18728
19348
  const credentials = requireAuth();
18729
19349
  const client = new ApiClient(credentials);
@@ -18756,7 +19376,7 @@ Workspace: ${workspaceName || workspaceId}
18756
19376
  var createCommand3 = new Command28("create").description("Create a new Runwork app").argument("[name]", "App name").action(async (name) => {
18757
19377
  await runCreateFlow(name);
18758
19378
  });
18759
- var infoCommand2 = new Command28("info").description("Show detailed app info, preview status, and registries").argument("[app]", "App ID, name, or slug").option("--workspace <id>", "Workspace ID").action(async (appArg, opts, command) => {
19379
+ var infoCommand2 = new Command28("info").description("Show detailed app info, preview status, and registries").argument("[app]", "App ID, name, or slug").option("--workspace <name-or-id>", "Workspace name or ID").action(async (appArg, opts, command) => {
18760
19380
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
18761
19381
  const credentials = requireAuth();
18762
19382
  const client = new ApiClient(credentials);
@@ -18783,15 +19403,157 @@ var infoCommand2 = new Command28("info").description("Show detailed app info, pr
18783
19403
  });
18784
19404
  var appsCommand = new Command28("apps").description("Manage workspace apps").addCommand(listCommand12).addCommand(createCommand3).addCommand(infoCommand2);
18785
19405
 
19406
+ // src/commands/members.ts
19407
+ init_store();
19408
+ init_client();
19409
+ import { Command as Command29 } from "commander";
19410
+ function formatMemberRows(members) {
19411
+ return members.map((m) => ({
19412
+ name: m.user.displayName || m.user.email,
19413
+ email: m.user.email,
19414
+ role: m.role,
19415
+ status: m.status,
19416
+ userId: m.userId
19417
+ }));
19418
+ }
19419
+ var listCommand13 = new Command29("list").description("List members of a workspace (name, email, role)").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
19420
+ const useJson = shouldOutputJson(command.optsWithGlobals().json);
19421
+ const credentials = requireAuth();
19422
+ const client = new ApiClient(credentials);
19423
+ const { workspaceId, workspaceName } = await resolveWorkspace2(client, opts);
19424
+ try {
19425
+ const members = await client.listWorkspaceMembers(workspaceId);
19426
+ if (useJson) {
19427
+ jsonOut({ workspaceId, workspaceName, members });
19428
+ return;
19429
+ }
19430
+ if (members.length === 0) {
19431
+ console.log("No members found in this workspace.");
19432
+ return;
19433
+ }
19434
+ console.log(`
19435
+ Workspace: ${workspaceName || workspaceId}
19436
+ `);
19437
+ console.log(` ${"NAME".padEnd(24)} ${"EMAIL".padEnd(32)} ${"ROLE".padEnd(10)} STATUS`);
19438
+ console.log(" " + "-".repeat(76));
19439
+ for (const row of formatMemberRows(members)) {
19440
+ console.log(` ${row.name.padEnd(24)} ${row.email.padEnd(32)} ${row.role.padEnd(10)} ${row.status}`);
19441
+ }
19442
+ console.log("");
19443
+ } catch (err) {
19444
+ console.error("Failed to list members:", err instanceof Error ? err.message : err);
19445
+ process.exit(1);
19446
+ }
19447
+ });
19448
+ var membersCommand = new Command29("members").description("List workspace members").addCommand(listCommand13);
19449
+
19450
+ // src/commands/api.ts
19451
+ init_store();
19452
+ init_client();
19453
+ import { Command as Command30 } from "commander";
19454
+ import { readFileSync as readFileSync37 } from "fs";
19455
+ function normalizeApiPath(rawPath, baseUrl) {
19456
+ if (/^https?:\/\//i.test(rawPath)) {
19457
+ const target = new URL(rawPath);
19458
+ const base = new URL(baseUrl);
19459
+ if (target.origin !== base.origin) {
19460
+ throw new Error(`Refusing to call ${target.origin}: \`runwork api\` only calls the platform API at ${base.origin}.`);
19461
+ }
19462
+ return target.pathname + target.search;
19463
+ }
19464
+ return rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
19465
+ }
19466
+ var apiCommand = new Command30("api").description("Make an authenticated request to the Runwork platform API (escape hatch for endpoints the CLI does not cover)").argument("<method>", "HTTP method (GET, POST, PUT, DELETE, etc.)").argument("[path]", "API path (e.g. /api/workspaces); optional with --curl").option("--body <json>", "Request body JSON (or @file.json)").option("--header <header>", 'Request header (format: "Key: Value", repeatable)', (val, prev) => [...prev, val], []).option("--query <query>", "Query string (e.g. limit=10&offset=0)").option("--curl <command>", "Parse a curl command (method, path, headers, body)").option("--curl-file <file>", "Read curl command from a file").addHelpText("after", `
19467
+ Examples:
19468
+ runwork api GET /api/workspaces
19469
+ runwork api GET /api/workspaces/<id>/members
19470
+ runwork api POST /api/workspaces/<id>/skills --body '{"name":"my-skill"}'
19471
+
19472
+ Authentication uses your stored Runwork credentials; the API key never needs
19473
+ to be read or pasted manually. Prefer a dedicated command when one exists
19474
+ (runwork apps/members/schedules/...).`).action(async (method, path2, opts, command) => {
19475
+ const useJson = shouldOutputJson(command.optsWithGlobals().json);
19476
+ const credentials = requireAuth();
19477
+ const client = new ApiClient(credentials);
19478
+ let finalMethod = method;
19479
+ let finalPath = path2;
19480
+ const headers = {};
19481
+ let body;
19482
+ const query = opts.query;
19483
+ if (opts.curl || opts.curlFile) {
19484
+ let curlStr = opts.curl;
19485
+ if (opts.curlFile) {
19486
+ try {
19487
+ curlStr = readFileSync37(opts.curlFile, "utf-8");
19488
+ } catch (err) {
19489
+ console.error(`Could not read --curl-file: ${err instanceof Error ? err.message : err}`);
19490
+ process.exit(1);
19491
+ }
19492
+ }
19493
+ const parsed = await parseCurlToRequest(curlStr);
19494
+ finalMethod = parsed.method;
19495
+ finalPath = parsed.path + (parsed.query ? `?${parsed.query}` : "");
19496
+ Object.assign(headers, parsed.headers);
19497
+ body = parsed.body;
19498
+ }
19499
+ for (const h of opts.header || []) {
19500
+ const [key, ...rest] = h.split(":");
19501
+ headers[key.trim()] = rest.join(":").trim();
19502
+ }
19503
+ if (opts.body) {
19504
+ let raw = opts.body;
19505
+ if (raw.startsWith("@")) {
19506
+ try {
19507
+ raw = readFileSync37(raw.slice(1), "utf-8");
19508
+ } catch (err) {
19509
+ console.error(`Could not read body file: ${err instanceof Error ? err.message : err}`);
19510
+ process.exit(1);
19511
+ }
19512
+ }
19513
+ try {
19514
+ body = JSON.parse(raw);
19515
+ } catch {
19516
+ console.error("Invalid JSON in --body");
19517
+ process.exit(1);
19518
+ }
19519
+ }
19520
+ if (!finalPath) {
19521
+ console.error("Usage: runwork api <method> <path>");
19522
+ console.error(' or: runwork api <method> --curl "curl ..."');
19523
+ process.exit(1);
19524
+ }
19525
+ try {
19526
+ const normalizedPath = normalizeApiPath(finalPath, credentials.baseUrl || "https://runwork.ai");
19527
+ const result = await client.rawApiCall(finalMethod, normalizedPath, {
19528
+ body,
19529
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
19530
+ query
19531
+ });
19532
+ if (useJson) {
19533
+ jsonOut({ success: result.ok, status: result.status, body: result.body });
19534
+ } else if (typeof result.body === "string") {
19535
+ console.log(result.body);
19536
+ } else {
19537
+ console.log(JSON.stringify(result.body, null, 2));
19538
+ }
19539
+ if (!result.ok)
19540
+ process.exit(1);
19541
+ } catch (err) {
19542
+ console.error("API call failed:", err instanceof Error ? err.message : err);
19543
+ process.exit(1);
19544
+ }
19545
+ });
19546
+
18786
19547
  // src/commands/doctor.ts
18787
19548
  init_colors();
18788
- import { Command as Command29 } from "commander";
19549
+ import { Command as Command31 } from "commander";
18789
19550
 
18790
19551
  // src/health/checks.ts
18791
19552
  init_subprocess();
18792
19553
  init_store();
18793
19554
  init_client();
18794
- import { existsSync as existsSync44, readFileSync as readFileSync37 } from "fs";
19555
+ import { parse as parse2 } from "smol-toml";
19556
+ import { existsSync as existsSync44, readFileSync as readFileSync38 } from "fs";
18795
19557
  import { join as join40, sep as sep3 } from "path";
18796
19558
  import { homedir as homedir22, platform as osPlatform2, arch as osArch } from "os";
18797
19559
  init_http();
@@ -18827,7 +19589,7 @@ function buildContext() {
18827
19589
  const configPath = join40(process.cwd(), ".runwork.json");
18828
19590
  if (existsSync44(configPath)) {
18829
19591
  try {
18830
- config = JSON.parse(readFileSync37(configPath, "utf-8"));
19592
+ config = JSON.parse(readFileSync38(configPath, "utf-8"));
18831
19593
  } catch {}
18832
19594
  }
18833
19595
  return { credentials, client, config, cwd: process.cwd() };
@@ -19047,6 +19809,14 @@ async function checkGitCredentialHelper(ctx) {
19047
19809
  fix: "runwork doctor --fix (re-registers with the current binary path)"
19048
19810
  };
19049
19811
  }
19812
+ if (!lookup.hasReset) {
19813
+ return {
19814
+ name: "git-credential-helper",
19815
+ status: "fail",
19816
+ message: `helper registered for ${origin} but the credential-manager reset entry is missing (Windows may show a Git Credential Manager popup)`,
19817
+ fix: "runwork doctor --fix"
19818
+ };
19819
+ }
19050
19820
  return {
19051
19821
  name: "git-credential-helper",
19052
19822
  status: "pass",
@@ -19176,7 +19946,7 @@ function loadSetupState5() {
19176
19946
  for (const p of [projectPath, userPath]) {
19177
19947
  if (existsSync44(p)) {
19178
19948
  try {
19179
- return JSON.parse(readFileSync37(p, "utf-8"));
19949
+ return JSON.parse(readFileSync38(p, "utf-8"));
19180
19950
  } catch {
19181
19951
  continue;
19182
19952
  }
@@ -19184,6 +19954,95 @@ function loadSetupState5() {
19184
19954
  }
19185
19955
  return null;
19186
19956
  }
19957
+ var RUNWORK_NETWORK_DOMAINS = ["runwork.ai", "*.runwork.ai"];
19958
+ async function checkCodexNetwork() {
19959
+ const name = "codex-network";
19960
+ const state = loadSetupState5();
19961
+ if (!state || !state.configuredAgents.includes("codex")) {
19962
+ return { name, status: "skip", message: "Codex not configured for Runwork" };
19963
+ }
19964
+ const configPath = join40(homedir22(), ".codex", "config.toml");
19965
+ if (!existsSync44(configPath)) {
19966
+ return { name, status: "skip", message: "no Codex config found" };
19967
+ }
19968
+ let parsed;
19969
+ try {
19970
+ parsed = parse2(readFileSync38(configPath, "utf-8"));
19971
+ } catch {
19972
+ return { name, status: "warn", message: "could not parse ~/.codex/config.toml" };
19973
+ }
19974
+ const features = parsed.features && typeof parsed.features === "object" ? parsed.features : undefined;
19975
+ const proxy = features?.network_proxy;
19976
+ if (proxy && proxy.enabled === true) {
19977
+ const domains = proxy.domains && typeof proxy.domains === "object" ? proxy.domains : {};
19978
+ if (RUNWORK_NETWORK_DOMAINS.some((d) => domains[d] === "deny")) {
19979
+ return {
19980
+ name,
19981
+ status: "warn",
19982
+ message: "network proxy denies runwork.ai; the Runwork CLI is blocked",
19983
+ fix: 'remove the runwork.ai "deny" rule in ~/.codex/config.toml'
19984
+ };
19985
+ }
19986
+ if (RUNWORK_NETWORK_DOMAINS.every((d) => domains[d] === "allow")) {
19987
+ return { name, status: "pass", message: "proxy-allowlisted: runwork.ai permitted, other domains scoped by your proxy" };
19988
+ }
19989
+ return {
19990
+ name,
19991
+ status: "warn",
19992
+ message: "network proxy is on but runwork.ai is not allowlisted",
19993
+ fix: "runwork sync"
19994
+ };
19995
+ }
19996
+ const sww = parsed.sandbox_workspace_write && typeof parsed.sandbox_workspace_write === "object" ? parsed.sandbox_workspace_write : undefined;
19997
+ const sandboxMode = typeof parsed.sandbox_mode === "string" ? parsed.sandbox_mode : undefined;
19998
+ if (sww?.network_access === true || sandboxMode === "danger-full-access") {
19999
+ return { name, status: "pass", message: "open: outbound network enabled (all domains)" };
20000
+ }
20001
+ if (sww?.network_access === false) {
20002
+ return {
20003
+ name,
20004
+ status: "warn",
20005
+ message: "off: network explicitly disabled; the Runwork CLI cannot reach the network",
20006
+ fix: "set [sandbox_workspace_write] network_access = true, or run runwork sync"
20007
+ };
20008
+ }
20009
+ return {
20010
+ name,
20011
+ status: "warn",
20012
+ message: "off: network is disabled by default in Codex workspace-write sandbox; the Runwork CLI cannot reach the network",
20013
+ fix: "runwork sync"
20014
+ };
20015
+ }
20016
+ async function checkCodexDesktopProject() {
20017
+ const name = "codex-desktop-project";
20018
+ const state = loadSetupState5();
20019
+ const usesCodex = !!state && (state.configuredAgents.includes("codex-app") || state.configuredAgents.includes("codex"));
20020
+ if (!usesCodex) {
20021
+ return { name, status: "skip", message: "Codex not configured for Runwork" };
20022
+ }
20023
+ const statePath2 = join40(homedir22(), ".codex", ".codex-global-state.json");
20024
+ if (!existsSync44(statePath2)) {
20025
+ return { name, status: "skip", message: "Codex desktop app not detected" };
20026
+ }
20027
+ let savedRoots = [];
20028
+ try {
20029
+ const parsed = JSON.parse(readFileSync38(statePath2, "utf-8"));
20030
+ const roots = parsed["electron-saved-workspace-roots"];
20031
+ savedRoots = Array.isArray(roots) ? roots.filter((r) => typeof r === "string") : [];
20032
+ } catch {
20033
+ return { name, status: "warn", message: "could not read Codex desktop state" };
20034
+ }
20035
+ const runworkDir = join40(homedir22(), ".runwork");
20036
+ if (savedRoots.includes(runworkDir)) {
20037
+ return { name, status: "pass", message: "Runwork project added to Codex desktop sidebar" };
20038
+ }
20039
+ return {
20040
+ name,
20041
+ status: "warn",
20042
+ message: "Runwork project not added to Codex desktop sidebar",
20043
+ fix: "quit the Codex app, then run runwork sync (sync skips this while Codex is open)"
20044
+ };
20045
+ }
19187
20046
  async function checkAgentSetup() {
19188
20047
  const state = loadSetupState5();
19189
20048
  if (!state) {
@@ -19228,7 +20087,7 @@ async function checkAgentSetup() {
19228
20087
  const mcpConfigPath = getMcpConfigPath2(slug, "user");
19229
20088
  if (mcpConfigPath && existsSync44(mcpConfigPath)) {
19230
20089
  try {
19231
- const content = readFileSync37(mcpConfigPath, "utf-8");
20090
+ const content = readFileSync38(mcpConfigPath, "utf-8");
19232
20091
  const missingMcp = state.mcpServers.filter((name) => !content.includes(name));
19233
20092
  if (missingMcp.length > 0) {
19234
20093
  details.push(`${missingMcp.length} MCP server(s) missing from ${slug} config`);
@@ -19286,7 +20145,8 @@ function getMcpConfigPath2(slug, scope) {
19286
20145
  case "windsurf":
19287
20146
  return scope === "project" ? join40(process.cwd(), ".windsurf", "mcp.json") : join40(home, ".windsurf", "mcp.json");
19288
20147
  case "codex":
19289
- return scope === "user" ? join40(home, ".codex", "mcp.json") : null;
20148
+ case "codex-app":
20149
+ return scope === "user" ? join40(home, ".codex", "config.toml") : null;
19290
20150
  case "gemini":
19291
20151
  return scope === "user" ? join40(home, ".gemini", "settings.json") : null;
19292
20152
  default:
@@ -19299,6 +20159,7 @@ function getSkillsDir(slug, scope) {
19299
20159
  case "claude-code":
19300
20160
  return scope === "project" ? join40(process.cwd(), ".claude", "skills") : join40(home, ".claude", "skills");
19301
20161
  case "codex":
20162
+ case "codex-app":
19302
20163
  return scope === "project" ? join40(process.cwd(), ".codex", "skills") : join40(home, ".codex", "skills");
19303
20164
  case "gemini":
19304
20165
  return scope === "project" ? join40(process.cwd(), ".gemini", "skills") : join40(home, ".gemini", "skills");
@@ -19325,7 +20186,9 @@ var CHECK_RUNNERS = [
19325
20186
  { names: ["app-exists"], run: async (ctx) => [await checkAppExists(ctx)] },
19326
20187
  { names: ["git-remote"], run: async (ctx) => [await checkGitRemote(ctx)] },
19327
20188
  { names: ["deploy-freshness"], run: async (ctx) => [await checkDeployFreshness(ctx)] },
19328
- { names: ["agent-setup"], run: async () => [await checkAgentSetup()] }
20189
+ { names: ["agent-setup"], run: async () => [await checkAgentSetup()] },
20190
+ { names: ["codex-network"], run: async () => [await checkCodexNetwork()] },
20191
+ { names: ["codex-desktop-project"], run: async () => [await checkCodexDesktopProject()] }
19329
20192
  ];
19330
20193
  var ALL_CHECK_NAMES = CHECK_RUNNERS.flatMap((r) => r.names);
19331
20194
  async function runAllChecks(options) {
@@ -19398,7 +20261,7 @@ async function applyDoctorFixes(ctx, failingNames) {
19398
20261
  }
19399
20262
 
19400
20263
  // src/agents/runtime-detection.ts
19401
- import { existsSync as existsSync46, readFileSync as readFileSync38, statSync as statSync6, readdirSync as readdirSync13 } from "fs";
20264
+ import { existsSync as existsSync46, readFileSync as readFileSync39, statSync as statSync6, readdirSync as readdirSync13 } from "fs";
19402
20265
  import { homedir as homedir23 } from "os";
19403
20266
  import { join as join42 } from "path";
19404
20267
  var RUNWORK_SESSIONS_DIR = join42(homedir23(), ".runwork", "sessions");
@@ -19468,7 +20331,7 @@ function readHookSessionInfo(sessionId) {
19468
20331
  if (!existsSync46(path2))
19469
20332
  return null;
19470
20333
  try {
19471
- const raw = readFileSync38(path2, "utf8");
20334
+ const raw = readFileSync39(path2, "utf8");
19472
20335
  const parsed = JSON.parse(raw);
19473
20336
  return parsed;
19474
20337
  } catch {
@@ -19688,7 +20551,9 @@ var CHECK_LABELS = {
19688
20551
  "app-exists": "App exists",
19689
20552
  "git-remote": "Git remote",
19690
20553
  "deploy-freshness": "Deploy freshness",
19691
- "agent-setup": "Agent setup"
20554
+ "agent-setup": "Agent setup",
20555
+ "codex-network": "Codex network",
20556
+ "codex-desktop-project": "Codex project"
19692
20557
  };
19693
20558
  function printHumanReport(report) {
19694
20559
  console.log("");
@@ -19753,7 +20618,7 @@ function parseCheckNames(raw) {
19753
20618
  const unknown = requested.filter((n) => !known.has(n));
19754
20619
  return { only, unknown };
19755
20620
  }
19756
- var doctorCommand = new Command29("doctor").description("Check system health: auth, network, project config, agent setup").option("-v, --verbose", "Include host-agent detection results, runtime info, and allowlisted env vars (useful for AI agents debugging their own environment)").option("--check <names>", `Run only the named checks (comma-separated). Available: ${ALL_CHECK_NAMES.join(", ")}`).option("--fix", "Auto-remediate fixable failures (git credential helper, runwork remote), then re-check").action(async (opts, command) => {
20621
+ var doctorCommand = new Command31("doctor").description("Check system health: auth, network, project config, agent setup").option("-v, --verbose", "Include host-agent detection results, runtime info, and allowlisted env vars (useful for AI agents debugging their own environment)").option("--check <names>", `Run only the named checks (comma-separated). Available: ${ALL_CHECK_NAMES.join(", ")}`).option("--fix", "Auto-remediate fixable failures (git credential helper, runwork remote), then re-check").action(async (opts, command) => {
19757
20622
  const asJson = shouldOutputJson(command.optsWithGlobals().json);
19758
20623
  let only;
19759
20624
  if (opts.check) {
@@ -19804,8 +20669,8 @@ var doctorCommand = new Command29("doctor").description("Check system health: au
19804
20669
  // src/commands/share-convo.ts
19805
20670
  init_store();
19806
20671
  init_client();
19807
- import { Command as Command30 } from "commander";
19808
- import { readFileSync as readFileSync39, existsSync as existsSync47 } from "fs";
20672
+ import { Command as Command32 } from "commander";
20673
+ import { readFileSync as readFileSync40, existsSync as existsSync47 } from "fs";
19809
20674
  import { createHash as createHash4 } from "crypto";
19810
20675
  function nativeBundleFormatForAgent(slug) {
19811
20676
  if (slug === "claude-code" || slug === "claude-desktop")
@@ -19847,7 +20712,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
19847
20712
  const credentials = requireAuth();
19848
20713
  const client = new ApiClient(credentials);
19849
20714
  const { workspaceId } = await resolveWorkspace2(client, { workspace: opts.workspace });
19850
- const transcriptContent = readFileSync39(opts.transcriptFile, "utf8");
20715
+ const transcriptContent = readFileSync40(opts.transcriptFile, "utf8");
19851
20716
  const bundles = [
19852
20717
  {
19853
20718
  format: "transcript",
@@ -19872,7 +20737,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
19872
20737
  const nativeFormat = nativeBundleFormatForAgent(sourceAgent);
19873
20738
  if (nativeFormat) {
19874
20739
  try {
19875
- const content = readFileSync39(nativeFilePath, "utf8");
20740
+ const content = readFileSync40(nativeFilePath, "utf8");
19876
20741
  bundles.push({
19877
20742
  format: nativeFormat,
19878
20743
  content,
@@ -19888,7 +20753,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
19888
20753
  let metadata = {};
19889
20754
  if (opts.metadataFile) {
19890
20755
  try {
19891
- metadata = JSON.parse(readFileSync39(opts.metadataFile, "utf8"));
20756
+ metadata = JSON.parse(readFileSync40(opts.metadataFile, "utf8"));
19892
20757
  } catch (err) {
19893
20758
  console.error(`Error: --metadata-file is not valid JSON: ${err instanceof Error ? err.message : err}`);
19894
20759
  process.exit(1);
@@ -19943,17 +20808,17 @@ Skipped: ${result.skipped.map((s) => `${s.identifier} (${s.reason})`).join(", ")
19943
20808
  process.exit(1);
19944
20809
  }
19945
20810
  }
19946
- var shareConvoCommand = new Command30("share-convo").description("Share the current AI conversation with a teammate").option("--to <email>", "Recipient email (repeatable)", (value, prev = []) => [...prev, value], []).option("--transcript-file <path>", "Path to the LLM-emitted markdown transcript (required)").option("--native-file <path>", "Path to the native session file (optional; auto-detected from env vars otherwise)").option("--source-agent <slug>", "Override host-agent detection (e.g. claude-code, codex, claude-desktop)").option("--title <string>", "Short title for the conversation (required)").option("--note <string>", "Optional personal note to recipients").option("--ttl-days <n>", "Days until expiration (1-30, default 7)").option("--metadata-json <json>", "Inline JSON object with workMode, openQuestions, suggestedNextStep, etc.").option("--metadata-file <path>", "Path to a JSON file with the same metadata fields").option("--workspace <id>", "Workspace ID").action((opts, command) => runShareConvo(opts, command, false));
20811
+ var shareConvoCommand = new Command32("share-convo").description("Share the current AI conversation with a teammate").option("--to <email>", "Recipient email (repeatable)", (value, prev = []) => [...prev, value], []).option("--transcript-file <path>", "Path to the LLM-emitted markdown transcript (required)").option("--native-file <path>", "Path to the native session file (optional; auto-detected from env vars otherwise)").option("--source-agent <slug>", "Override host-agent detection (e.g. claude-code, codex, claude-desktop)").option("--title <string>", "Short title for the conversation (required)").option("--note <string>", "Optional personal note to recipients").option("--ttl-days <n>", "Days until expiration (1-30, default 7)").option("--metadata-json <json>", "Inline JSON object with workMode, openQuestions, suggestedNextStep, etc.").option("--metadata-file <path>", "Path to a JSON file with the same metadata fields").option("--workspace <name-or-id>", "Workspace name or ID").action((opts, command) => runShareConvo(opts, command, false));
19947
20812
 
19948
20813
  // src/commands/save-convo.ts
19949
- import { Command as Command31 } from "commander";
19950
- var saveConvoCommand = new Command31("save-convo").description("Save the current AI conversation as a personal checkpoint").option("--transcript-file <path>", "Path to the LLM-emitted markdown transcript (required)").option("--native-file <path>", "Path to the native session file (optional; auto-detected from env vars)").option("--source-agent <slug>", "Override host-agent detection").option("--title <string>", "Short title for the conversation (required)").option("--note <string>", "Optional note to your future self").option("--ttl-days <n>", "Days until expiration (1-30, default 7)").option("--metadata-json <json>", "Inline JSON object with workMode, openQuestions, etc.").option("--metadata-file <path>", "Path to a JSON file with metadata fields").option("--workspace <id>", "Workspace ID").action((opts, command) => runShareConvo({ ...opts, personal: true }, command, true));
20814
+ import { Command as Command33 } from "commander";
20815
+ var saveConvoCommand = new Command33("save-convo").description("Save the current AI conversation as a personal checkpoint").option("--transcript-file <path>", "Path to the LLM-emitted markdown transcript (required)").option("--native-file <path>", "Path to the native session file (optional; auto-detected from env vars)").option("--source-agent <slug>", "Override host-agent detection").option("--title <string>", "Short title for the conversation (required)").option("--note <string>", "Optional note to your future self").option("--ttl-days <n>", "Days until expiration (1-30, default 7)").option("--metadata-json <json>", "Inline JSON object with workMode, openQuestions, etc.").option("--metadata-file <path>", "Path to a JSON file with metadata fields").option("--workspace <name-or-id>", "Workspace name or ID").action((opts, command) => runShareConvo({ ...opts, personal: true }, command, true));
19951
20816
 
19952
20817
  // src/commands/inbox.ts
19953
20818
  init_store();
19954
20819
  init_client();
19955
- import { Command as Command32 } from "commander";
19956
- var inboxCommand = new Command32("inbox").description("List shared conversations visible to you").option("--filter <scope>", "Filter: all | received | sent | saved", "all").option("--limit <n>", "Max rows to return", "50").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
20820
+ import { Command as Command34 } from "commander";
20821
+ var inboxCommand = new Command34("inbox").description("List shared conversations visible to you").option("--filter <scope>", "Filter: all | received | sent | saved", "all").option("--limit <n>", "Max rows to return", "50").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
19957
20822
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
19958
20823
  const scope = opts.filter === "received" || opts.filter === "sent" || opts.filter === "saved" ? opts.filter : "all";
19959
20824
  const limit = opts.limit ? parseInt(opts.limit, 10) : 50;
@@ -19993,7 +20858,7 @@ Shared conversations (${scope}, ${total}):
19993
20858
  // src/commands/resume.ts
19994
20859
  init_store();
19995
20860
  init_client();
19996
- import { Command as Command33 } from "commander";
20861
+ import { Command as Command35 } from "commander";
19997
20862
  import { writeFileSync as writeFileSync31, mkdirSync as mkdirSync28, realpathSync } from "fs";
19998
20863
  import { homedir as homedir24 } from "os";
19999
20864
  import { join as join43 } from "path";
@@ -20067,7 +20932,7 @@ function isAgentInstalled(agent) {
20067
20932
  }
20068
20933
  return false;
20069
20934
  }
20070
- var resumeCommand2 = new Command33("resume").description("Resume a shared conversation locally in your agent of choice").argument("<share-id>", "The share ID (sc_*) returned by share-convo or save-convo").option("--agent <slug>", "Override target agent (e.g. claude-code, codex)").option("--into <path>", "Override target cwd (defaults to current $PWD)").option("--dry-run", "Print the resume command instead of executing it").option("--pick", "Show interactive picker (requires TTY) - not yet implemented").option("--workspace <id>", "Workspace ID").action(async (shareId, opts, command) => {
20935
+ var resumeCommand2 = new Command35("resume").description("Resume a shared conversation locally in your agent of choice").argument("<share-id>", "The share ID (sc_*) returned by share-convo or save-convo").option("--agent <slug>", "Override target agent (e.g. claude-code, codex)").option("--into <path>", "Override target cwd (defaults to current $PWD)").option("--dry-run", "Print the resume command instead of executing it").option("--pick", "Show interactive picker (requires TTY) - not yet implemented").option("--workspace <name-or-id>", "Workspace name or ID").action(async (shareId, opts, command) => {
20071
20936
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
20072
20937
  const credentials = requireAuth();
20073
20938
  const client = new ApiClient(credentials);
@@ -20292,7 +21157,7 @@ process.on("uncaughtException", (err) => {
20292
21157
  console.error(`Uncaught exception: ${formatError(err)}`);
20293
21158
  process.exit(1);
20294
21159
  });
20295
- var program = new Command34;
21160
+ var program = new Command36;
20296
21161
  program.name("runwork").description("Runwork CLI - local development for Runwork apps").version(VERSION).option("--json", "Output as JSON (auto-enabled when stdout is not a TTY)");
20297
21162
  program.addCommand(infoCommand);
20298
21163
  program.addCommand(loginCommand);
@@ -20321,6 +21186,8 @@ program.addCommand(syncCommand);
20321
21186
  program.addCommand(buildPluginCommand);
20322
21187
  program.addCommand(uninstallCommand);
20323
21188
  program.addCommand(appsCommand);
21189
+ program.addCommand(membersCommand);
21190
+ program.addCommand(apiCommand);
20324
21191
  program.addCommand(doctorCommand);
20325
21192
  program.addCommand(shareConvoCommand);
20326
21193
  program.addCommand(saveConvoCommand);