runwork 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 = {}) {
@@ -5638,6 +5811,15 @@ export interface JobResult {
5638
5811
  /** Additional result data (varies by job) */
5639
5812
  [key: string]: unknown;
5640
5813
  }
5814
+ /**
5815
+ * Wire-safe schedule definition: what crosses the RPC boundary into
5816
+ * SchedulerDO. Deliberately excludes the handler function; over DO RPC a
5817
+ * function arrives as a callback stub, and invoking a stubbed handler
5818
+ * serializes JobContext back to the calling isolate, where env bindings (R2
5819
+ * buckets, DO namespaces) cannot be serialized (AGENTCLI-6). Handlers are
5820
+ * bound inside the DO from its own isolate's module registry.
5821
+ */
5822
+ export type ScheduledJobDefinition = Omit<ScheduledJob, 'handler'> & Partial<Pick<ScheduledJob, 'handler'>>;
5641
5823
  export interface JobLogger {
5642
5824
  info(message: string, data?: Record<string, unknown>): void;
5643
5825
  warn(message: string, data?: Record<string, unknown>): void;
@@ -5652,6 +5834,13 @@ export interface JobRunRecord {
5652
5834
  completedAt?: number;
5653
5835
  result?: JobResult;
5654
5836
  error?: string;
5837
+ /**
5838
+ * Stack trace of the failure. Stored because the error MESSAGE alone made
5839
+ * the 2026-07 "Could not serialize object of type ..." production failures
5840
+ * undiagnosable: the message names the workerd serializer, not the framework
5841
+ * call site that hit it (AGENTCLI-6).
5842
+ */
5843
+ errorStack?: string;
5655
5844
  }
5656
5845
  export interface ScheduleState {
5657
5846
  name: string;
@@ -5721,7 +5910,7 @@ export declare class SchedulerDO extends DurableObject<Env> {
5721
5910
  * Initialize the job scheduler with job definitions.
5722
5911
  * Can be called again after HMR to sync new/changed job definitions.
5723
5912
  */
5724
- initializeJobScheduler(jobs: ScheduledJob[]): Promise<void>;
5913
+ initializeJobScheduler(jobs: ScheduledJobDefinition[]): Promise<void>;
5725
5914
  /**
5726
5915
  * Handle alarm - execute due jobs
5727
5916
  */
@@ -7381,7 +7570,7 @@ function createKeyboardListener() {
7381
7570
  }
7382
7571
 
7383
7572
  // src/generated/version.ts
7384
- var VERSION = "0.17.0";
7573
+ var VERSION = "0.18.0";
7385
7574
 
7386
7575
  // src/commands/dev.ts
7387
7576
  var exports_dev = {};
@@ -7515,7 +7704,64 @@ async function execDev(options) {
7515
7704
  } else {
7516
7705
  console.log(dim("Starting dev session..."));
7517
7706
  }
7518
- const session = await client.startDevSession(config.appId);
7707
+ let session;
7708
+ try {
7709
+ const boot = await awaitDevSessionReady({
7710
+ start: () => client.startDevSession(config.appId),
7711
+ pollStatus: () => client.getDevStatus(config.appId),
7712
+ onWaiting: (elapsedMs) => {
7713
+ const seconds = Math.round(elapsedMs / 1000);
7714
+ if (useJson) {
7715
+ jsonLine({
7716
+ event: "startup",
7717
+ phase: "dev_session_waiting",
7718
+ elapsedSeconds: seconds,
7719
+ timestamp: ts(),
7720
+ note: "Sandbox is booting. Cold boots can take a few minutes; keep waiting."
7721
+ });
7722
+ } else {
7723
+ console.log(dim(` Still starting the sandbox... (${seconds}s elapsed; cold boots can take a few minutes)`));
7724
+ }
7725
+ },
7726
+ onStartError: (err) => {
7727
+ const message = err instanceof Error ? err.message : String(err);
7728
+ if (useJson) {
7729
+ jsonLine({
7730
+ event: "startup",
7731
+ phase: "dev_session_retrying",
7732
+ timestamp: ts(),
7733
+ warning: `Boot call failed (${message}). The sandbox may still be booting server-side; watching status until it becomes ready.`
7734
+ });
7735
+ } else {
7736
+ console.warn(yellow(` Boot call failed (${message}); watching sandbox status in case the boot completes server-side...`));
7737
+ }
7738
+ }
7739
+ });
7740
+ session = boot.session;
7741
+ } catch (error) {
7742
+ const isTimeout = error instanceof DevBootTimeoutError;
7743
+ const message = error instanceof Error ? error.message : String(error);
7744
+ if (useJson) {
7745
+ jsonLine({
7746
+ event: "error",
7747
+ phase: "dev_session",
7748
+ timestamp: ts(),
7749
+ error: {
7750
+ message,
7751
+ 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.",
7752
+ suggestions: [
7753
+ "Re-run `runwork dev --detach --json` in about a minute; if the sandbox finished booting server-side it will be picked up quickly",
7754
+ "Run `runwork logs` to inspect sandbox logs for boot errors",
7755
+ "Run `runwork doctor` to verify auth and connectivity"
7756
+ ]
7757
+ }
7758
+ });
7759
+ } else {
7760
+ console.error(red(`Failed to start dev session: ${message}`));
7761
+ console.error(dim(" Re-run `runwork dev` in about a minute; if the sandbox finished booting server-side it will be picked up quickly."));
7762
+ }
7763
+ process.exit(1);
7764
+ }
7519
7765
  await populateSkill(cwd, client, config.appId);
7520
7766
  if (useJson)
7521
7767
  jsonLine({ event: "startup", phase: "skill_fetched", timestamp: ts() });
@@ -7804,7 +8050,21 @@ async function runDevDetachParent(opts) {
7804
8050
  const outcome = await runAsDetachedParent({
7805
8051
  appDir: cwd,
7806
8052
  expectedAppId: config.appId,
7807
- childArgs
8053
+ childArgs,
8054
+ onWaiting: (elapsedMs) => {
8055
+ const seconds = Math.round(elapsedMs / 1000);
8056
+ if (opts.json) {
8057
+ jsonLine({
8058
+ event: "waiting",
8059
+ phase: "sandbox_boot",
8060
+ elapsedSeconds: seconds,
8061
+ timestamp: ts(),
8062
+ note: "Sandbox is booting. Cold boots can take a few minutes; keep waiting for session_started."
8063
+ });
8064
+ } else {
8065
+ console.log(dim(` Still starting... (${seconds}s elapsed; cold boots can take a few minutes)`));
8066
+ }
8067
+ }
7808
8068
  });
7809
8069
  switch (outcome.result) {
7810
8070
  case "started": {
@@ -7862,9 +8122,10 @@ async function runDevDetachParent(opts) {
7862
8122
  timestamp: ts(),
7863
8123
  error: {
7864
8124
  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.",
8125
+ 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
8126
  suggestions: [
7867
- "Inspect the child stderr at .runwork/dev-stderr.log",
8127
+ "Check childLogTail below; the child logs structured errors to .runwork/dev-stdout.log (stderr carries crash traces)",
8128
+ "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
8129
  "Run `runwork doctor` to verify auth and connectivity",
7869
8130
  "Try `runwork dev` (foreground) to see the failure inline"
7870
8131
  ],
@@ -7873,7 +8134,7 @@ async function runDevDetachParent(opts) {
7873
8134
  });
7874
8135
  } else {
7875
8136
  console.error(yellow(message));
7876
- console.error(dim(" Inspect .runwork/dev-stderr.log or run `runwork dev` to see what failed."));
8137
+ console.error(dim(" Inspect .runwork/dev-stdout.log and dev-stderr.log, or run `runwork dev` to see what failed."));
7877
8138
  if (outcome.childLogTail) {
7878
8139
  console.error(dim(" Tail of child stderr:"));
7879
8140
  for (const line of outcome.childLogTail.split(`
@@ -7887,7 +8148,8 @@ async function runDevDetachParent(opts) {
7887
8148
  return;
7888
8149
  }
7889
8150
  case "timeout": {
7890
- const message = `Detached dev session did not become ready within 90s.`;
8151
+ const timeoutSeconds = Math.round(DETACH_READY_TIMEOUT_MS / 1000);
8152
+ const message = `Detached dev session did not become ready within ${timeoutSeconds}s.`;
7891
8153
  if (opts.json) {
7892
8154
  jsonLine({
7893
8155
  event: "error",
@@ -7895,9 +8157,10 @@ async function runDevDetachParent(opts) {
7895
8157
  timestamp: ts(),
7896
8158
  error: {
7897
8159
  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.",
8160
+ 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
8161
  suggestions: [
7900
- "Inspect the child stderr at .runwork/dev-stderr.log",
8162
+ "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",
8163
+ "Inspect the child logs at .runwork/dev-stdout.log and .runwork/dev-stderr.log",
7901
8164
  "Run `runwork doctor` to verify auth and connectivity",
7902
8165
  "Try `runwork dev` (foreground) to see the failure inline"
7903
8166
  ],
@@ -7906,7 +8169,7 @@ async function runDevDetachParent(opts) {
7906
8169
  });
7907
8170
  } else {
7908
8171
  console.error(yellow(message));
7909
- console.error(dim(" Inspect .runwork/dev-stderr.log or run `runwork dev` to see what failed."));
8172
+ console.error(dim(" Inspect .runwork/dev-stdout.log and dev-stderr.log, or run `runwork dev` to see what failed."));
7910
8173
  if (outcome.childLogTail) {
7911
8174
  console.error(dim(" Tail of child stderr:"));
7912
8175
  for (const line of outcome.childLogTail.split(`
@@ -7956,6 +8219,7 @@ var init_dev = __esm(() => {
7956
8219
  init_tailer();
7957
8220
  init_session();
7958
8221
  init_detach();
8222
+ init_boot_await();
7959
8223
  init_stop();
7960
8224
  init_attach();
7961
8225
  init_types_manager();
@@ -8291,7 +8555,7 @@ var init_welcome = __esm(() => {
8291
8555
  });
8292
8556
 
8293
8557
  // src/index.ts
8294
- import { Command as Command34 } from "commander";
8558
+ import { Command as Command36 } from "commander";
8295
8559
 
8296
8560
  // src/commands/login.ts
8297
8561
  init_login_flow();
@@ -8394,8 +8658,23 @@ function getDeploySummary(cwd) {
8394
8658
  }
8395
8659
 
8396
8660
  // src/deploy/deploy-status.ts
8661
+ init_session();
8397
8662
  import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync12 } from "fs";
8398
8663
  import { dirname as dirname4, join as join17 } from "path";
8664
+ function evaluateDeployStatus(status, deps = {}) {
8665
+ if (status.state !== "in-progress") {
8666
+ return { status, effectiveState: status.state };
8667
+ }
8668
+ const bootTime = deps.bootTime ?? currentBootTime;
8669
+ const pidAlive = deps.pidAlive ?? isPidAlive;
8670
+ if (typeof status.bootTime === "number" && Math.abs(bootTime() - status.bootTime) > BOOT_TIME_TOLERANCE_MS) {
8671
+ return { status, effectiveState: "stale", staleReason: "boot-time-mismatch" };
8672
+ }
8673
+ if (typeof status.pid === "number" && !pidAlive(status.pid)) {
8674
+ return { status, effectiveState: "stale", staleReason: "process-gone" };
8675
+ }
8676
+ return { status, effectiveState: "in-progress" };
8677
+ }
8399
8678
  function statusPath(cwd) {
8400
8679
  return join17(cwd, ".runwork", "deploy-status.json");
8401
8680
  }
@@ -8425,6 +8704,9 @@ function readDeployStatus(cwd) {
8425
8704
  }
8426
8705
  }
8427
8706
 
8707
+ // src/commands/deploy.ts
8708
+ init_session();
8709
+
8428
8710
  // src/deploy/detach.ts
8429
8711
  init_detach();
8430
8712
  import * as fs5 from "fs";
@@ -8536,7 +8818,7 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
8536
8818
  if (opts.detach && !isChild) {
8537
8819
  const startedAt = new Date().toISOString();
8538
8820
  const handle = spawnDetachedDeploy(buildDeployChildArgs(process.argv), cwd);
8539
- writeDeployStatus(cwd, { state: "in-progress", startedAt, pid: handle.pid });
8821
+ writeDeployStatus(cwd, { state: "in-progress", startedAt, pid: handle.pid, bootTime: currentBootTime() });
8540
8822
  if (useJson) {
8541
8823
  jsonLine({ event: "deploy_started", detached: true, pid: handle.pid, logPath: deployLogPath(cwd), startedAt, hint: "Poll `runwork deploy --status` for completion." });
8542
8824
  } else {
@@ -8639,7 +8921,7 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
8639
8921
  const deployed = deploymentUrl.length > 0;
8640
8922
  if (!deployed) {
8641
8923
  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"]));
8924
+ 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
8925
  process.exit(1);
8644
8926
  }
8645
8927
  console.error("Deploy did not return a URL. The deploy may have been a no-op or failed server-side.");
@@ -8648,6 +8930,21 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
8648
8930
  }
8649
8931
  const deployedSha = getHeadSha(cwd);
8650
8932
  const finishedAt = new Date().toISOString();
8933
+ const shaVerdict = assessBuiltSha(deployedSha, result.builtFromSha);
8934
+ if (shaVerdict === "mismatch") {
8935
+ const detail = `The platform built this deploy from ${result.builtFromSha.slice(0, 7)} but your pushed HEAD is ${deployedSha.slice(0, 7)}. Production is NOT running your latest code.`;
8936
+ writeDeployStatus(cwd, { state: "failed", startedAt: childStartedAt, finishedAt, error: detail });
8937
+ deployFinalized = true;
8938
+ if (useJson) {
8939
+ jsonOut(buildErrorResponse("deploy", "Deployed artifact does not match pushed commit", detail, [
8940
+ "Run `runwork dev --restart --detach` to resync the build sandbox, then retry `runwork deploy`",
8941
+ "Verify with `runwork logs --production --once` which version is actually serving"
8942
+ ]));
8943
+ process.exit(1);
8944
+ }
8945
+ console.error(`Deploy verification failed: ${detail}`);
8946
+ process.exit(1);
8947
+ }
8651
8948
  if (deployedSha) {
8652
8949
  writeDeployState(cwd, { sha: deployedSha, deployedAt: finishedAt, url: deploymentUrl });
8653
8950
  }
@@ -8664,7 +8961,9 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
8664
8961
  deployed,
8665
8962
  url: deploymentUrl,
8666
8963
  appName: config.appName,
8667
- deployedSha
8964
+ deployedSha,
8965
+ builtFromSha: result.builtFromSha,
8966
+ shaVerified: shaVerdict === "match"
8668
8967
  },
8669
8968
  guide: buildDeployGuide(),
8670
8969
  ...deployGuardWarning ? { warning: deployGuardWarning } : {}
@@ -8674,19 +8973,34 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
8674
8973
  }
8675
8974
  console.log(`Deployed: ${deploymentUrl}`);
8676
8975
  if (deployedSha)
8677
- console.log(dim(`Commit: ${deployedSha.slice(0, 7)}`));
8976
+ console.log(dim(`Commit: ${deployedSha.slice(0, 7)}${shaVerdict === "match" ? " (verified)" : shaVerdict === "unverified" ? " (provenance unverified by server)" : ""}`));
8678
8977
  });
8978
+ function assessBuiltSha(localSha, builtFromSha) {
8979
+ if (!localSha || !builtFromSha)
8980
+ return "unverified";
8981
+ return builtFromSha === localSha ? "match" : "mismatch";
8982
+ }
8679
8983
  function printDeployStatus(cwd, useJson) {
8680
8984
  const status = readDeployStatus(cwd);
8681
8985
  const summary = getDeploySummary(cwd);
8986
+ const evaluated = status ? evaluateDeployStatus(status) : null;
8987
+ 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
8988
  if (useJson) {
8683
- jsonOut({ success: true, command: "deploy", result: { status, deploy: summary } });
8989
+ jsonOut({
8990
+ success: true,
8991
+ command: "deploy",
8992
+ result: {
8993
+ status: evaluated ? { ...evaluated.status, state: evaluated.effectiveState, staleReason: evaluated.staleReason } : null,
8994
+ deploy: summary,
8995
+ hint: evaluated?.effectiveState === "stale" ? staleHint : undefined
8996
+ }
8997
+ });
8684
8998
  return;
8685
8999
  }
8686
- if (!status) {
9000
+ if (!status || !evaluated) {
8687
9001
  console.log("No deploy has been started from this machine yet.");
8688
9002
  } else {
8689
- const label = status.state === "succeeded" ? green("succeeded") : status.state === "failed" ? yellow("failed") : "in-progress";
9003
+ const label = evaluated.effectiveState === "succeeded" ? green("succeeded") : evaluated.effectiveState === "failed" ? yellow("failed") : evaluated.effectiveState === "stale" ? yellow(`stale (${evaluated.staleReason})`) : "in-progress";
8690
9004
  console.log(`Last deploy: ${label}`);
8691
9005
  console.log(dim(` started: ${status.startedAt}`));
8692
9006
  if (status.finishedAt)
@@ -8695,8 +9009,10 @@ function printDeployStatus(cwd, useJson) {
8695
9009
  console.log(dim(` url: ${status.url}`));
8696
9010
  if (status.error)
8697
9011
  console.log(yellow(` error: ${status.error}`));
8698
- if (status.state === "in-progress")
9012
+ if (evaluated.effectiveState === "in-progress")
8699
9013
  console.log(dim(` logs: ${deployLogPath(cwd)}`));
9014
+ if (evaluated.effectiveState === "stale")
9015
+ console.log(yellow(` ${staleHint}`));
8700
9016
  }
8701
9017
  if (summary.deployedShortSha) {
8702
9018
  const sync = summary.inSync === true ? green("in sync") : summary.inSync === false ? yellow("local has undeployed commits") : dim("unknown");
@@ -9280,9 +9596,14 @@ import { readFileSync as readFileSync18, existsSync as existsSync21 } from "fs";
9280
9596
  init_store();
9281
9597
  init_prompt();
9282
9598
  import { existsSync as existsSync20, readFileSync as readFileSync17 } from "fs";
9599
+ 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
9600
  async function resolveWorkspace2(client, options = {}) {
9284
9601
  if (options.workspace) {
9285
- return { workspaceId: options.workspace, workspaceName: "", source: "flag" };
9602
+ if (UUID_RE.test(options.workspace)) {
9603
+ return { workspaceId: options.workspace, workspaceName: "", source: "flag" };
9604
+ }
9605
+ const ws2 = await resolveWorkspace(client, options.workspace);
9606
+ return { workspaceId: ws2.id, workspaceName: ws2.name, source: "flag" };
9286
9607
  }
9287
9608
  if (process.env.WORKSPACE_ID) {
9288
9609
  return { workspaceId: process.env.WORKSPACE_ID, workspaceName: "", source: "flag" };
@@ -9317,6 +9638,10 @@ async function resolveWorkspace2(client, options = {}) {
9317
9638
  saveDefaultWorkspace(ws2.id, ws2.name);
9318
9639
  return { workspaceId: ws2.id, workspaceName: ws2.name, source: "prompt" };
9319
9640
  }
9641
+ if (!process.stdout.isTTY) {
9642
+ console.error(`Workspace required. Pass --workspace <name-or-id>. Available: ${workspaces.map((w) => w.name).join(", ")}`);
9643
+ process.exit(1);
9644
+ }
9320
9645
  const choices = workspaces.map((ws2) => ({ label: ws2.name, value: ws2.id }));
9321
9646
  const chosen = await promptSelect("Select a workspace:", choices);
9322
9647
  const ws = workspaces.find((w) => w.id === chosen.value);
@@ -9545,7 +9870,13 @@ async function parseCurlToRequest(curlStr) {
9545
9870
  }
9546
9871
 
9547
9872
  // 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) => {
9873
+ function resolveIntegrationConnection(integrations, integration) {
9874
+ const match = integrations.find((i) => (i.canonicalId || i.integrationId) === integration || i.provider === integration);
9875
+ if (!match)
9876
+ return null;
9877
+ return { connectionId: match.id || match.integrationId, match };
9878
+ }
9879
+ 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
9880
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
9550
9881
  const credentials = requireAuth();
9551
9882
  const client = new ApiClient(credentials);
@@ -9604,7 +9935,7 @@ Usage: Add the integration ID to APP_INTEGRATION_REQUIREMENTS in worker/integrat
9604
9935
  process.exit(1);
9605
9936
  }
9606
9937
  });
9607
- var listCommand = new Command10("list").description("List connected workspace integrations").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
9938
+ var listCommand = new Command10("list").description("List connected workspace integrations").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
9608
9939
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
9609
9940
  const credentials = requireAuth();
9610
9941
  const client = new ApiClient(credentials);
@@ -9650,14 +9981,14 @@ Used by your team (${teamOnly.length} more):
9650
9981
  console.log(` ${t.canonicalId} - Used by ${display}`);
9651
9982
  }
9652
9983
  console.log(`
9653
- Connect at https://runwork.ai/workspace-settings?section=integrations`);
9984
+ Connect at https://runwork.ai/integrations`);
9654
9985
  }
9655
9986
  } catch (err) {
9656
9987
  console.error("Failed to list integrations:", err instanceof Error ? err.message : err);
9657
9988
  process.exit(1);
9658
9989
  }
9659
9990
  });
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) => {
9991
+ 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
9992
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
9662
9993
  const credentials = requireAuth();
9663
9994
  const client = new ApiClient(credentials);
@@ -9711,12 +10042,12 @@ var callCommand = new Command10("call").description("Make a proxy call to a conn
9711
10042
  }
9712
10043
  try {
9713
10044
  const integrations = await client.listConnectedIntegrations(workspaceId);
9714
- const match = integrations.find((i) => (i.canonicalId || i.integrationId) === integration || i.provider === integration);
9715
- if (!match) {
10045
+ const resolved = resolveIntegrationConnection(integrations, integration);
10046
+ if (!resolved) {
9716
10047
  console.error(`Integration "${integration}" not found. Run "runwork integrations list" to see connected integrations.`);
9717
10048
  process.exit(1);
9718
10049
  }
9719
- const result = await client.callIntegrationProxy(match.integrationId, finalMethod, finalPath, { body, headers: Object.keys(headers).length > 0 ? headers : undefined, query });
10050
+ const result = await client.callIntegrationProxy(resolved.connectionId, finalMethod, finalPath, { body, headers: Object.keys(headers).length > 0 ? headers : undefined, query });
9720
10051
  if (useJson) {
9721
10052
  jsonOut(result);
9722
10053
  return;
@@ -10345,7 +10676,7 @@ function truncate(text2, max) {
10345
10676
  return first;
10346
10677
  return first.slice(0, max - 3) + "...";
10347
10678
  }
10348
- var listCommand2 = new Command13("list").description("List workspace skills (app, external, MCP-generated)").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
10679
+ 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
10680
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
10350
10681
  const credentials = requireAuth();
10351
10682
  const client = new ApiClient(credentials);
@@ -10402,7 +10733,7 @@ async function readStdin2() {
10402
10733
  }
10403
10734
  return Buffer.concat(chunks).toString("utf-8");
10404
10735
  }
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) => {
10736
+ 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
10737
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
10407
10738
  const credentials = requireAuth();
10408
10739
  const client = new ApiClient(credentials);
@@ -10497,7 +10828,7 @@ var pushCommand = new Command13("push").description("Upload a local skill file t
10497
10828
  process.exit(1);
10498
10829
  }
10499
10830
  });
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) => {
10831
+ 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
10832
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
10502
10833
  const credentials = requireAuth();
10503
10834
  const client = new ApiClient(credentials);
@@ -10600,7 +10931,7 @@ var searchCommand2 = new Command13("search").description("Search community skill
10600
10931
  process.exit(1);
10601
10932
  }
10602
10933
  });
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) => {
10934
+ 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
10935
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
10605
10936
  const credentials = requireAuth();
10606
10937
  const client = new ApiClient(credentials);
@@ -11115,6 +11446,22 @@ function formatCombinedDigest(sessions, opts = { days: 7 }) {
11115
11446
  `);
11116
11447
  }
11117
11448
 
11449
+ // src/agents/utils/skill-name.ts
11450
+ function canonicalSkillName(raw) {
11451
+ const afterPrefix = raw.includes(":") ? raw.slice(raw.lastIndexOf(":") + 1) : raw;
11452
+ return afterPrefix.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
11453
+ }
11454
+ function skillNameFromPath(path2) {
11455
+ const trimmed = path2.trim().replace(/\/SKILL\.md$/i, "");
11456
+ if (!trimmed.includes("/") || /[<>]/.test(trimmed))
11457
+ return null;
11458
+ const segment = trimmed.replace(/\/+$/, "").split("/").pop();
11459
+ if (!segment)
11460
+ return null;
11461
+ const canonical = canonicalSkillName(segment);
11462
+ return canonical || null;
11463
+ }
11464
+
11118
11465
  // src/agents/utils/json-config.ts
11119
11466
  import { readFileSync as readFileSync22, writeFileSync as writeFileSync14, mkdirSync as mkdirSync13, existsSync as existsSync25 } from "fs";
11120
11467
  import { dirname as dirname5 } from "path";
@@ -11838,6 +12185,7 @@ ${instructions}`;
11838
12185
  let outputTokens = 0;
11839
12186
  let cacheReadTokens = 0;
11840
12187
  let latestActivityMs = 0;
12188
+ const activeDays = new Set;
11841
12189
  const seenEntries = new Set;
11842
12190
  let cwdEntries;
11843
12191
  try {
@@ -11891,6 +12239,7 @@ ${instructions}`;
11891
12239
  sessionHadActivity = true;
11892
12240
  if (ts > latestActivityMs)
11893
12241
  latestActivityMs = ts;
12242
+ activeDays.add(new Date(ts).toISOString().slice(0, 10));
11894
12243
  const type = entry.type;
11895
12244
  if (type === "user" || type === "assistant") {
11896
12245
  const msg = entry.message;
@@ -11943,6 +12292,7 @@ ${instructions}`;
11943
12292
  aiLinesAdded: 0,
11944
12293
  aiLinesRemoved: 0,
11945
12294
  lastActiveAt: latestActivityMs > 0 ? new Date(latestActivityMs).toISOString() : null,
12295
+ activeDays: [...activeDays].sort(),
11946
12296
  extra: { inputTokens, outputTokens, cacheReadTokens }
11947
12297
  };
11948
12298
  } catch {
@@ -12047,7 +12397,7 @@ ${instructions}`;
12047
12397
  } catch {
12048
12398
  continue;
12049
12399
  }
12050
- let pendingCommand = null;
12400
+ const pendingToolUses = [];
12051
12401
  for (const line of content.split(`
12052
12402
  `)) {
12053
12403
  if (!line)
@@ -12065,39 +12415,48 @@ ${instructions}`;
12065
12415
  if (!Number.isFinite(ts) || ts <= sinceMs)
12066
12416
  continue;
12067
12417
  const type = entry.type;
12068
- if (type === "assistant") {
12069
- const msg = entry.message;
12070
- if (msg && typeof msg === "object") {
12071
- const blocks = Array.isArray(msg.content) ? msg.content : [];
12072
- for (const block of blocks) {
12073
- if (block && block.type === "tool_use" && block.name === "Skill") {
12074
- const input = block.input;
12075
- if (input && typeof input.skill === "string") {
12076
- recordSkill(input.skill, ts);
12077
- }
12418
+ const msg = entry.message;
12419
+ if (type === "assistant" && msg && typeof msg === "object") {
12420
+ const blocks = Array.isArray(msg.content) ? msg.content : [];
12421
+ for (const block of blocks) {
12422
+ if (block && block.type === "tool_use" && block.name === "Skill") {
12423
+ const input = block.input;
12424
+ if (input && typeof input.skill === "string") {
12425
+ const name = canonicalSkillName(input.skill);
12426
+ if (name)
12427
+ pendingToolUses.push({ name, ts });
12078
12428
  }
12079
12429
  }
12080
12430
  }
12081
12431
  }
12082
- if (type === "user") {
12083
- const msg = entry.message;
12084
- const msgContent = msg?.content;
12085
- const text2 = typeof msgContent === "string" ? msgContent : "";
12086
- if (pendingCommand && text2.startsWith("Base directory for this skill:")) {
12087
- recordSkill(pendingCommand.name, pendingCommand.ts);
12088
- pendingCommand = null;
12089
- continue;
12090
- }
12091
- if (pendingCommand && type === "user") {
12092
- pendingCommand = null;
12432
+ if (type === "user" && msg && typeof msg === "object") {
12433
+ const texts = [];
12434
+ if (typeof msg.content === "string") {
12435
+ texts.push(msg.content);
12436
+ } else if (Array.isArray(msg.content)) {
12437
+ for (const block of msg.content) {
12438
+ if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
12439
+ texts.push(block.text);
12440
+ }
12441
+ }
12093
12442
  }
12094
- const match = text2.match(/<command-name>\/?([^<]+)<\/command-name>/);
12095
- if (match) {
12096
- const cmdName = match[1].replace(/^\//, "");
12097
- pendingCommand = { name: cmdName, ts };
12443
+ for (const text2 of texts) {
12444
+ const m = text2.match(/^Base directory for this skill: (.+)$/m);
12445
+ if (!m)
12446
+ continue;
12447
+ const name = skillNameFromPath(m[1]);
12448
+ if (!name)
12449
+ continue;
12450
+ const pendingIdx = pendingToolUses.findIndex((p) => p.name === name);
12451
+ if (pendingIdx >= 0)
12452
+ pendingToolUses.splice(pendingIdx, 1);
12453
+ recordSkill(name, ts);
12098
12454
  }
12099
12455
  }
12100
12456
  }
12457
+ for (const pending of pendingToolUses) {
12458
+ recordSkill(pending.name, pending.ts);
12459
+ }
12101
12460
  }
12102
12461
  }
12103
12462
  if (skillCounts.size === 0)
@@ -12671,11 +13030,14 @@ class ClaudeDesktopAdapter {
12671
13030
  const modelsUsed = new Set;
12672
13031
  let maxMcpTools = 0;
12673
13032
  const agentSessionsDir = join23(claudeAppDir, "local-agent-mode-sessions");
13033
+ const activeDays = new Set;
12674
13034
  if (existsSync29(agentSessionsDir)) {
12675
13035
  this.walkSessionDirs(agentSessionsDir, sinceMs, (session) => {
12676
13036
  const sessionTime = session.lastActivityAt ?? session.createdAt ?? 0;
12677
- if (sessionTime > sinceMs)
13037
+ if (sessionTime > sinceMs) {
12678
13038
  sessionCount++;
13039
+ activeDays.add(new Date(sessionTime).toISOString().slice(0, 10));
13040
+ }
12679
13041
  if (sessionTime > latestActivity)
12680
13042
  latestActivity = sessionTime;
12681
13043
  if (session.model)
@@ -12691,8 +13053,10 @@ class ClaudeDesktopAdapter {
12691
13053
  if (existsSync29(codeSessionsDir)) {
12692
13054
  this.walkSessionDirs(codeSessionsDir, sinceMs, (session) => {
12693
13055
  const sessionTime = session.lastActivityAt ?? session.createdAt ?? 0;
12694
- if (sessionTime > sinceMs)
13056
+ if (sessionTime > sinceMs) {
12695
13057
  sessionCount++;
13058
+ activeDays.add(new Date(sessionTime).toISOString().slice(0, 10));
13059
+ }
12696
13060
  if (sessionTime > latestActivity)
12697
13061
  latestActivity = sessionTime;
12698
13062
  if (session.model)
@@ -12725,6 +13089,7 @@ class ClaudeDesktopAdapter {
12725
13089
  aiLinesAdded: 0,
12726
13090
  aiLinesRemoved: 0,
12727
13091
  lastActiveAt: latestActivity > 0 ? new Date(latestActivity).toISOString() : null,
13092
+ activeDays: activeDays.size > 0 ? [...activeDays].sort() : undefined,
12728
13093
  modelsUsed: Array.from(modelsUsed),
12729
13094
  mcpToolCount: maxMcpTools > 0 ? maxMcpTools : undefined,
12730
13095
  extra: scheduledTaskRuns > 0 ? { scheduledTaskRuns } : undefined
@@ -12733,46 +13098,6 @@ class ClaudeDesktopAdapter {
12733
13098
  return null;
12734
13099
  }
12735
13100
  }
12736
- async readSessionDigests(sinceISO) {
12737
- try {
12738
- const os2 = platform3();
12739
- const claudeAppDir = os2 === "darwin" ? join23(homedir6(), "Library", "Application Support", "Claude") : os2 === "win32" ? join23(process.env.APPDATA || join23(homedir6(), "AppData", "Roaming"), "Claude") : join23(homedir6(), ".config", "Claude");
12740
- const sessionsDir = join23(claudeAppDir, "claude-code-sessions");
12741
- if (!existsSync29(sessionsDir))
12742
- return null;
12743
- const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
12744
- let files;
12745
- try {
12746
- files = readdirSync6(sessionsDir).filter((f) => f.endsWith(".jsonl"));
12747
- } catch {
12748
- return null;
12749
- }
12750
- const digests = [];
12751
- for (const file of files) {
12752
- const filePath = join23(sessionsDir, file);
12753
- let stat;
12754
- try {
12755
- stat = statSync4(filePath);
12756
- } catch {
12757
- continue;
12758
- }
12759
- if (stat.mtimeMs <= sinceMs)
12760
- continue;
12761
- let content;
12762
- try {
12763
- content = readFileSync25(filePath, "utf-8");
12764
- } catch {
12765
- continue;
12766
- }
12767
- const digest = extractClaudeJsonlSession(content, this.slug, file.replace(/\.jsonl$/, ""));
12768
- if (digest)
12769
- digests.push(digest);
12770
- }
12771
- return digests;
12772
- } catch {
12773
- return null;
12774
- }
12775
- }
12776
13101
  async readVersion() {
12777
13102
  try {
12778
13103
  const os2 = platform3();
@@ -12793,53 +13118,6 @@ class ClaudeDesktopAdapter {
12793
13118
  return null;
12794
13119
  }
12795
13120
  }
12796
- async readSkillUsage(lastSyncAt) {
12797
- try {
12798
- const baseDir = getCoworkBaseDir();
12799
- if (!existsSync29(baseDir))
12800
- return null;
12801
- const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
12802
- const skillCounts = new Map;
12803
- const agentSessionsDir = baseDir;
12804
- this.walkSessionDirs(agentSessionsDir, sinceMs, (session) => {
12805
- const sessionTime = session.lastActivityAt ?? session.createdAt ?? 0;
12806
- if (sessionTime <= sinceMs)
12807
- return;
12808
- const mcpTools = session.enabledMcpTools;
12809
- if (!mcpTools || typeof mcpTools !== "object")
12810
- return;
12811
- for (const toolName of Object.keys(mcpTools)) {
12812
- const skillMatch = toolName.match(/skill_([A-Za-z0-9_]+)/);
12813
- if (!skillMatch)
12814
- continue;
12815
- const rawName = skillMatch[1].replace(/_/g, " ").trim();
12816
- if (!rawName)
12817
- continue;
12818
- const existing = skillCounts.get(rawName);
12819
- if (existing) {
12820
- existing.count++;
12821
- if (sessionTime > existing.lastTs)
12822
- existing.lastTs = sessionTime;
12823
- } else {
12824
- skillCounts.set(rawName, { count: 1, lastTs: sessionTime });
12825
- }
12826
- }
12827
- });
12828
- if (skillCounts.size === 0)
12829
- return null;
12830
- const results = [];
12831
- for (const [skillName, { count, lastTs }] of skillCounts) {
12832
- results.push({
12833
- skillName,
12834
- count,
12835
- lastUsedAt: new Date(lastTs).toISOString()
12836
- });
12837
- }
12838
- return results;
12839
- } catch {
12840
- return null;
12841
- }
12842
- }
12843
13121
  walkSessionDirs(baseDir, _sinceMs, onSession) {
12844
13122
  try {
12845
13123
  for (const orgDir of readdirSync6(baseDir)) {
@@ -13120,14 +13398,18 @@ ${instructions}`;
13120
13398
  try {
13121
13399
  const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
13122
13400
  const globalDbPath = this.globalStorageDbPath();
13123
- let sessionCount = 0;
13401
+ let newComposersWithoutId = 0;
13402
+ const activeComposerIds = new Set;
13403
+ const activeDays = new Set;
13404
+ let messageCount = 0;
13405
+ let toolCallCount = 0;
13124
13406
  let totalLinesAdded = 0;
13125
13407
  let totalLinesRemoved = 0;
13126
13408
  let latestActivity = 0;
13127
13409
  let agenticSessions = 0;
13128
13410
  let chatSessions = 0;
13129
13411
  if (existsSync30(globalDbPath)) {
13130
- const composerResult = queryReadonlySqlite(globalDbPath, `SELECT value FROM cursorDiskKV WHERE key LIKE 'composerData-%'`);
13412
+ const composerResult = queryReadonlySqlite(globalDbPath, `SELECT value FROM cursorDiskKV WHERE key LIKE 'composerData:%'`);
13131
13413
  if (composerResult) {
13132
13414
  for (const line of composerResult.split(`
13133
13415
  `)) {
@@ -13137,7 +13419,11 @@ ${instructions}`;
13137
13419
  const data = JSON.parse(line);
13138
13420
  const createdAt = data.createdAt ?? 0;
13139
13421
  if (createdAt > sinceMs) {
13140
- sessionCount++;
13422
+ if (typeof data.composerId === "string")
13423
+ activeComposerIds.add(data.composerId);
13424
+ else
13425
+ newComposersWithoutId++;
13426
+ activeDays.add(new Date(createdAt).toISOString().slice(0, 10));
13141
13427
  totalLinesAdded += data.totalLinesAdded ?? 0;
13142
13428
  totalLinesRemoved += data.totalLinesRemoved ?? 0;
13143
13429
  if (data.unifiedMode === "agent" || data.isAgentic)
@@ -13152,7 +13438,34 @@ ${instructions}`;
13152
13438
  }
13153
13439
  }
13154
13440
  }
13441
+ const bubbleResult = queryReadonlySqlite(globalDbPath, `SELECT json_object('k', key, 'type', json_extract(value,'$.type'), 'ts', json_extract(value,'$.createdAt'), 'tool', json_extract(value,'$.toolFormerData.name')) FROM cursorDiskKV WHERE key LIKE 'bubbleId:%'`);
13442
+ if (bubbleResult) {
13443
+ for (const line of bubbleResult.split(`
13444
+ `)) {
13445
+ if (!line.trim())
13446
+ continue;
13447
+ try {
13448
+ const b = JSON.parse(line);
13449
+ const ts = typeof b.ts === "string" ? Date.parse(b.ts) : NaN;
13450
+ if (!Number.isFinite(ts) || ts <= sinceMs)
13451
+ continue;
13452
+ if (ts > latestActivity)
13453
+ latestActivity = ts;
13454
+ activeDays.add(new Date(ts).toISOString().slice(0, 10));
13455
+ const parts = (b.k ?? "").split(":");
13456
+ if (parts.length >= 3)
13457
+ activeComposerIds.add(parts[1]);
13458
+ if (b.tool)
13459
+ toolCallCount++;
13460
+ else if (b.type === 1 || b.type === 2)
13461
+ messageCount++;
13462
+ } catch {
13463
+ continue;
13464
+ }
13465
+ }
13466
+ }
13155
13467
  }
13468
+ const sessionCount = newComposersWithoutId + activeComposerIds.size;
13156
13469
  const trackingDbPath = join24(homedir7(), ".cursor", "ai-tracking", "ai-code-tracking.db");
13157
13470
  let aiCommitCount = 0;
13158
13471
  let avgAiPercent = 0;
@@ -13165,17 +13478,19 @@ ${instructions}`;
13165
13478
  avgAiPercent = parseFloat(parts[1]) || 0;
13166
13479
  }
13167
13480
  }
13168
- if (sessionCount === 0 && latestActivity <= sinceMs)
13481
+ const hasNewActivity = sessionCount > 0 || messageCount > 0;
13482
+ if (!hasNewActivity && latestActivity <= sinceMs)
13169
13483
  return null;
13170
13484
  return {
13171
- hasNewActivity: sessionCount > 0,
13485
+ hasNewActivity,
13172
13486
  sessionCount,
13173
- messageCount: 0,
13174
- toolCallCount: 0,
13487
+ messageCount,
13488
+ toolCallCount,
13175
13489
  tokensUsed: 0,
13176
13490
  aiLinesAdded: totalLinesAdded,
13177
13491
  aiLinesRemoved: totalLinesRemoved,
13178
13492
  lastActiveAt: latestActivity > 0 ? new Date(latestActivity).toISOString() : null,
13493
+ activeDays: activeDays.size > 0 ? [...activeDays].sort() : undefined,
13179
13494
  extra: {
13180
13495
  agenticSessions,
13181
13496
  chatSessions,
@@ -13410,6 +13725,8 @@ var AGENT_REGISTRY = [
13410
13725
  },
13411
13726
  manualSetup: {
13412
13727
  title: "Install Runwork plugin",
13728
+ action: "Install Runwork plugin",
13729
+ preposition: "for",
13413
13730
  showAfter: "connecting",
13414
13731
  downloadArtifact: {
13415
13732
  label: "Download plugin",
@@ -13417,7 +13734,7 @@ var AGENT_REGISTRY = [
13417
13734
  command: "build-plugin"
13418
13735
  },
13419
13736
  steps: [
13420
- { id: "download", label: "Click Download plugin above to save runwork-plugin.zip" },
13737
+ { id: "download", label: "Download the plugin. It saves runwork-plugin.zip to your Downloads folder.", control: "download-artifact" },
13421
13738
  { id: "open", label: "Open Claude Desktop and sign in if you haven't already" },
13422
13739
  { id: "cowork", label: "Go to the Cowork tab (top of the window)" },
13423
13740
  { id: "customize", label: "Click Customize in the sidebar" },
@@ -13517,14 +13834,15 @@ var AGENT_REGISTRY = [
13517
13834
  method: "any",
13518
13835
  target: [
13519
13836
  { method: "path", target: { macos: "/Applications/Codex.app" } },
13520
- { method: "windows-appx", target: "OpenAI.Codex" }
13837
+ { method: "macos-bundle-id", target: "com.openai.codex" },
13838
+ { method: "windows-appx", target: ["OpenAI.Codex"] }
13521
13839
  ]
13522
13840
  },
13523
- launch: { app: { macos: "Codex", windows: "Codex" } },
13841
+ launch: { app: { macos: "Codex", windows: "Codex" }, bundleId: { macos: "com.openai.codex" } },
13524
13842
  logo: "codex",
13525
13843
  downloadUrl: "https://openai.com/codex/",
13526
13844
  skillsPaths: { global: ".codex/skills", project: ".agents/skills" },
13527
- instructionFile: { global: ".codex/instructions.md", project: "AGENTS.md" },
13845
+ instructionFile: { global: ".codex/AGENTS.md", project: "AGENTS.md" },
13528
13846
  firstClass: true,
13529
13847
  resumeCapability: {
13530
13848
  mode: "file-drop-only",
@@ -13549,7 +13867,7 @@ var AGENT_REGISTRY = [
13549
13867
  logo: "codex",
13550
13868
  downloadUrl: "https://github.com/openai/codex",
13551
13869
  skillsPaths: { global: ".codex/skills", project: ".agents/skills" },
13552
- instructionFile: { global: ".codex/instructions.md", project: "AGENTS.md" },
13870
+ instructionFile: { global: ".codex/AGENTS.md", project: "AGENTS.md" },
13553
13871
  firstClass: true,
13554
13872
  resumeCapability: {
13555
13873
  mode: "cli-resume",
@@ -13590,6 +13908,8 @@ var AGENT_REGISTRY = [
13590
13908
  },
13591
13909
  manualSetup: {
13592
13910
  title: "Connect Runwork in ChatGPT",
13911
+ action: "Create Runwork connector",
13912
+ preposition: "in",
13593
13913
  showAfter: "connecting",
13594
13914
  copyValue: { label: "Workspace MCP URL", token: "{mcpUrl}" },
13595
13915
  steps: [
@@ -13597,7 +13917,7 @@ var AGENT_REGISTRY = [
13597
13917
  { id: "settings-apps", label: "Open the profile menu (bottom-left), then Settings, then Apps" },
13598
13918
  { id: "dev-mode", label: 'Click "Advanced settings" and turn on "Developer mode" (skip if it is already on)' },
13599
13919
  { id: "create-app", label: 'Go back to Apps and click "Create app"' },
13600
- { id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL", and paste the Workspace MCP URL above into the Server URL field' },
13920
+ { id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL", then paste this Workspace MCP URL into the Server URL field:', control: "copy-value" },
13601
13921
  { id: "oauth", label: 'Leave Authentication set to "OAuth", check "I understand and want to continue", then click Create' },
13602
13922
  { id: "authorize", label: "Complete the OAuth sign-in to authorize Runwork for your account" },
13603
13923
  { id: "confirm", label: 'In a new chat, ask "Do you have access to Runwork tools?" to confirm (or check Settings, then Apps, for Runwork)' }
@@ -13607,6 +13927,8 @@ var AGENT_REGISTRY = [
13607
13927
  key: "team",
13608
13928
  label: "Team / Enterprise",
13609
13929
  title: "Connect the Runwork app in ChatGPT",
13930
+ action: "Connect the Runwork app",
13931
+ preposition: "in",
13610
13932
  steps: [
13611
13933
  { id: "open", label: "Open chatgpt.com and sign in" },
13612
13934
  { id: "settings-apps", label: "Open the profile menu (bottom-left), then Settings, then Apps" },
@@ -13619,12 +13941,14 @@ var AGENT_REGISTRY = [
13619
13941
  key: "personal",
13620
13942
  label: "Personal",
13621
13943
  title: "Create the Runwork connector in ChatGPT",
13944
+ action: "Create Runwork connector",
13945
+ preposition: "in",
13622
13946
  steps: [
13623
13947
  { id: "open", label: "Open chatgpt.com and sign in" },
13624
13948
  { id: "settings-apps", label: "Open the profile menu (bottom-left), then Settings, then Apps" },
13625
13949
  { id: "dev-mode", label: 'Click "Advanced settings" and turn on "Developer mode" (skip if it is already on)' },
13626
13950
  { id: "create-app", label: 'Go back to Apps and click "Create app"' },
13627
- { id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL", and paste the Workspace MCP URL above into the Server URL field' },
13951
+ { id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL", then paste this Workspace MCP URL into the Server URL field:', control: "copy-value" },
13628
13952
  { id: "oauth", label: 'Leave Authentication set to "OAuth", check "I understand and want to continue", then click Create' },
13629
13953
  { id: "authorize", label: "Complete the OAuth sign-in to authorize Runwork for your account" },
13630
13954
  { id: "confirm", label: 'In a new chat, ask "Do you have access to Runwork tools?" to confirm (or check Settings, then Apps, for Runwork)' }
@@ -13643,10 +13967,11 @@ var AGENT_REGISTRY = [
13643
13967
  method: "any",
13644
13968
  target: [
13645
13969
  { method: "path", target: { macos: "/Applications/ChatGPT.app" } },
13646
- { method: "windows-appx", target: "OpenAI.ChatGPT" }
13970
+ { method: "macos-bundle-id", target: "com.openai.chat" },
13971
+ { method: "windows-appx", target: ["OpenAI.ChatGPT"] }
13647
13972
  ]
13648
13973
  },
13649
- launch: { app: { macos: "ChatGPT", windows: "ChatGPT" }, url: "https://chatgpt.com/?prompt={prompt}" },
13974
+ launch: { app: { macos: "ChatGPT", windows: "ChatGPT" }, bundleId: { macos: "com.openai.chat" }, url: "https://chatgpt.com/?prompt={prompt}" },
13650
13975
  logo: "openai",
13651
13976
  downloadUrl: "https://chatgpt.com/download",
13652
13977
  firstClass: true,
@@ -13663,6 +13988,8 @@ var AGENT_REGISTRY = [
13663
13988
  },
13664
13989
  manualSetup: {
13665
13990
  title: "Connect Runwork in ChatGPT",
13991
+ action: "Create Runwork connector",
13992
+ preposition: "in",
13666
13993
  showAfter: "connecting",
13667
13994
  copyValue: { label: "Workspace MCP URL", token: "{mcpUrl}" },
13668
13995
  steps: [
@@ -13671,7 +13998,7 @@ var AGENT_REGISTRY = [
13671
13998
  { id: "settings-apps", label: "Open the profile menu (bottom-left), then Settings, then Apps" },
13672
13999
  { id: "dev-mode", label: 'Click "Advanced settings" and turn on "Developer mode" (skip if it is already on)' },
13673
14000
  { id: "create-app", label: 'Go back to Apps and click "Create app"' },
13674
- { id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL", and paste the Workspace MCP URL above into the Server URL field' },
14001
+ { id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL", then paste this Workspace MCP URL into the Server URL field:', control: "copy-value" },
13675
14002
  { id: "oauth", label: 'Leave Authentication set to "OAuth", check "I understand and want to continue", then click Create' },
13676
14003
  { id: "authorize", label: "Complete the OAuth sign-in to authorize Runwork for your account" },
13677
14004
  { id: "confirm", label: 'Open the ChatGPT desktop app and, in a new chat, ask "Do you have access to Runwork tools?" to confirm' }
@@ -13681,6 +14008,8 @@ var AGENT_REGISTRY = [
13681
14008
  key: "team",
13682
14009
  label: "Team / Enterprise",
13683
14010
  title: "Connect the Runwork app in ChatGPT",
14011
+ action: "Connect the Runwork app",
14012
+ preposition: "in",
13684
14013
  steps: [
13685
14014
  { id: "install", label: "Install the ChatGPT desktop app from chatgpt.com/download if you have not already" },
13686
14015
  { id: "open", label: "Open the ChatGPT desktop app and sign in" },
@@ -13694,13 +14023,15 @@ var AGENT_REGISTRY = [
13694
14023
  key: "personal",
13695
14024
  label: "Personal",
13696
14025
  title: "Create the Runwork connector in ChatGPT",
14026
+ action: "Create Runwork connector",
14027
+ preposition: "in",
13697
14028
  steps: [
13698
14029
  { id: "install", label: "Install the ChatGPT desktop app from chatgpt.com/download if you have not already" },
13699
14030
  { id: "open-web", label: "Connectors are created on chatgpt.com: open it in your browser and sign in" },
13700
14031
  { id: "settings-apps", label: "Open the profile menu (bottom-left), then Settings, then Apps" },
13701
14032
  { id: "dev-mode", label: 'Click "Advanced settings" and turn on "Developer mode" (skip if it is already on)' },
13702
14033
  { id: "create-app", label: 'Go back to Apps and click "Create app"' },
13703
- { id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL", and paste the Workspace MCP URL above into the Server URL field' },
14034
+ { id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL", then paste this Workspace MCP URL into the Server URL field:', control: "copy-value" },
13704
14035
  { id: "oauth", label: 'Leave Authentication set to "OAuth", check "I understand and want to continue", then click Create' },
13705
14036
  { id: "authorize", label: "Complete the OAuth sign-in to authorize Runwork for your account" },
13706
14037
  { id: "confirm", label: 'Open the ChatGPT desktop app and, in a new chat, ask "Do you have access to Runwork tools?" to confirm' }
@@ -13936,14 +14267,28 @@ function resolveToAbsolute(ps, scope) {
13936
14267
  return scope === "global" ? join26(homedir9(), resolved) : join26(process.cwd(), resolved);
13937
14268
  }
13938
14269
 
14270
+ // src/agents/detection-probes.ts
14271
+ function powershellQuote(value) {
14272
+ return `'${value.replace(/'/g, "''")}'`;
14273
+ }
14274
+ function isValidBundleId(id) {
14275
+ return /^[A-Za-z0-9][A-Za-z0-9.-]*$/.test(id);
14276
+ }
14277
+ function macosBundleIdProbeScript(id) {
14278
+ 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`;
14279
+ }
14280
+ function appxPackageProbeScript(pkg) {
14281
+ return `$p = Get-AppxPackage -Name ${powershellQuote(pkg)} -ErrorAction SilentlyContinue; if ($null -ne $p) { exit 0 } exit 1`;
14282
+ }
14283
+ function startAppProbeScript(pattern) {
14284
+ return `$a = Get-StartApps -Name ${powershellQuote(pattern)} -ErrorAction SilentlyContinue | Select-Object -First 1; if ($null -ne $a) { exit 0 } exit 1`;
14285
+ }
14286
+
13939
14287
  // src/agents/detection.ts
13940
14288
  var execFileAsync = promisify(execFile);
13941
14289
  function isWindows() {
13942
14290
  return platform7() === "win32";
13943
14291
  }
13944
- function powershellQuote(value) {
13945
- return `'${value.replace(/'/g, "''")}'`;
13946
- }
13947
14292
  function toList(value) {
13948
14293
  return Array.isArray(value) ? value : [value];
13949
14294
  }
@@ -13963,23 +14308,30 @@ function checkPath(target) {
13963
14308
  return existsSync32(resolved);
13964
14309
  return existsSync32(join27(homedir10(), resolved));
13965
14310
  }
14311
+ async function checkMacosBundleId(target) {
14312
+ if (platform7() !== "darwin")
14313
+ return false;
14314
+ for (const id of toList(target)) {
14315
+ if (!isValidBundleId(id))
14316
+ continue;
14317
+ try {
14318
+ await execFileAsync("sh", ["-c", macosBundleIdProbeScript(id)]);
14319
+ return true;
14320
+ } catch {}
14321
+ }
14322
+ return false;
14323
+ }
13966
14324
  async function checkWindowsAppxPackage(target) {
13967
14325
  if (!isWindows())
13968
14326
  return false;
13969
- const probes = toList(target).map((pkg) => {
13970
- const script = `$p = Get-AppxPackage -Name ${powershellQuote(pkg)} -ErrorAction SilentlyContinue; if ($null -ne $p) { exit 0 } exit 1`;
13971
- return runPowerShell(script);
13972
- });
14327
+ const probes = toList(target).map((pkg) => runPowerShell(appxPackageProbeScript(pkg)));
13973
14328
  const results = await Promise.all(probes);
13974
14329
  return results.some(Boolean);
13975
14330
  }
13976
14331
  async function checkWindowsStartApp(target) {
13977
14332
  if (!isWindows())
13978
14333
  return false;
13979
- const probes = toList(target).map((pattern) => {
13980
- const script = `$a = Get-StartApps -Name ${powershellQuote(pattern)} -ErrorAction SilentlyContinue | Select-Object -First 1; if ($null -ne $a) { exit 0 } exit 1`;
13981
- return runPowerShell(script);
13982
- });
14334
+ const probes = toList(target).map((pattern) => runPowerShell(startAppProbeScript(pattern)));
13983
14335
  const results = await Promise.all(probes);
13984
14336
  return results.some(Boolean);
13985
14337
  }
@@ -13995,6 +14347,8 @@ async function runAgentDetection(detection) {
13995
14347
  return checkWindowsAppxPackage(detection.target);
13996
14348
  case "windows-start-app":
13997
14349
  return checkWindowsStartApp(detection.target);
14350
+ case "macos-bundle-id":
14351
+ return checkMacosBundleId(detection.target);
13998
14352
  case "any": {
13999
14353
  const probes = await Promise.all(detection.target.map((p) => runAgentDetection(p)));
14000
14354
  return probes.some(Boolean);
@@ -14106,6 +14460,26 @@ class CodexAdapter {
14106
14460
  }
14107
14461
  }
14108
14462
  }
14463
+ if (config.networkAllowlist?.length) {
14464
+ const features = parsed.features && typeof parsed.features === "object" ? parsed.features : undefined;
14465
+ const proxy = features?.network_proxy;
14466
+ if (proxy && proxy.enabled === true) {
14467
+ if (!proxy.domains || typeof proxy.domains !== "object")
14468
+ proxy.domains = {};
14469
+ const domains = proxy.domains;
14470
+ for (const host of config.networkAllowlist) {
14471
+ if (!(host in domains))
14472
+ domains[host] = "allow";
14473
+ }
14474
+ } else {
14475
+ if (!parsed.sandbox_workspace_write || typeof parsed.sandbox_workspace_write !== "object") {
14476
+ parsed.sandbox_workspace_write = {};
14477
+ }
14478
+ const sww = parsed.sandbox_workspace_write;
14479
+ if (sww.network_access === undefined)
14480
+ sww.network_access = true;
14481
+ }
14482
+ }
14109
14483
  mkdirSync20(join28(configPath, ".."), { recursive: true });
14110
14484
  writeFileSync21(configPath, stringify(parsed));
14111
14485
  }
@@ -14147,50 +14521,162 @@ class CodexAdapter {
14147
14521
  return null;
14148
14522
  const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
14149
14523
  const sinceSec = Math.floor(sinceMs / 1000);
14524
+ const rollout = this.scanRolloutActivity(sinceMs);
14525
+ let sessionCount = rollout.sessionCount;
14526
+ let tokensUsed = rollout.tokensUsed;
14527
+ let latestMs = rollout.latestMs;
14528
+ let versions = [];
14150
14529
  const dbPath = join28(codexDir, "state_5.sqlite");
14151
- if (!existsSync33(dbPath))
14152
- return null;
14153
- const countResult = queryReadonlySqlite(dbPath, `SELECT count(*) FROM threads WHERE updated_at > ${sinceSec}`);
14154
- const sessionCount = parseInt(countResult) || 0;
14155
- const tokensResult = queryReadonlySqlite(dbPath, `SELECT coalesce(sum(tokens_used), 0) FROM threads WHERE updated_at > ${sinceSec}`);
14156
- const tokensUsed = parseInt(tokensResult) || 0;
14157
- const latestResult = queryReadonlySqlite(dbPath, `SELECT max(updated_at) FROM threads`);
14158
- const latestSec = parseInt(latestResult) || 0;
14159
- const versionsResult = queryReadonlySqlite(dbPath, `SELECT DISTINCT cli_version FROM threads WHERE cli_version IS NOT NULL ORDER BY created_at DESC LIMIT 3`);
14160
- const versions = versionsResult ? versionsResult.split(/[\r\n]+/).filter(Boolean) : [];
14161
- let messageCount = 0;
14162
- const historyPath = join28(codexDir, "history.jsonl");
14163
- if (existsSync33(historyPath)) {
14164
- const content = readFileSync26(historyPath, "utf-8").trim();
14165
- if (content) {
14166
- for (const line of content.split(/[\r\n]+/)) {
14167
- try {
14168
- const entry = JSON.parse(line);
14169
- if (entry.ts && entry.ts > sinceSec)
14170
- messageCount++;
14171
- } catch {
14172
- continue;
14530
+ if (existsSync33(dbPath)) {
14531
+ const countResult = queryReadonlySqlite(dbPath, `SELECT count(*) FROM threads WHERE updated_at > ${sinceSec}`);
14532
+ const dbSessionCount = parseInt(countResult) || 0;
14533
+ const tokensResult = queryReadonlySqlite(dbPath, `SELECT coalesce(sum(tokens_used), 0) FROM threads WHERE updated_at > ${sinceSec}`);
14534
+ const dbTokens = parseInt(tokensResult) || 0;
14535
+ const latestResult = queryReadonlySqlite(dbPath, `SELECT max(updated_at) FROM threads`);
14536
+ const latestSec = parseInt(latestResult) || 0;
14537
+ const versionsResult = queryReadonlySqlite(dbPath, `SELECT DISTINCT cli_version FROM threads WHERE cli_version IS NOT NULL ORDER BY created_at DESC LIMIT 3`);
14538
+ versions = versionsResult ? versionsResult.split(/[\r\n]+/).filter(Boolean) : [];
14539
+ sessionCount = Math.max(dbSessionCount, rollout.sessionCount);
14540
+ if (dbTokens > 0)
14541
+ tokensUsed = dbTokens;
14542
+ if (latestSec * 1000 > latestMs)
14543
+ latestMs = latestSec * 1000;
14544
+ }
14545
+ let messageCount = rollout.messageCount;
14546
+ if (messageCount === 0) {
14547
+ const historyPath = join28(codexDir, "history.jsonl");
14548
+ if (existsSync33(historyPath)) {
14549
+ const content = readFileSync26(historyPath, "utf-8").trim();
14550
+ if (content) {
14551
+ for (const line of content.split(/[\r\n]+/)) {
14552
+ try {
14553
+ const entry = JSON.parse(line);
14554
+ if (entry.ts && entry.ts > sinceSec)
14555
+ messageCount++;
14556
+ } catch {
14557
+ continue;
14558
+ }
14173
14559
  }
14174
14560
  }
14175
14561
  }
14176
14562
  }
14177
- if (sessionCount === 0 && latestSec * 1000 <= sinceMs)
14563
+ const hasNewActivity = sessionCount > 0 || messageCount > 0;
14564
+ if (!hasNewActivity && latestMs <= sinceMs)
14178
14565
  return null;
14179
14566
  return {
14180
- hasNewActivity: sessionCount > 0,
14567
+ hasNewActivity,
14181
14568
  sessionCount,
14182
14569
  messageCount,
14183
- toolCallCount: 0,
14570
+ toolCallCount: rollout.toolCallCount,
14184
14571
  tokensUsed,
14185
14572
  aiLinesAdded: 0,
14186
14573
  aiLinesRemoved: 0,
14187
- lastActiveAt: latestSec > 0 ? new Date(latestSec * 1000).toISOString() : null,
14574
+ lastActiveAt: latestMs > 0 ? new Date(latestMs).toISOString() : null,
14575
+ activeDays: rollout.activeDays.length > 0 ? rollout.activeDays : undefined,
14188
14576
  extra: versions.length > 0 ? { cliVersions: versions } : undefined
14189
14577
  };
14190
14578
  } catch {
14191
14579
  return null;
14192
14580
  }
14193
14581
  }
14582
+ scanRolloutActivity(sinceMs) {
14583
+ const days = new Set;
14584
+ const result = {
14585
+ sessionCount: 0,
14586
+ messageCount: 0,
14587
+ toolCallCount: 0,
14588
+ tokensUsed: 0,
14589
+ latestMs: 0,
14590
+ activeDays: []
14591
+ };
14592
+ const sessionsDir = join28(homedir11(), ".codex", "sessions");
14593
+ if (!existsSync33(sessionsDir))
14594
+ return result;
14595
+ const files = [];
14596
+ const walk = (dir) => {
14597
+ let entries;
14598
+ try {
14599
+ entries = readdirSync9(dir, { withFileTypes: true });
14600
+ } catch {
14601
+ return;
14602
+ }
14603
+ for (const e of entries) {
14604
+ const full = join28(dir, e.name);
14605
+ if (e.isDirectory())
14606
+ walk(full);
14607
+ else if (e.name.startsWith("rollout-") && e.name.endsWith(".jsonl"))
14608
+ files.push(full);
14609
+ }
14610
+ };
14611
+ walk(sessionsDir);
14612
+ for (const file of files) {
14613
+ let stat;
14614
+ try {
14615
+ stat = statSync5(file);
14616
+ } catch {
14617
+ continue;
14618
+ }
14619
+ if (stat.mtimeMs <= sinceMs)
14620
+ continue;
14621
+ let content;
14622
+ try {
14623
+ content = readFileSync26(file, "utf-8");
14624
+ } catch {
14625
+ continue;
14626
+ }
14627
+ let fileHadActivity = false;
14628
+ let fileTokens = 0;
14629
+ for (const line of content.split(`
14630
+ `)) {
14631
+ if (!line.trim())
14632
+ continue;
14633
+ let o;
14634
+ try {
14635
+ o = JSON.parse(line);
14636
+ } catch {
14637
+ continue;
14638
+ }
14639
+ const ts = typeof o.timestamp === "string" ? Date.parse(o.timestamp) : NaN;
14640
+ if (!Number.isFinite(ts) || ts <= sinceMs)
14641
+ continue;
14642
+ const p = o.payload;
14643
+ if (!p || typeof p !== "object")
14644
+ continue;
14645
+ const pt = p.type;
14646
+ if (o.type === "event_msg") {
14647
+ if (pt === "user_message" || pt === "agent_message") {
14648
+ result.messageCount++;
14649
+ fileHadActivity = true;
14650
+ if (ts > result.latestMs)
14651
+ result.latestMs = ts;
14652
+ days.add(new Date(ts).toISOString().slice(0, 10));
14653
+ } else if (pt === "token_count") {
14654
+ const info = p.info;
14655
+ const usage = info?.total_token_usage;
14656
+ if (usage) {
14657
+ const total = (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0);
14658
+ if (total > fileTokens)
14659
+ fileTokens = total;
14660
+ }
14661
+ if (ts > result.latestMs)
14662
+ result.latestMs = ts;
14663
+ }
14664
+ } else if (o.type === "response_item" && (pt === "function_call" || pt === "tool_search_call")) {
14665
+ result.toolCallCount++;
14666
+ fileHadActivity = true;
14667
+ if (ts > result.latestMs)
14668
+ result.latestMs = ts;
14669
+ days.add(new Date(ts).toISOString().slice(0, 10));
14670
+ }
14671
+ }
14672
+ if (fileHadActivity) {
14673
+ result.sessionCount++;
14674
+ result.tokensUsed += fileTokens;
14675
+ }
14676
+ }
14677
+ result.activeDays = [...days].sort();
14678
+ return result;
14679
+ }
14194
14680
  async readSessionDigests(sinceISO) {
14195
14681
  try {
14196
14682
  const sessionsDir = join28(homedir11(), ".codex", "sessions");
@@ -14308,9 +14794,10 @@ class CodexAdapter {
14308
14794
  } catch {
14309
14795
  return;
14310
14796
  }
14797
+ const seenInFile = new Set;
14311
14798
  for (const line of content.split(`
14312
14799
  `)) {
14313
- if (!line)
14800
+ if (!line || !line.includes("SKILL.md"))
14314
14801
  continue;
14315
14802
  let entry;
14316
14803
  try {
@@ -14318,35 +14805,37 @@ class CodexAdapter {
14318
14805
  } catch {
14319
14806
  continue;
14320
14807
  }
14321
- if (entry.type !== "session_meta")
14808
+ if (entry.type !== "response_item")
14322
14809
  continue;
14323
14810
  const payload = entry.payload;
14324
14811
  if (!payload)
14325
14812
  continue;
14813
+ const pt = payload.type;
14814
+ if (pt !== "function_call" && pt !== "custom_tool_call")
14815
+ continue;
14326
14816
  const tsRaw = entry.timestamp;
14327
14817
  const ts = typeof tsRaw === "string" ? Date.parse(tsRaw) : 0;
14328
- if (ts <= sinceMs)
14818
+ if (!Number.isFinite(ts) || ts <= sinceMs)
14329
14819
  continue;
14330
- const instructions = payload.instructions;
14331
- if (typeof instructions !== "string")
14820
+ const args = typeof payload.arguments === "string" ? payload.arguments : JSON.stringify(payload.arguments ?? "");
14821
+ const m = args.match(/([^\s"']*\/skills\/[^\s"']*\/)SKILL\.md/);
14822
+ if (!m)
14332
14823
  continue;
14333
- const skillRegex = /^- ([^:]+):\s+.+\(file:\s+(.+\/SKILL\.md)\)/gm;
14334
- let match;
14335
- while ((match = skillRegex.exec(instructions)) !== null) {
14336
- const skillName = match[1].trim();
14337
- const skillPath = match[2];
14338
- if (skillPath.includes("/.system/"))
14339
- continue;
14340
- const existing = skillCounts.get(skillName);
14341
- if (existing) {
14342
- existing.count++;
14343
- if (ts > existing.lastTs)
14344
- existing.lastTs = ts;
14345
- } else {
14346
- skillCounts.set(skillName, { count: 1, lastTs: ts });
14347
- }
14824
+ const skillDir = m[1];
14825
+ if (skillDir.includes("/plugins/cache/openai-") || skillDir.includes("/.system/"))
14826
+ continue;
14827
+ const skillName = skillNameFromPath(skillDir);
14828
+ if (!skillName || seenInFile.has(skillName))
14829
+ continue;
14830
+ seenInFile.add(skillName);
14831
+ const existing = skillCounts.get(skillName);
14832
+ if (existing) {
14833
+ existing.count++;
14834
+ if (ts > existing.lastTs)
14835
+ existing.lastTs = ts;
14836
+ } else {
14837
+ skillCounts.set(skillName, { count: 1, lastTs: ts });
14348
14838
  }
14349
- break;
14350
14839
  }
14351
14840
  }
14352
14841
  registerDesktopWorkspace(workspacePath, label) {
@@ -14520,7 +15009,7 @@ class ClineAdapter {
14520
15009
  }
14521
15010
 
14522
15011
  // src/agents/gemini.ts
14523
- import { existsSync as existsSync35, mkdirSync as mkdirSync22, readdirSync as readdirSync11, readFileSync as readFileSync28, rmSync as rmSync10, writeFileSync as writeFileSync23 } from "fs";
15012
+ import { existsSync as existsSync35, mkdirSync as mkdirSync22, readdirSync as readdirSync11, readFileSync as readFileSync28, rmSync as rmSync10, statSync as statSync6, writeFileSync as writeFileSync23 } from "fs";
14524
15013
  import { join as join30 } from "path";
14525
15014
  import { homedir as homedir13 } from "os";
14526
15015
  class GeminiAdapter {
@@ -14598,6 +15087,86 @@ class GeminiAdapter {
14598
15087
  mkdirSync22(join30(settingsPath, ".."), { recursive: true });
14599
15088
  writeFileSync23(settingsPath, JSON.stringify(settings, null, 2));
14600
15089
  }
15090
+ async readUsageStats(lastSyncAt) {
15091
+ try {
15092
+ const tmpDir = join30(homedir13(), ".gemini", "tmp");
15093
+ if (!existsSync35(tmpDir))
15094
+ return null;
15095
+ const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
15096
+ let sessionCount = 0;
15097
+ let messageCount = 0;
15098
+ let latestMs = 0;
15099
+ const activeDays = new Set;
15100
+ let projects;
15101
+ try {
15102
+ projects = readdirSync11(tmpDir, { withFileTypes: true });
15103
+ } catch {
15104
+ return null;
15105
+ }
15106
+ for (const project of projects) {
15107
+ if (!project.isDirectory())
15108
+ continue;
15109
+ const chatsDir = join30(tmpDir, project.name, "chats");
15110
+ let files;
15111
+ try {
15112
+ files = readdirSync11(chatsDir, { withFileTypes: true });
15113
+ } catch {
15114
+ continue;
15115
+ }
15116
+ for (const file of files) {
15117
+ if (!file.name.startsWith("session-") || !file.name.endsWith(".json"))
15118
+ continue;
15119
+ const filePath = join30(chatsDir, file.name);
15120
+ let stat;
15121
+ try {
15122
+ stat = statSync6(filePath);
15123
+ } catch {
15124
+ continue;
15125
+ }
15126
+ if (stat.mtimeMs <= sinceMs)
15127
+ continue;
15128
+ let session;
15129
+ try {
15130
+ session = JSON.parse(readFileSync28(filePath, "utf-8"));
15131
+ } catch {
15132
+ continue;
15133
+ }
15134
+ if (!Array.isArray(session.messages))
15135
+ continue;
15136
+ let sessionHadActivity = false;
15137
+ for (const m of session.messages) {
15138
+ const ts = typeof m.timestamp === "string" ? Date.parse(m.timestamp) : NaN;
15139
+ if (!Number.isFinite(ts) || ts <= sinceMs)
15140
+ continue;
15141
+ if (m.type === "user" || m.type === "gemini") {
15142
+ messageCount++;
15143
+ sessionHadActivity = true;
15144
+ if (ts > latestMs)
15145
+ latestMs = ts;
15146
+ activeDays.add(new Date(ts).toISOString().slice(0, 10));
15147
+ }
15148
+ }
15149
+ if (sessionHadActivity)
15150
+ sessionCount++;
15151
+ }
15152
+ }
15153
+ if (sessionCount === 0)
15154
+ return null;
15155
+ return {
15156
+ hasNewActivity: true,
15157
+ sessionCount,
15158
+ messageCount,
15159
+ toolCallCount: 0,
15160
+ tokensUsed: 0,
15161
+ aiLinesAdded: 0,
15162
+ aiLinesRemoved: 0,
15163
+ lastActiveAt: latestMs > 0 ? new Date(latestMs).toISOString() : null,
15164
+ activeDays: [...activeDays].sort()
15165
+ };
15166
+ } catch {
15167
+ return null;
15168
+ }
15169
+ }
14601
15170
  async cleanup(scope, manifest) {
14602
15171
  if (scope === "user") {
14603
15172
  removeRunworkMcpServers(join30(homedir13(), ".gemini", "settings.json"), "mcpServers");
@@ -15412,7 +15981,7 @@ function parseJson(content, source) {
15412
15981
  }
15413
15982
 
15414
15983
  // src/commands/entities.ts
15415
- 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) => {
15984
+ 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) => {
15416
15985
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15417
15986
  const credentials = requireAuth();
15418
15987
  const client = new ApiClient(credentials);
@@ -15447,7 +16016,7 @@ Workspace: ${workspaceName || workspaceId}
15447
16016
  process.exit(1);
15448
16017
  }
15449
16018
  });
15450
- 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) => {
16019
+ 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) => {
15451
16020
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15452
16021
  const credentials = requireAuth();
15453
16022
  const client = new ApiClient(credentials);
@@ -15479,7 +16048,7 @@ Next cursor: ${result.next}`);
15479
16048
  process.exit(1);
15480
16049
  }
15481
16050
  });
15482
- 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) => {
16051
+ 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) => {
15483
16052
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15484
16053
  const credentials = requireAuth();
15485
16054
  const client = new ApiClient(credentials);
@@ -15496,7 +16065,7 @@ var getCommand = new Command15("get").description("Get a single entity record by
15496
16065
  process.exit(1);
15497
16066
  }
15498
16067
  });
15499
- 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) => {
16068
+ 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) => {
15500
16069
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15501
16070
  const credentials = requireAuth();
15502
16071
  const client = new ApiClient(credentials);
@@ -15515,7 +16084,7 @@ var createCommand = new Command15("create").description("Create a new entity rec
15515
16084
  process.exit(1);
15516
16085
  }
15517
16086
  });
15518
- 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) => {
16087
+ 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) => {
15519
16088
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15520
16089
  const credentials = requireAuth();
15521
16090
  const client = new ApiClient(credentials);
@@ -15534,7 +16103,7 @@ var updateCommand = new Command15("update").description("Update an existing enti
15534
16103
  process.exit(1);
15535
16104
  }
15536
16105
  });
15537
- 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) => {
16106
+ 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) => {
15538
16107
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15539
16108
  const credentials = requireAuth();
15540
16109
  const client = new ApiClient(credentials);
@@ -15573,7 +16142,7 @@ function truncate2(text2, max) {
15573
16142
  return first;
15574
16143
  return first.slice(0, max - 3) + "...";
15575
16144
  }
15576
- 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) => {
16145
+ 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) => {
15577
16146
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15578
16147
  const credentials = requireAuth();
15579
16148
  const client = new ApiClient(credentials);
@@ -15607,7 +16176,7 @@ Workspace: ${workspaceName || workspaceId}
15607
16176
  process.exit(1);
15608
16177
  }
15609
16178
  });
15610
- 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) => {
16179
+ 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) => {
15611
16180
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15612
16181
  const credentials = requireAuth();
15613
16182
  const client = new ApiClient(credentials);
@@ -15641,7 +16210,7 @@ var triggerCommand = new Command16("trigger").description("Trigger a workflow by
15641
16210
  process.exit(1);
15642
16211
  }
15643
16212
  });
15644
- 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) => {
16213
+ 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) => {
15645
16214
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15646
16215
  const credentials = requireAuth();
15647
16216
  const client = new ApiClient(credentials);
@@ -15674,7 +16243,7 @@ var instancesCommand = new Command16("instances").description("List workflow ins
15674
16243
  process.exit(1);
15675
16244
  }
15676
16245
  });
15677
- 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) => {
16246
+ 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) => {
15678
16247
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15679
16248
  const credentials = requireAuth();
15680
16249
  const client = new ApiClient(credentials);
@@ -15702,16 +16271,64 @@ var workflowsCommand = new Command16("workflows").description("Manage workspace
15702
16271
  init_store();
15703
16272
  init_client();
15704
16273
  import { Command as Command17 } from "commander";
15705
- function truncate3(text2, max) {
15706
- if (!text2)
16274
+ // src/utils/table.ts
16275
+ function clip(value, max) {
16276
+ if (max <= 0)
15707
16277
  return "";
15708
- const first = text2.split(`
15709
- `)[0];
15710
- if (first.length <= max)
15711
- return first;
15712
- return first.slice(0, max - 3) + "...";
16278
+ if (value.length <= max)
16279
+ return value;
16280
+ if (max <= 3)
16281
+ return value.slice(0, max);
16282
+ return value.slice(0, max - 3) + "...";
16283
+ }
16284
+ function renderTable(columns, rows, indent = " ") {
16285
+ const gap = " ";
16286
+ const lastIdx = columns.length - 1;
16287
+ const data = rows.map((row) => columns.map((c, i) => {
16288
+ const raw = row[i] == null ? "" : String(row[i]);
16289
+ return c.max !== undefined ? clip(raw, c.max) : raw;
16290
+ }));
16291
+ const widths = columns.map((c, i) => {
16292
+ let w = c.header.length;
16293
+ for (const row of data)
16294
+ w = Math.max(w, row[i].length);
16295
+ return w;
16296
+ });
16297
+ const renderRow = (cells, colorize) => {
16298
+ const parts = cells.map((cell, i) => {
16299
+ const padded = i === lastIdx ? cell : cell.padEnd(widths[i]);
16300
+ const color = columns[i].color;
16301
+ return colorize && color ? color(padded) : padded;
16302
+ });
16303
+ return (indent + parts.join(gap)).replace(/\s+$/, "");
16304
+ };
16305
+ const out = [renderRow(columns.map((c) => c.header), false)];
16306
+ const total = widths.reduce((a, b) => a + b, 0) + gap.length * Math.max(0, columns.length - 1);
16307
+ out.push(indent + "-".repeat(total));
16308
+ for (const row of data)
16309
+ out.push(renderRow(row, true));
16310
+ return out.join(`
16311
+ `);
15713
16312
  }
15714
- 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) => {
16313
+
16314
+ // src/commands/schedules.ts
16315
+ function extractScheduleHistoryRecords(data) {
16316
+ if (!data || !Array.isArray(data.records))
16317
+ return [];
16318
+ return data.records;
16319
+ }
16320
+ function scheduleRowCells(s) {
16321
+ const mode = s.deploymentMode ? ` (${s.deploymentMode})` : "";
16322
+ const status = s.status ? s.status + mode : mode.trim() || "-";
16323
+ return [
16324
+ s.scheduleName || "(unnamed)",
16325
+ s.appName || s.appId || "-",
16326
+ s.schedule || "-",
16327
+ status,
16328
+ s.description || ""
16329
+ ];
16330
+ }
16331
+ 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) => {
15715
16332
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15716
16333
  const credentials = requireAuth();
15717
16334
  const client = new ApiClient(credentials);
@@ -15733,20 +16350,20 @@ var listCommand5 = new Command17("list").description("List scheduled jobs in the
15733
16350
  console.log(`
15734
16351
  Workspace: ${workspaceName || workspaceId}
15735
16352
  `);
15736
- console.log(" " + "Name".padEnd(24) + "App".padEnd(20) + "Schedule".padEnd(18) + "Status".padEnd(12) + "Description");
15737
- console.log(" " + "-".repeat(90));
15738
- for (const s of schedules) {
15739
- const mode = s.deploymentMode ? ` (${s.deploymentMode})` : "";
15740
- const status = s.status ? s.status + mode : mode.trim() || "-";
15741
- 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));
15742
- }
16353
+ console.log(renderTable([
16354
+ { header: "Name" },
16355
+ { header: "App" },
16356
+ { header: "Schedule" },
16357
+ { header: "Status" },
16358
+ { header: "Description", max: 60 }
16359
+ ], schedules.map(scheduleRowCells)));
15743
16360
  console.log("");
15744
16361
  } catch (err) {
15745
16362
  console.error("Failed to list schedules:", err instanceof Error ? err.message : err);
15746
16363
  process.exit(1);
15747
16364
  }
15748
16365
  });
15749
- 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) => {
16366
+ 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) => {
15750
16367
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15751
16368
  const credentials = requireAuth();
15752
16369
  const client = new ApiClient(credentials);
@@ -15772,7 +16389,7 @@ var triggerCommand2 = new Command17("trigger").description("Manually trigger a s
15772
16389
  process.exit(1);
15773
16390
  }
15774
16391
  });
15775
- 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) => {
16392
+ 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) => {
15776
16393
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15777
16394
  const credentials = requireAuth();
15778
16395
  const client = new ApiClient(credentials);
@@ -15780,25 +16397,34 @@ var historyCommand = new Command17("history").description("Show execution histor
15780
16397
  const { appId, appName } = await resolveApp2(client, workspaceId, opts);
15781
16398
  try {
15782
16399
  const data = await client.getScheduleHistory(workspaceId, appId);
16400
+ const records = extractScheduleHistoryRecords(data);
15783
16401
  if (useJson) {
15784
- jsonOut({ history: data.history, appId, appName });
16402
+ jsonOut({ history: records, appId, appName });
15785
16403
  return;
15786
16404
  }
15787
- if (!data.history || data.history.length === 0) {
16405
+ if (records.length === 0) {
15788
16406
  console.log(`No schedule history found for app "${appName}".`);
15789
16407
  return;
15790
16408
  }
15791
16409
  console.log(`
15792
16410
  Schedule history for app: ${appName}
15793
16411
  `);
15794
- console.log(" " + "Schedule".padEnd(24) + "Executed At".padEnd(26) + "Status".padEnd(12) + "Duration");
15795
- console.log(" " + "-".repeat(72));
15796
- for (const entry of data.history) {
16412
+ const nameW = Math.max("Schedule".length, ...records.map((r) => (r.jobName || "-").length));
16413
+ console.log(" " + "Schedule".padEnd(nameW + 2) + "Started At".padEnd(26) + "Status".padEnd(12) + "Duration");
16414
+ console.log(" " + "-".repeat(nameW + 2 + 26 + 12 + 8));
16415
+ for (const entry of records) {
16416
+ const startedAt = entry.startedAt ? new Date(entry.startedAt).toISOString() : "-";
15797
16417
  const duration = entry.durationMs != null ? `${entry.durationMs}ms` : "-";
15798
- console.log(" " + entry.scheduleName.padEnd(24) + entry.executedAt.padEnd(26) + entry.status.padEnd(12) + duration);
16418
+ console.log(" " + (entry.jobName || "-").padEnd(nameW + 2) + startedAt.padEnd(26) + entry.status.padEnd(12) + duration);
15799
16419
  if (entry.error) {
15800
16420
  console.log(` Error: ${entry.error}`);
15801
16421
  }
16422
+ if (entry.errorStack) {
16423
+ for (const line of entry.errorStack.split(`
16424
+ `).slice(0, 8)) {
16425
+ console.log(` ${line.trim()}`);
16426
+ }
16427
+ }
15802
16428
  }
15803
16429
  console.log("");
15804
16430
  } catch (err) {
@@ -15806,7 +16432,7 @@ Schedule history for app: ${appName}
15806
16432
  process.exit(1);
15807
16433
  }
15808
16434
  });
15809
- 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) => {
16435
+ 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) => {
15810
16436
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15811
16437
  const credentials = requireAuth();
15812
16438
  const client = new ApiClient(credentials);
@@ -15828,7 +16454,7 @@ Schedule "${name}" status for app "${appName}":
15828
16454
  process.exit(1);
15829
16455
  }
15830
16456
  });
15831
- 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) => {
16457
+ 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) => {
15832
16458
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15833
16459
  const credentials = requireAuth();
15834
16460
  const client = new ApiClient(credentials);
@@ -15846,7 +16472,7 @@ var pauseCommand = new Command17("pause").description("Pause a scheduled job").a
15846
16472
  process.exit(1);
15847
16473
  }
15848
16474
  });
15849
- 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) => {
16475
+ 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) => {
15850
16476
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15851
16477
  const credentials = requireAuth();
15852
16478
  const client = new ApiClient(credentials);
@@ -15871,7 +16497,7 @@ init_store();
15871
16497
  init_client();
15872
16498
  import { Command as Command18 } from "commander";
15873
16499
  init_http();
15874
- function truncate4(text2, max) {
16500
+ function truncate3(text2, max) {
15875
16501
  if (!text2)
15876
16502
  return "";
15877
16503
  const first = text2.split(`
@@ -15880,7 +16506,7 @@ function truncate4(text2, max) {
15880
16506
  return first;
15881
16507
  return first.slice(0, max - 3) + "...";
15882
16508
  }
15883
- 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) => {
16509
+ 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) => {
15884
16510
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15885
16511
  const credentials = requireAuth();
15886
16512
  const client = new ApiClient(credentials);
@@ -15908,7 +16534,7 @@ Workspace: ${workspaceName || workspaceId}
15908
16534
  const path2 = e.endpointPath.padEnd(36);
15909
16535
  const app = (e.appName || e.appId).padEnd(20);
15910
16536
  const auth = (e.auth || "").padEnd(10);
15911
- const desc = truncate4(e.description, 40);
16537
+ const desc = truncate3(e.description, 40);
15912
16538
  const mode = e.deploymentMode ? ` [${e.deploymentMode}]` : "";
15913
16539
  console.log(` ${method} ${path2} ${app} ${auth} ${desc}${mode}`);
15914
16540
  }
@@ -15918,7 +16544,7 @@ Workspace: ${workspaceName || workspaceId}
15918
16544
  process.exit(1);
15919
16545
  }
15920
16546
  });
15921
- 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) => {
16547
+ 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) => {
15922
16548
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
15923
16549
  const credentials = requireAuth();
15924
16550
  const client = new ApiClient(credentials);
@@ -16049,7 +16675,7 @@ function formatSize(bytes) {
16049
16675
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
16050
16676
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
16051
16677
  }
16052
- var listCommand7 = new Command19("list").description("List file storage buckets in the workspace").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
16678
+ 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) => {
16053
16679
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16054
16680
  const credentials = requireAuth();
16055
16681
  const client = new ApiClient(credentials);
@@ -16079,7 +16705,7 @@ Workspace: ${workspaceName || workspaceId}
16079
16705
  process.exit(1);
16080
16706
  }
16081
16707
  });
16082
- 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) => {
16708
+ 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) => {
16083
16709
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16084
16710
  const credentials = requireAuth();
16085
16711
  const client = new ApiClient(credentials);
@@ -16116,7 +16742,7 @@ var lsCommand = new Command19("ls").description("List files in a bucket").argume
16116
16742
  process.exit(1);
16117
16743
  }
16118
16744
  });
16119
- 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) => {
16745
+ 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) => {
16120
16746
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16121
16747
  const credentials = requireAuth();
16122
16748
  const client = new ApiClient(credentials);
@@ -16139,7 +16765,7 @@ var downloadCommand = new Command19("download").description("Download a file fro
16139
16765
  process.exit(1);
16140
16766
  }
16141
16767
  });
16142
- 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) => {
16768
+ 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) => {
16143
16769
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16144
16770
  const credentials = requireAuth();
16145
16771
  const client = new ApiClient(credentials);
@@ -16162,7 +16788,7 @@ var uploadCommand = new Command19("upload").description("Upload a local file to
16162
16788
  process.exit(1);
16163
16789
  }
16164
16790
  });
16165
- 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) => {
16791
+ 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) => {
16166
16792
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16167
16793
  const credentials = requireAuth();
16168
16794
  const client = new ApiClient(credentials);
@@ -16192,7 +16818,7 @@ var filesCommand = new Command19("files").description("Manage workspace file sto
16192
16818
  init_store();
16193
16819
  init_client();
16194
16820
  import { Command as Command20 } from "commander";
16195
- 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) => {
16821
+ 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) => {
16196
16822
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16197
16823
  const credentials = requireAuth();
16198
16824
  const client = new ApiClient(credentials);
@@ -16231,7 +16857,7 @@ init_store();
16231
16857
  init_client();
16232
16858
  import { Command as Command21 } from "commander";
16233
16859
  init_prompt();
16234
- var listCommand9 = new Command21("list").description("List API keys for the workspace").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
16860
+ 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) => {
16235
16861
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16236
16862
  const credentials = requireAuth();
16237
16863
  const client = new ApiClient(credentials);
@@ -16266,7 +16892,7 @@ Workspace: ${workspaceName || workspaceId}
16266
16892
  process.exit(1);
16267
16893
  }
16268
16894
  });
16269
- 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) => {
16895
+ 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) => {
16270
16896
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16271
16897
  const credentials = requireAuth();
16272
16898
  const client = new ApiClient(credentials);
@@ -16314,7 +16940,7 @@ var apiKeysCommand = new Command21("api-keys").description("Manage workspace API
16314
16940
  init_store();
16315
16941
  init_client();
16316
16942
  import { Command as Command22 } from "commander";
16317
- function truncate5(text2, max) {
16943
+ function truncate4(text2, max) {
16318
16944
  if (!text2)
16319
16945
  return "";
16320
16946
  const first = text2.split(`
@@ -16323,7 +16949,7 @@ function truncate5(text2, max) {
16323
16949
  return first;
16324
16950
  return first.slice(0, max - 3) + "...";
16325
16951
  }
16326
- 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) => {
16952
+ 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) => {
16327
16953
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16328
16954
  const credentials = requireAuth();
16329
16955
  const client = new ApiClient(credentials);
@@ -16349,8 +16975,8 @@ Workspace: ${workspaceName || workspaceId}
16349
16975
  for (const a of agents) {
16350
16976
  const name = a.agentName.padEnd(24);
16351
16977
  const type = a.type.padEnd(16);
16352
- const app = truncate5(a.appName, 18).padEnd(20);
16353
- const desc = truncate5(a.description, 40).padEnd(42);
16978
+ const app = truncate4(a.appName, 18).padEnd(20);
16979
+ const desc = truncate4(a.description, 40).padEnd(42);
16354
16980
  const integrations = (a.integrations?.join(", ") || "").padEnd(20);
16355
16981
  const entities = (a.entities?.join(", ") || "").padEnd(20);
16356
16982
  const mode = a.deploymentMode || "";
@@ -16362,7 +16988,7 @@ Workspace: ${workspaceName || workspaceId}
16362
16988
  process.exit(1);
16363
16989
  }
16364
16990
  });
16365
- 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) => {
16991
+ 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) => {
16366
16992
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16367
16993
  const credentials = requireAuth();
16368
16994
  const client = new ApiClient(credentials);
@@ -16380,7 +17006,7 @@ var statusCommand2 = new Command22("status").description("Get status of a specif
16380
17006
  process.exit(1);
16381
17007
  }
16382
17008
  });
16383
- var executionsCommand = new Command22("executions").description("List agent execution history for workspace").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
17009
+ 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) => {
16384
17010
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16385
17011
  const credentials = requireAuth();
16386
17012
  const client = new ApiClient(credentials);
@@ -16399,9 +17025,9 @@ var executionsCommand = new Command22("executions").description("List agent exec
16399
17025
  console.log(" " + "-".repeat(160));
16400
17026
  for (const e of executions) {
16401
17027
  const sessionId = e.sessionId.padEnd(36);
16402
- const agent = truncate5(e.agentName, 20).padEnd(22);
16403
- const app = truncate5(e.appId, 20).padEnd(22);
16404
- const prompt = truncate5(e.prompt, 50).padEnd(52);
17028
+ const agent = truncate4(e.agentName, 20).padEnd(22);
17029
+ const app = truncate4(e.appId, 20).padEnd(22);
17030
+ const prompt = truncate4(e.prompt, 50).padEnd(52);
16405
17031
  const status = e.status.padEnd(12);
16406
17032
  const duration = e.durationMs != null ? `${e.durationMs}ms` : "";
16407
17033
  const usage = e.usage ? `${e.usage.inputTokens}/${e.usage.outputTokens}` : "";
@@ -16413,7 +17039,7 @@ var executionsCommand = new Command22("executions").description("List agent exec
16413
17039
  process.exit(1);
16414
17040
  }
16415
17041
  });
16416
- 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) => {
17042
+ 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) => {
16417
17043
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16418
17044
  const credentials = requireAuth();
16419
17045
  const client = new ApiClient(credentials);
@@ -16437,7 +17063,7 @@ init_store();
16437
17063
  init_client();
16438
17064
  import { Command as Command23 } from "commander";
16439
17065
  init_prompt();
16440
- var listCommand11 = new Command23("list").description("List workspace MCP servers").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
17066
+ var listCommand11 = new Command23("list").description("List workspace MCP servers").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
16441
17067
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16442
17068
  const credentials = requireAuth();
16443
17069
  const client = new ApiClient(credentials);
@@ -16493,7 +17119,7 @@ Workspace: ${workspaceName || workspaceId}
16493
17119
  process.exit(1);
16494
17120
  }
16495
17121
  });
16496
- 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) => {
17122
+ 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) => {
16497
17123
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16498
17124
  const credentials = requireAuth();
16499
17125
  const client = new ApiClient(credentials);
@@ -16521,7 +17147,7 @@ var addCommand = new Command23("add").description("Add an MCP server to workspac
16521
17147
  process.exit(1);
16522
17148
  }
16523
17149
  });
16524
- 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) => {
17150
+ 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) => {
16525
17151
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16526
17152
  const credentials = requireAuth();
16527
17153
  const client = new ApiClient(credentials);
@@ -16584,7 +17210,7 @@ var searchCommand3 = new Command23("search").description("Search community MCP s
16584
17210
  process.exit(1);
16585
17211
  }
16586
17212
  });
16587
- 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) => {
17213
+ 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) => {
16588
17214
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
16589
17215
  const credentials = requireAuth();
16590
17216
  const client = new ApiClient(credentials);
@@ -16717,6 +17343,12 @@ var RUNWORK_AGENT_DEFAULTS = {
16717
17343
  var AGENT_DEFAULTS_SCHEMA_VERSION = 1;
16718
17344
 
16719
17345
  // src/commands/sync-telemetry.ts
17346
+ var TELEMETRY_BACKFILL_MS = 30 * 24 * 60 * 60 * 1000;
17347
+ function resolveTelemetrySince(state, nowMs = Date.now()) {
17348
+ if (state.lastTelemetryAt)
17349
+ return state.lastTelemetryAt;
17350
+ return new Date(nowMs - TELEMETRY_BACKFILL_MS).toISOString();
17351
+ }
16720
17352
  async function collectTelemetryEvents(params) {
16721
17353
  const now = params.now ?? new Date().toISOString();
16722
17354
  const events = [];
@@ -16727,7 +17359,7 @@ async function collectTelemetryEvents(params) {
16727
17359
  let error;
16728
17360
  if (adapter2.readUsageStats) {
16729
17361
  try {
16730
- stats = await adapter2.readUsageStats(params.state.lastSyncAt ?? null);
17362
+ stats = await adapter2.readUsageStats(params.since);
16731
17363
  if (stats && stats.hasNewActivity) {
16732
17364
  events.push({
16733
17365
  eventType: "local_agent.usage",
@@ -16740,6 +17372,7 @@ async function collectTelemetryEvents(params) {
16740
17372
  aiLinesAdded: stats.aiLinesAdded,
16741
17373
  aiLinesRemoved: stats.aiLinesRemoved,
16742
17374
  lastActiveAt: stats.lastActiveAt,
17375
+ activeDays: stats.activeDays,
16743
17376
  modelsUsed: stats.modelsUsed,
16744
17377
  mcpToolCount: stats.mcpToolCount,
16745
17378
  ...stats.extra
@@ -16754,7 +17387,7 @@ async function collectTelemetryEvents(params) {
16754
17387
  }
16755
17388
  if (adapter2.readSkillUsage) {
16756
17389
  try {
16757
- skills = await adapter2.readSkillUsage(params.state.lastSyncAt ?? null);
17390
+ skills = await adapter2.readSkillUsage(params.since);
16758
17391
  if (skills && skills.length > 0) {
16759
17392
  for (const entry of skills) {
16760
17393
  events.push({
@@ -18079,6 +18712,7 @@ async function syncFromState(state, statePath2, credentials, opts) {
18079
18712
  const telemetry = await collectTelemetryEvents({
18080
18713
  adapters,
18081
18714
  state,
18715
+ since: resolveTelemetrySince(state),
18082
18716
  syncedSkillsCount: remoteSkills.length,
18083
18717
  syncedMcpServersCount: mcpEntries.length,
18084
18718
  teamInstructionsApplied: false,
@@ -18458,21 +19092,25 @@ async function syncFromState(state, statePath2, credentials, opts) {
18458
19092
  state.skillHashes = mergedHashes;
18459
19093
  writeFileSync29(statePath2, JSON.stringify(state, null, 2));
18460
19094
  try {
19095
+ const telemetryNow = new Date().toISOString();
18461
19096
  const telemetry = await collectTelemetryEvents({
18462
19097
  adapters,
18463
19098
  state,
19099
+ since: resolveTelemetrySince(state, Date.parse(telemetryNow)),
18464
19100
  syncedSkillsCount: remoteSkills.length,
18465
19101
  syncedMcpServersCount: mcpEntries.length,
18466
19102
  teamInstructionsApplied,
18467
- agentConfigsApplied
19103
+ agentConfigsApplied,
19104
+ now: telemetryNow
18468
19105
  });
18469
19106
  if (opts.verbose)
18470
19107
  printTelemetryVerbose(telemetry);
18471
19108
  await client.reportTelemetry(state.workspaceId, telemetry.events);
19109
+ state.lastTelemetryAt = telemetryNow;
18472
19110
  if (telemetry.healthReported) {
18473
19111
  state.lastHealthReportAt = new Date().toISOString();
18474
- writeFileSync29(statePath2, JSON.stringify(state, null, 2));
18475
19112
  }
19113
+ writeFileSync29(statePath2, JSON.stringify(state, null, 2));
18476
19114
  } catch {}
18477
19115
  const failedNote = summary.adaptersFailed > 0 ? ` (${summary.adaptersFailed} failed)` : "";
18478
19116
  if (isVerbose()) {
@@ -18589,7 +19227,7 @@ async function resolveAndPersistWorkspace(client, opts) {
18589
19227
  }
18590
19228
  return { workspaceId, workspaceName, workspaceSlug };
18591
19229
  }
18592
- 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) => {
19230
+ 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) => {
18593
19231
  const credentials = requireAuth();
18594
19232
  const client = new ApiClient(credentials);
18595
19233
  const { workspaceId, workspaceName, workspaceSlug } = await resolveAndPersistWorkspace(client, opts);
@@ -18941,7 +19579,7 @@ init_store();
18941
19579
  init_client();
18942
19580
  import { Command as Command28 } from "commander";
18943
19581
  init_init();
18944
- var listCommand12 = new Command28("list").description("List apps in workspace").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
19582
+ var listCommand12 = new Command28("list").description("List apps in workspace").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
18945
19583
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
18946
19584
  const credentials = requireAuth();
18947
19585
  const client = new ApiClient(credentials);
@@ -18974,7 +19612,7 @@ Workspace: ${workspaceName || workspaceId}
18974
19612
  var createCommand3 = new Command28("create").description("Create a new Runwork app").argument("[name]", "App name").action(async (name) => {
18975
19613
  await runCreateFlow(name);
18976
19614
  });
18977
- 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) => {
19615
+ 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) => {
18978
19616
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
18979
19617
  const credentials = requireAuth();
18980
19618
  const client = new ApiClient(credentials);
@@ -19001,15 +19639,157 @@ var infoCommand2 = new Command28("info").description("Show detailed app info, pr
19001
19639
  });
19002
19640
  var appsCommand = new Command28("apps").description("Manage workspace apps").addCommand(listCommand12).addCommand(createCommand3).addCommand(infoCommand2);
19003
19641
 
19642
+ // src/commands/members.ts
19643
+ init_store();
19644
+ init_client();
19645
+ import { Command as Command29 } from "commander";
19646
+ function formatMemberRows(members) {
19647
+ return members.map((m) => ({
19648
+ name: m.user.displayName || m.user.email,
19649
+ email: m.user.email,
19650
+ role: m.role,
19651
+ status: m.status,
19652
+ userId: m.userId
19653
+ }));
19654
+ }
19655
+ 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) => {
19656
+ const useJson = shouldOutputJson(command.optsWithGlobals().json);
19657
+ const credentials = requireAuth();
19658
+ const client = new ApiClient(credentials);
19659
+ const { workspaceId, workspaceName } = await resolveWorkspace2(client, opts);
19660
+ try {
19661
+ const members = await client.listWorkspaceMembers(workspaceId);
19662
+ if (useJson) {
19663
+ jsonOut({ workspaceId, workspaceName, members });
19664
+ return;
19665
+ }
19666
+ if (members.length === 0) {
19667
+ console.log("No members found in this workspace.");
19668
+ return;
19669
+ }
19670
+ console.log(`
19671
+ Workspace: ${workspaceName || workspaceId}
19672
+ `);
19673
+ console.log(` ${"NAME".padEnd(24)} ${"EMAIL".padEnd(32)} ${"ROLE".padEnd(10)} STATUS`);
19674
+ console.log(" " + "-".repeat(76));
19675
+ for (const row of formatMemberRows(members)) {
19676
+ console.log(` ${row.name.padEnd(24)} ${row.email.padEnd(32)} ${row.role.padEnd(10)} ${row.status}`);
19677
+ }
19678
+ console.log("");
19679
+ } catch (err) {
19680
+ console.error("Failed to list members:", err instanceof Error ? err.message : err);
19681
+ process.exit(1);
19682
+ }
19683
+ });
19684
+ var membersCommand = new Command29("members").description("List workspace members").addCommand(listCommand13);
19685
+
19686
+ // src/commands/api.ts
19687
+ init_store();
19688
+ init_client();
19689
+ import { Command as Command30 } from "commander";
19690
+ import { readFileSync as readFileSync37 } from "fs";
19691
+ function normalizeApiPath(rawPath, baseUrl) {
19692
+ if (/^https?:\/\//i.test(rawPath)) {
19693
+ const target = new URL(rawPath);
19694
+ const base = new URL(baseUrl);
19695
+ if (target.origin !== base.origin) {
19696
+ throw new Error(`Refusing to call ${target.origin}: \`runwork api\` only calls the platform API at ${base.origin}.`);
19697
+ }
19698
+ return target.pathname + target.search;
19699
+ }
19700
+ return rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
19701
+ }
19702
+ 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", `
19703
+ Examples:
19704
+ runwork api GET /api/workspaces
19705
+ runwork api GET /api/workspaces/<id>/members
19706
+ runwork api POST /api/workspaces/<id>/skills --body '{"name":"my-skill"}'
19707
+
19708
+ Authentication uses your stored Runwork credentials; the API key never needs
19709
+ to be read or pasted manually. Prefer a dedicated command when one exists
19710
+ (runwork apps/members/schedules/...).`).action(async (method, path2, opts, command) => {
19711
+ const useJson = shouldOutputJson(command.optsWithGlobals().json);
19712
+ const credentials = requireAuth();
19713
+ const client = new ApiClient(credentials);
19714
+ let finalMethod = method;
19715
+ let finalPath = path2;
19716
+ const headers = {};
19717
+ let body;
19718
+ const query = opts.query;
19719
+ if (opts.curl || opts.curlFile) {
19720
+ let curlStr = opts.curl;
19721
+ if (opts.curlFile) {
19722
+ try {
19723
+ curlStr = readFileSync37(opts.curlFile, "utf-8");
19724
+ } catch (err) {
19725
+ console.error(`Could not read --curl-file: ${err instanceof Error ? err.message : err}`);
19726
+ process.exit(1);
19727
+ }
19728
+ }
19729
+ const parsed = await parseCurlToRequest(curlStr);
19730
+ finalMethod = parsed.method;
19731
+ finalPath = parsed.path + (parsed.query ? `?${parsed.query}` : "");
19732
+ Object.assign(headers, parsed.headers);
19733
+ body = parsed.body;
19734
+ }
19735
+ for (const h of opts.header || []) {
19736
+ const [key, ...rest] = h.split(":");
19737
+ headers[key.trim()] = rest.join(":").trim();
19738
+ }
19739
+ if (opts.body) {
19740
+ let raw = opts.body;
19741
+ if (raw.startsWith("@")) {
19742
+ try {
19743
+ raw = readFileSync37(raw.slice(1), "utf-8");
19744
+ } catch (err) {
19745
+ console.error(`Could not read body file: ${err instanceof Error ? err.message : err}`);
19746
+ process.exit(1);
19747
+ }
19748
+ }
19749
+ try {
19750
+ body = JSON.parse(raw);
19751
+ } catch {
19752
+ console.error("Invalid JSON in --body");
19753
+ process.exit(1);
19754
+ }
19755
+ }
19756
+ if (!finalPath) {
19757
+ console.error("Usage: runwork api <method> <path>");
19758
+ console.error(' or: runwork api <method> --curl "curl ..."');
19759
+ process.exit(1);
19760
+ }
19761
+ try {
19762
+ const normalizedPath = normalizeApiPath(finalPath, credentials.baseUrl || "https://runwork.ai");
19763
+ const result = await client.rawApiCall(finalMethod, normalizedPath, {
19764
+ body,
19765
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
19766
+ query
19767
+ });
19768
+ if (useJson) {
19769
+ jsonOut({ success: result.ok, status: result.status, body: result.body });
19770
+ } else if (typeof result.body === "string") {
19771
+ console.log(result.body);
19772
+ } else {
19773
+ console.log(JSON.stringify(result.body, null, 2));
19774
+ }
19775
+ if (!result.ok)
19776
+ process.exit(1);
19777
+ } catch (err) {
19778
+ console.error("API call failed:", err instanceof Error ? err.message : err);
19779
+ process.exit(1);
19780
+ }
19781
+ });
19782
+
19004
19783
  // src/commands/doctor.ts
19005
19784
  init_colors();
19006
- import { Command as Command29 } from "commander";
19785
+ import { Command as Command31 } from "commander";
19007
19786
 
19008
19787
  // src/health/checks.ts
19009
19788
  init_subprocess();
19010
19789
  init_store();
19011
19790
  init_client();
19012
- import { existsSync as existsSync44, readFileSync as readFileSync37 } from "fs";
19791
+ import { parse as parse2 } from "smol-toml";
19792
+ import { existsSync as existsSync44, readFileSync as readFileSync38 } from "fs";
19013
19793
  import { join as join40, sep as sep3 } from "path";
19014
19794
  import { homedir as homedir22, platform as osPlatform2, arch as osArch } from "os";
19015
19795
  init_http();
@@ -19045,7 +19825,7 @@ function buildContext() {
19045
19825
  const configPath = join40(process.cwd(), ".runwork.json");
19046
19826
  if (existsSync44(configPath)) {
19047
19827
  try {
19048
- config = JSON.parse(readFileSync37(configPath, "utf-8"));
19828
+ config = JSON.parse(readFileSync38(configPath, "utf-8"));
19049
19829
  } catch {}
19050
19830
  }
19051
19831
  return { credentials, client, config, cwd: process.cwd() };
@@ -19265,6 +20045,14 @@ async function checkGitCredentialHelper(ctx) {
19265
20045
  fix: "runwork doctor --fix (re-registers with the current binary path)"
19266
20046
  };
19267
20047
  }
20048
+ if (!lookup.hasReset) {
20049
+ return {
20050
+ name: "git-credential-helper",
20051
+ status: "fail",
20052
+ message: `helper registered for ${origin} but the credential-manager reset entry is missing (Windows may show a Git Credential Manager popup)`,
20053
+ fix: "runwork doctor --fix"
20054
+ };
20055
+ }
19268
20056
  return {
19269
20057
  name: "git-credential-helper",
19270
20058
  status: "pass",
@@ -19394,7 +20182,7 @@ function loadSetupState5() {
19394
20182
  for (const p of [projectPath, userPath]) {
19395
20183
  if (existsSync44(p)) {
19396
20184
  try {
19397
- return JSON.parse(readFileSync37(p, "utf-8"));
20185
+ return JSON.parse(readFileSync38(p, "utf-8"));
19398
20186
  } catch {
19399
20187
  continue;
19400
20188
  }
@@ -19402,6 +20190,95 @@ function loadSetupState5() {
19402
20190
  }
19403
20191
  return null;
19404
20192
  }
20193
+ var RUNWORK_NETWORK_DOMAINS = ["runwork.ai", "*.runwork.ai"];
20194
+ async function checkCodexNetwork() {
20195
+ const name = "codex-network";
20196
+ const state = loadSetupState5();
20197
+ if (!state || !state.configuredAgents.includes("codex")) {
20198
+ return { name, status: "skip", message: "Codex not configured for Runwork" };
20199
+ }
20200
+ const configPath = join40(homedir22(), ".codex", "config.toml");
20201
+ if (!existsSync44(configPath)) {
20202
+ return { name, status: "skip", message: "no Codex config found" };
20203
+ }
20204
+ let parsed;
20205
+ try {
20206
+ parsed = parse2(readFileSync38(configPath, "utf-8"));
20207
+ } catch {
20208
+ return { name, status: "warn", message: "could not parse ~/.codex/config.toml" };
20209
+ }
20210
+ const features = parsed.features && typeof parsed.features === "object" ? parsed.features : undefined;
20211
+ const proxy = features?.network_proxy;
20212
+ if (proxy && proxy.enabled === true) {
20213
+ const domains = proxy.domains && typeof proxy.domains === "object" ? proxy.domains : {};
20214
+ if (RUNWORK_NETWORK_DOMAINS.some((d) => domains[d] === "deny")) {
20215
+ return {
20216
+ name,
20217
+ status: "warn",
20218
+ message: "network proxy denies runwork.ai; the Runwork CLI is blocked",
20219
+ fix: 'remove the runwork.ai "deny" rule in ~/.codex/config.toml'
20220
+ };
20221
+ }
20222
+ if (RUNWORK_NETWORK_DOMAINS.every((d) => domains[d] === "allow")) {
20223
+ return { name, status: "pass", message: "proxy-allowlisted: runwork.ai permitted, other domains scoped by your proxy" };
20224
+ }
20225
+ return {
20226
+ name,
20227
+ status: "warn",
20228
+ message: "network proxy is on but runwork.ai is not allowlisted",
20229
+ fix: "runwork sync"
20230
+ };
20231
+ }
20232
+ const sww = parsed.sandbox_workspace_write && typeof parsed.sandbox_workspace_write === "object" ? parsed.sandbox_workspace_write : undefined;
20233
+ const sandboxMode = typeof parsed.sandbox_mode === "string" ? parsed.sandbox_mode : undefined;
20234
+ if (sww?.network_access === true || sandboxMode === "danger-full-access") {
20235
+ return { name, status: "pass", message: "open: outbound network enabled (all domains)" };
20236
+ }
20237
+ if (sww?.network_access === false) {
20238
+ return {
20239
+ name,
20240
+ status: "warn",
20241
+ message: "off: network explicitly disabled; the Runwork CLI cannot reach the network",
20242
+ fix: "set [sandbox_workspace_write] network_access = true, or run runwork sync"
20243
+ };
20244
+ }
20245
+ return {
20246
+ name,
20247
+ status: "warn",
20248
+ message: "off: network is disabled by default in Codex workspace-write sandbox; the Runwork CLI cannot reach the network",
20249
+ fix: "runwork sync"
20250
+ };
20251
+ }
20252
+ async function checkCodexDesktopProject() {
20253
+ const name = "codex-desktop-project";
20254
+ const state = loadSetupState5();
20255
+ const usesCodex = !!state && (state.configuredAgents.includes("codex-app") || state.configuredAgents.includes("codex"));
20256
+ if (!usesCodex) {
20257
+ return { name, status: "skip", message: "Codex not configured for Runwork" };
20258
+ }
20259
+ const statePath2 = join40(homedir22(), ".codex", ".codex-global-state.json");
20260
+ if (!existsSync44(statePath2)) {
20261
+ return { name, status: "skip", message: "Codex desktop app not detected" };
20262
+ }
20263
+ let savedRoots = [];
20264
+ try {
20265
+ const parsed = JSON.parse(readFileSync38(statePath2, "utf-8"));
20266
+ const roots = parsed["electron-saved-workspace-roots"];
20267
+ savedRoots = Array.isArray(roots) ? roots.filter((r) => typeof r === "string") : [];
20268
+ } catch {
20269
+ return { name, status: "warn", message: "could not read Codex desktop state" };
20270
+ }
20271
+ const runworkDir = join40(homedir22(), ".runwork");
20272
+ if (savedRoots.includes(runworkDir)) {
20273
+ return { name, status: "pass", message: "Runwork project added to Codex desktop sidebar" };
20274
+ }
20275
+ return {
20276
+ name,
20277
+ status: "warn",
20278
+ message: "Runwork project not added to Codex desktop sidebar",
20279
+ fix: "quit the Codex app, then run runwork sync (sync skips this while Codex is open)"
20280
+ };
20281
+ }
19405
20282
  async function checkAgentSetup() {
19406
20283
  const state = loadSetupState5();
19407
20284
  if (!state) {
@@ -19446,7 +20323,7 @@ async function checkAgentSetup() {
19446
20323
  const mcpConfigPath = getMcpConfigPath2(slug, "user");
19447
20324
  if (mcpConfigPath && existsSync44(mcpConfigPath)) {
19448
20325
  try {
19449
- const content = readFileSync37(mcpConfigPath, "utf-8");
20326
+ const content = readFileSync38(mcpConfigPath, "utf-8");
19450
20327
  const missingMcp = state.mcpServers.filter((name) => !content.includes(name));
19451
20328
  if (missingMcp.length > 0) {
19452
20329
  details.push(`${missingMcp.length} MCP server(s) missing from ${slug} config`);
@@ -19504,7 +20381,8 @@ function getMcpConfigPath2(slug, scope) {
19504
20381
  case "windsurf":
19505
20382
  return scope === "project" ? join40(process.cwd(), ".windsurf", "mcp.json") : join40(home, ".windsurf", "mcp.json");
19506
20383
  case "codex":
19507
- return scope === "user" ? join40(home, ".codex", "mcp.json") : null;
20384
+ case "codex-app":
20385
+ return scope === "user" ? join40(home, ".codex", "config.toml") : null;
19508
20386
  case "gemini":
19509
20387
  return scope === "user" ? join40(home, ".gemini", "settings.json") : null;
19510
20388
  default:
@@ -19517,6 +20395,7 @@ function getSkillsDir(slug, scope) {
19517
20395
  case "claude-code":
19518
20396
  return scope === "project" ? join40(process.cwd(), ".claude", "skills") : join40(home, ".claude", "skills");
19519
20397
  case "codex":
20398
+ case "codex-app":
19520
20399
  return scope === "project" ? join40(process.cwd(), ".codex", "skills") : join40(home, ".codex", "skills");
19521
20400
  case "gemini":
19522
20401
  return scope === "project" ? join40(process.cwd(), ".gemini", "skills") : join40(home, ".gemini", "skills");
@@ -19543,7 +20422,9 @@ var CHECK_RUNNERS = [
19543
20422
  { names: ["app-exists"], run: async (ctx) => [await checkAppExists(ctx)] },
19544
20423
  { names: ["git-remote"], run: async (ctx) => [await checkGitRemote(ctx)] },
19545
20424
  { names: ["deploy-freshness"], run: async (ctx) => [await checkDeployFreshness(ctx)] },
19546
- { names: ["agent-setup"], run: async () => [await checkAgentSetup()] }
20425
+ { names: ["agent-setup"], run: async () => [await checkAgentSetup()] },
20426
+ { names: ["codex-network"], run: async () => [await checkCodexNetwork()] },
20427
+ { names: ["codex-desktop-project"], run: async () => [await checkCodexDesktopProject()] }
19547
20428
  ];
19548
20429
  var ALL_CHECK_NAMES = CHECK_RUNNERS.flatMap((r) => r.names);
19549
20430
  async function runAllChecks(options) {
@@ -19616,7 +20497,7 @@ async function applyDoctorFixes(ctx, failingNames) {
19616
20497
  }
19617
20498
 
19618
20499
  // src/agents/runtime-detection.ts
19619
- import { existsSync as existsSync46, readFileSync as readFileSync38, statSync as statSync6, readdirSync as readdirSync13 } from "fs";
20500
+ import { existsSync as existsSync46, readFileSync as readFileSync39, statSync as statSync7, readdirSync as readdirSync13 } from "fs";
19620
20501
  import { homedir as homedir23 } from "os";
19621
20502
  import { join as join42 } from "path";
19622
20503
  var RUNWORK_SESSIONS_DIR = join42(homedir23(), ".runwork", "sessions");
@@ -19686,7 +20567,7 @@ function readHookSessionInfo(sessionId) {
19686
20567
  if (!existsSync46(path2))
19687
20568
  return null;
19688
20569
  try {
19689
- const raw = readFileSync38(path2, "utf8");
20570
+ const raw = readFileSync39(path2, "utf8");
19690
20571
  const parsed = JSON.parse(raw);
19691
20572
  return parsed;
19692
20573
  } catch {
@@ -19727,7 +20608,7 @@ function findCodexRolloutFile(threadId) {
19727
20608
  const full = join42(dir, entry);
19728
20609
  let s;
19729
20610
  try {
19730
- s = statSync6(full);
20611
+ s = statSync7(full);
19731
20612
  } catch {
19732
20613
  continue;
19733
20614
  }
@@ -19764,7 +20645,7 @@ function findNewestClaudeCodeSession() {
19764
20645
  continue;
19765
20646
  const full = join42(projectPath, file);
19766
20647
  try {
19767
- const s = statSync6(full);
20648
+ const s = statSync7(full);
19768
20649
  if (!best || s.mtimeMs > best.mtime) {
19769
20650
  best = {
19770
20651
  sessionId: file.replace(/\.jsonl$/, ""),
@@ -19797,7 +20678,7 @@ function findNewestCodexRollout() {
19797
20678
  const full = join42(dir, entry);
19798
20679
  let s;
19799
20680
  try {
19800
- s = statSync6(full);
20681
+ s = statSync7(full);
19801
20682
  } catch {
19802
20683
  continue;
19803
20684
  }
@@ -19906,7 +20787,9 @@ var CHECK_LABELS = {
19906
20787
  "app-exists": "App exists",
19907
20788
  "git-remote": "Git remote",
19908
20789
  "deploy-freshness": "Deploy freshness",
19909
- "agent-setup": "Agent setup"
20790
+ "agent-setup": "Agent setup",
20791
+ "codex-network": "Codex network",
20792
+ "codex-desktop-project": "Codex project"
19910
20793
  };
19911
20794
  function printHumanReport(report) {
19912
20795
  console.log("");
@@ -19971,7 +20854,7 @@ function parseCheckNames(raw) {
19971
20854
  const unknown = requested.filter((n) => !known.has(n));
19972
20855
  return { only, unknown };
19973
20856
  }
19974
- 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) => {
20857
+ 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) => {
19975
20858
  const asJson = shouldOutputJson(command.optsWithGlobals().json);
19976
20859
  let only;
19977
20860
  if (opts.check) {
@@ -20022,8 +20905,8 @@ var doctorCommand = new Command29("doctor").description("Check system health: au
20022
20905
  // src/commands/share-convo.ts
20023
20906
  init_store();
20024
20907
  init_client();
20025
- import { Command as Command30 } from "commander";
20026
- import { readFileSync as readFileSync39, existsSync as existsSync47 } from "fs";
20908
+ import { Command as Command32 } from "commander";
20909
+ import { readFileSync as readFileSync40, existsSync as existsSync47 } from "fs";
20027
20910
  import { createHash as createHash4 } from "crypto";
20028
20911
  function nativeBundleFormatForAgent(slug) {
20029
20912
  if (slug === "claude-code" || slug === "claude-desktop")
@@ -20065,7 +20948,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
20065
20948
  const credentials = requireAuth();
20066
20949
  const client = new ApiClient(credentials);
20067
20950
  const { workspaceId } = await resolveWorkspace2(client, { workspace: opts.workspace });
20068
- const transcriptContent = readFileSync39(opts.transcriptFile, "utf8");
20951
+ const transcriptContent = readFileSync40(opts.transcriptFile, "utf8");
20069
20952
  const bundles = [
20070
20953
  {
20071
20954
  format: "transcript",
@@ -20090,7 +20973,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
20090
20973
  const nativeFormat = nativeBundleFormatForAgent(sourceAgent);
20091
20974
  if (nativeFormat) {
20092
20975
  try {
20093
- const content = readFileSync39(nativeFilePath, "utf8");
20976
+ const content = readFileSync40(nativeFilePath, "utf8");
20094
20977
  bundles.push({
20095
20978
  format: nativeFormat,
20096
20979
  content,
@@ -20106,7 +20989,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
20106
20989
  let metadata = {};
20107
20990
  if (opts.metadataFile) {
20108
20991
  try {
20109
- metadata = JSON.parse(readFileSync39(opts.metadataFile, "utf8"));
20992
+ metadata = JSON.parse(readFileSync40(opts.metadataFile, "utf8"));
20110
20993
  } catch (err) {
20111
20994
  console.error(`Error: --metadata-file is not valid JSON: ${err instanceof Error ? err.message : err}`);
20112
20995
  process.exit(1);
@@ -20161,17 +21044,17 @@ Skipped: ${result.skipped.map((s) => `${s.identifier} (${s.reason})`).join(", ")
20161
21044
  process.exit(1);
20162
21045
  }
20163
21046
  }
20164
- 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));
21047
+ 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));
20165
21048
 
20166
21049
  // src/commands/save-convo.ts
20167
- import { Command as Command31 } from "commander";
20168
- 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));
21050
+ import { Command as Command33 } from "commander";
21051
+ 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));
20169
21052
 
20170
21053
  // src/commands/inbox.ts
20171
21054
  init_store();
20172
21055
  init_client();
20173
- import { Command as Command32 } from "commander";
20174
- 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) => {
21056
+ import { Command as Command34 } from "commander";
21057
+ 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) => {
20175
21058
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
20176
21059
  const scope = opts.filter === "received" || opts.filter === "sent" || opts.filter === "saved" ? opts.filter : "all";
20177
21060
  const limit = opts.limit ? parseInt(opts.limit, 10) : 50;
@@ -20211,7 +21094,7 @@ Shared conversations (${scope}, ${total}):
20211
21094
  // src/commands/resume.ts
20212
21095
  init_store();
20213
21096
  init_client();
20214
- import { Command as Command33 } from "commander";
21097
+ import { Command as Command35 } from "commander";
20215
21098
  import { writeFileSync as writeFileSync31, mkdirSync as mkdirSync28, realpathSync } from "fs";
20216
21099
  import { homedir as homedir24 } from "os";
20217
21100
  import { join as join43 } from "path";
@@ -20285,7 +21168,7 @@ function isAgentInstalled(agent) {
20285
21168
  }
20286
21169
  return false;
20287
21170
  }
20288
- 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) => {
21171
+ 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) => {
20289
21172
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
20290
21173
  const credentials = requireAuth();
20291
21174
  const client = new ApiClient(credentials);
@@ -20510,7 +21393,7 @@ process.on("uncaughtException", (err) => {
20510
21393
  console.error(`Uncaught exception: ${formatError(err)}`);
20511
21394
  process.exit(1);
20512
21395
  });
20513
- var program = new Command34;
21396
+ var program = new Command36;
20514
21397
  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)");
20515
21398
  program.addCommand(infoCommand);
20516
21399
  program.addCommand(loginCommand);
@@ -20539,6 +21422,8 @@ program.addCommand(syncCommand);
20539
21422
  program.addCommand(buildPluginCommand);
20540
21423
  program.addCommand(uninstallCommand);
20541
21424
  program.addCommand(appsCommand);
21425
+ program.addCommand(membersCommand);
21426
+ program.addCommand(apiCommand);
20542
21427
  program.addCommand(doctorCommand);
20543
21428
  program.addCommand(shareConvoCommand);
20544
21429
  program.addCommand(saveConvoCommand);